Refactor local InSAR asset and production workflows
This commit is contained in:
@@ -27,6 +27,8 @@ from .orm import (
|
||||
ScanCursorORM,
|
||||
PathInventoryORM,
|
||||
SourceProductAssetORM,
|
||||
SourceMetadataDocumentORM,
|
||||
SARSceneGeometryProfileORM,
|
||||
OrbitAssetORM,
|
||||
SceneOrbitBindingORM,
|
||||
OrbitAssetDerivativeORM,
|
||||
@@ -103,7 +105,8 @@ __all__ = [
|
||||
"TimeseriesStackPlanORM", "TimeseriesStackPlanItemORM", "TimeseriesStackPlanEdgeORM",
|
||||
"SystemTaskORM", "TaskLogORM", "SystemJobORM", "ScanStateORM",
|
||||
"ManagedRootORM", "ScanCursorORM", "PathInventoryORM",
|
||||
"SourceProductAssetORM", "OrbitAssetORM", "SceneOrbitBindingORM",
|
||||
"SourceProductAssetORM", "SourceMetadataDocumentORM", "SARSceneGeometryProfileORM",
|
||||
"OrbitAssetORM", "SceneOrbitBindingORM",
|
||||
"OrbitAssetDerivativeORM", "AssetInventoryStateORM", "AssetInventoryIssueORM",
|
||||
"WorkflowDefORM", "WorkflowRunORM", "WorkflowStepORM", "WorkflowArtifactORM",
|
||||
"SystemWorkerHeartbeatORM",
|
||||
|
||||
@@ -3,7 +3,7 @@ SQLAlchemy ORM 模型定义。
|
||||
所有数据库表对应的 ORM 类均在此文件中定义。
|
||||
"""
|
||||
from sqlalchemy import (
|
||||
Column, Integer, BigInteger, String, Boolean, Float, JSON, Text,
|
||||
Column, Integer, BigInteger, String, Boolean, Float, JSON, Text, LargeBinary,
|
||||
DateTime, func, ForeignKey, UniqueConstraint, Index,
|
||||
)
|
||||
from sqlalchemy.orm import relationship
|
||||
@@ -384,7 +384,15 @@ class PairingMetricCacheORM(Base):
|
||||
spatial_baseline_meters = Column(Float, index=True, nullable=True)
|
||||
scene_center_distance_meters = Column(Float, index=True, nullable=True)
|
||||
scene_overlap_ratio = Column(Float, index=True, nullable=True)
|
||||
pair_aoi_overlap_ratio = Column(Float, nullable=True)
|
||||
orbit_direction = Column(String, index=True, nullable=True)
|
||||
same_relative_orbit = Column(Boolean, nullable=False, default=False, server_default="false", index=True)
|
||||
master_relative_orbit = Column(String(64), nullable=True)
|
||||
slave_relative_orbit = Column(String(64), nullable=True)
|
||||
dinsar_quality_tier = Column(String(16), nullable=False, default="C", server_default="C", index=True)
|
||||
dinsar_quality_score = Column(Float, nullable=True)
|
||||
dinsar_readiness = Column(String(32), nullable=False, default="CANDIDATE", server_default="CANDIDATE", index=True)
|
||||
dinsar_reasons_json = Column(JSON, nullable=True)
|
||||
same_satellite = Column(Boolean, nullable=False, default=True)
|
||||
same_satellite_family = Column(Boolean, nullable=False, default=True, server_default="true")
|
||||
same_look_direction = Column(Boolean, nullable=False, default=True, server_default="true")
|
||||
@@ -875,6 +883,12 @@ class SourceProductAssetORM(Base):
|
||||
mtime_epoch = Column(Float, nullable=True)
|
||||
checksum_sha256 = Column(String(64), nullable=True)
|
||||
checksum_status = Column(String(32), nullable=False, default="NOT_COMPUTED", server_default="NOT_COMPUTED")
|
||||
archive_integrity_status = Column(String(32), nullable=False, default="NOT_CHECKED", server_default="NOT_CHECKED", index=True)
|
||||
archive_integrity_method = Column(String(64), nullable=True)
|
||||
archive_integrity_checked_at = Column(DateTime, nullable=True)
|
||||
archive_integrity_error = Column(Text, nullable=True)
|
||||
archive_integrity_version = Column(String(32), nullable=True)
|
||||
archive_integrity_member_count = Column(Integer, nullable=True)
|
||||
parser_name = Column(String(64), nullable=True)
|
||||
parser_version = Column(String(32), nullable=True)
|
||||
parse_status = Column(String(32), nullable=False, default="PENDING", server_default="PENDING", index=True)
|
||||
@@ -895,6 +909,84 @@ class SourceProductAssetORM(Base):
|
||||
)
|
||||
|
||||
|
||||
class SourceMetadataDocumentORM(Base):
|
||||
__tablename__ = "source_metadata_documents"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
source_asset_id = Column(Integer, ForeignKey("source_product_assets.id", ondelete="CASCADE"), index=True, nullable=False)
|
||||
radar_data_id = Column(Integer, ForeignKey("radar_data.id", ondelete="SET NULL"), index=True, nullable=True)
|
||||
satellite_family = Column(String(32), index=True, nullable=True)
|
||||
source_format = Column(String(32), index=True, nullable=True)
|
||||
document_type = Column(String(32), index=True, nullable=False)
|
||||
member_path = Column(String, nullable=False)
|
||||
content_sha256 = Column(String(64), index=True, nullable=False)
|
||||
content_encoding = Column(String(16), nullable=False, default="gzip", server_default="gzip")
|
||||
content_bytes = Column(LargeBinary, nullable=False)
|
||||
content_size_bytes = Column(BigInteger, nullable=True)
|
||||
archive_path = Column(String, nullable=True)
|
||||
archive_mtime = Column(Float, nullable=True)
|
||||
parser_version = Column(String(32), nullable=True)
|
||||
parse_status = Column(String(32), nullable=False, default="OK", server_default="OK", index=True)
|
||||
parse_error = Column(Text, nullable=True)
|
||||
extracted_at = Column(DateTime, nullable=True)
|
||||
created_at = Column(DateTime, server_default=func.now(), nullable=False)
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
|
||||
source_asset = relationship("SourceProductAssetORM")
|
||||
radar_data = relationship("RadarDataORM")
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint("source_asset_id", "document_type", "member_path", name="uq_source_metadata_document_member"),
|
||||
Index("idx_source_metadata_documents_asset_type", "source_asset_id", "document_type"),
|
||||
Index("idx_source_metadata_documents_radar_type", "radar_data_id", "document_type"),
|
||||
)
|
||||
|
||||
|
||||
class SARSceneGeometryProfileORM(Base):
|
||||
__tablename__ = "sar_scene_geometry_profiles"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
source_asset_id = Column(Integer, ForeignKey("source_product_assets.id", ondelete="CASCADE"), unique=True, index=True, nullable=False)
|
||||
radar_data_id = Column(Integer, ForeignKey("radar_data.id", ondelete="CASCADE"), unique=True, index=True, nullable=True)
|
||||
satellite_family = Column(String(32), index=True, nullable=True)
|
||||
satellite = Column(String(32), index=True, nullable=True)
|
||||
source_format = Column(String(32), index=True, nullable=True)
|
||||
imaging_mode = Column(String(64), index=True, nullable=True)
|
||||
polarization = Column(String(64), index=True, nullable=True)
|
||||
orbit_direction = Column(String(32), index=True, nullable=True)
|
||||
look_direction = Column(String(32), index=True, nullable=True)
|
||||
absolute_orbit = Column(String(64), index=True, nullable=True)
|
||||
relative_orbit = Column(String(64), index=True, nullable=True)
|
||||
acquisition_start_time_utc = Column(DateTime, index=True, nullable=True)
|
||||
acquisition_stop_time_utc = Column(DateTime, nullable=True)
|
||||
scene_center_lon = Column(Float, nullable=True)
|
||||
scene_center_lat = Column(Float, nullable=True)
|
||||
footprint_geom = Column(Geometry("POLYGON", srid=4326), index=True, nullable=True)
|
||||
footprint_polygon = Column(JSON, nullable=True)
|
||||
swath_summary_json = Column(JSON, nullable=True)
|
||||
burst_summary_json = Column(JSON, nullable=True)
|
||||
incidence_angle_min = Column(Float, nullable=True)
|
||||
incidence_angle_max = Column(Float, nullable=True)
|
||||
doppler_summary_json = Column(JSON, nullable=True)
|
||||
state_vector_summary_json = Column(JSON, nullable=True)
|
||||
metadata_quality = Column(String(32), nullable=False, default="UNKNOWN", server_default="UNKNOWN", index=True)
|
||||
production_readiness = Column(String(32), nullable=False, default="UNKNOWN", server_default="UNKNOWN", index=True)
|
||||
readiness_reasons_json = Column(JSON, nullable=True)
|
||||
parser_version = Column(String(32), nullable=True)
|
||||
parsed_at = Column(DateTime, nullable=True)
|
||||
created_at = Column(DateTime, server_default=func.now(), nullable=False)
|
||||
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
|
||||
source_asset = relationship("SourceProductAssetORM")
|
||||
radar_data = relationship("RadarDataORM")
|
||||
|
||||
__table_args__ = (
|
||||
Index("idx_sar_scene_geometry_profiles_family_date", "satellite_family", "acquisition_start_time_utc"),
|
||||
Index("idx_sar_scene_geometry_profiles_track", "satellite_family", "relative_orbit", "orbit_direction"),
|
||||
Index("idx_sar_scene_geometry_profiles_readiness", "production_readiness", "metadata_quality"),
|
||||
)
|
||||
|
||||
|
||||
class OrbitAssetORM(Base):
|
||||
__tablename__ = "orbit_assets"
|
||||
|
||||
|
||||
@@ -261,10 +261,10 @@ class PairingRequest(BaseModel):
|
||||
"""D-InSAR 配对请求的参数模型(增强版 v2.0)"""
|
||||
# === 时空约束(保留) ===
|
||||
time_baseline_min: int = Field(default=1, ge=0, le=3650)
|
||||
time_baseline_max: int = Field(default=90, ge=1, le=3650)
|
||||
time_baseline_max: int = Field(default=30, ge=1, le=3650)
|
||||
overlap_threshold: float = Field(default=0.5, ge=0.0, le=1.0)
|
||||
spatial_baseline_max_meters: int = Field(default=3000, ge=0, le=PAIRING_CENTER_DISTANCE_MAX_METERS)
|
||||
limit_footprint_center_distance: bool = False
|
||||
spatial_baseline_max_meters: int = Field(default=5000, ge=0, le=PAIRING_CENTER_DISTANCE_MAX_METERS)
|
||||
limit_footprint_center_distance: bool = True
|
||||
coverage_diversity_penalty: float = Field(default=0.3, ge=0.0, le=1.0)
|
||||
require_same_imaging_mode: bool = True
|
||||
require_same_polarization: bool = True
|
||||
@@ -280,7 +280,7 @@ class PairingRequest(BaseModel):
|
||||
slave_date_to: Optional[str] = Field(default=None, pattern=r'^\d{8}$|^$')
|
||||
|
||||
# === 配对策略(新增) ===
|
||||
strategy: str = Field(default="all", pattern=r'^(all|sbas|sequential|star)$')
|
||||
strategy: str = Field(default="dinsar_production", pattern=r'^dinsar_production$')
|
||||
num_connections: int = Field(default=1, ge=1, le=10)
|
||||
reference_image_id: Optional[int] = None
|
||||
|
||||
@@ -303,6 +303,7 @@ class PairingRequest(BaseModel):
|
||||
normalized['overlap_threshold'] = normalized['pair_footprint_overlap_min_ratio']
|
||||
if normalized.get('footprint_center_distance_max_meters') not in (None, ''):
|
||||
normalized['spatial_baseline_max_meters'] = normalized['footprint_center_distance_max_meters']
|
||||
normalized['strategy'] = 'dinsar_production'
|
||||
return normalized
|
||||
|
||||
@field_validator(
|
||||
@@ -343,8 +344,15 @@ class PairingRequest(BaseModel):
|
||||
return None
|
||||
if not isinstance(value, list):
|
||||
return value
|
||||
normalized = [str(item).strip() for item in value if str(item).strip()]
|
||||
return normalized or None
|
||||
normalized = []
|
||||
for item in value:
|
||||
compact = str(item).strip().upper().replace("-", "").replace("_", "").replace(" ", "")
|
||||
if compact in {"LT1", "LT1A", "LT1B", "LUTAN1", "LUTAN1A", "LUTAN1B"}:
|
||||
normalized.append("LT1")
|
||||
elif compact in {"S1", "S1A", "S1B", "S1C", "SENTINEL1", "SENTINEL1A", "SENTINEL1B", "SENTINEL1C"}:
|
||||
normalized.append("S1")
|
||||
deduped = list(dict.fromkeys(normalized))
|
||||
return deduped or ["__UNSUPPORTED_DINSAR_FAMILY__"]
|
||||
|
||||
@field_validator('master_date_to')
|
||||
@classmethod
|
||||
@@ -381,6 +389,16 @@ class RadarPair(BaseModel):
|
||||
time_baseline_days: int
|
||||
spatial_baseline_meters: float
|
||||
scene_center_distance_meters: Optional[float] = None
|
||||
scene_overlap_ratio: Optional[float] = None
|
||||
pair_aoi_overlap_ratio: Optional[float] = None
|
||||
dinsar_quality_tier: Optional[str] = None
|
||||
dinsar_quality_score: Optional[float] = None
|
||||
dinsar_readiness: Optional[str] = None
|
||||
dinsar_reasons: Optional[List[str]] = None
|
||||
same_relative_orbit: Optional[bool] = None
|
||||
master_relative_orbit: Optional[str] = None
|
||||
slave_relative_orbit: Optional[str] = None
|
||||
production_summary: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
class PairingResponse(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user