from __future__ import annotations import uuid from datetime import datetime from geoalchemy2 import Geometry from sqlalchemy import CheckConstraint, DateTime, ForeignKey, Float, Index, JSON, String, Text, UniqueConstraint, func, text from sqlalchemy.sql.sqltypes import Integer from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import Mapped, mapped_column, relationship from app.db.base import Base SOURCE_CLASSIFICATIONS = ( "authoritative", "corroborative", "contextual", "derived", "experimental", ) SOURCE_FRESHNESS_STATUSES = ( "unknown", "current", "due", "stale", "not_applicable", "review_required", ) SOURCE_INGEST_STATUSES = ( "registered", "configured", "not_configured", "available", "ingested", "failed", "quarantined", "legacy_unverified", ) PROVENANCE_STATUSES = ("complete", "incomplete", "not_applicable") LINEAGE_STATUSES = ("complete", "incomplete", "not_applicable") VALIDATION_STATUSES = ("not_validated", "passed", "failed") QUARANTINE_STATUSES = ("not_quarantined", "quarantined") class Project(Base): __tablename__ = "projects" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) name: Mapped[str] = mapped_column(String(255), nullable=False) description: Mapped[str | None] = mapped_column(Text, nullable=True) region: Mapped[str] = mapped_column(String(120), default="Belgium and Belgian North Sea") status: Mapped[str] = mapped_column(String(32), default="active") created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) areas: Mapped[list["Area"]] = relationship("Area", back_populates="project", cascade="all, delete-orphan") datasets: Mapped[list["Dataset"]] = relationship("Dataset", back_populates="project", cascade="all, delete-orphan") class Area(Base): __tablename__ = "areas" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) project_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False) name: Mapped[str] = mapped_column(String(255), nullable=False) geometry: Mapped[str] = mapped_column(Geometry("MultiPolygon", srid=4326), nullable=False) original_crs: Mapped[str | None] = mapped_column(String(64), nullable=True) area_m2: Mapped[float | None] = mapped_column(Float, nullable=True) bbox: Mapped[str | None] = mapped_column(Geometry("Polygon", srid=4326), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) project: Mapped[Project] = relationship("Project", back_populates="areas") class SourceRegistry(Base): """Server-owned source identity and authority contract. Dataset metadata remains descriptive until a governed importer binds a dataset to both this registry entry and an immutable SourceSnapshot. """ __tablename__ = "source_registry" __table_args__ = ( UniqueConstraint("source_key", name="uq_source_registry_source_key"), CheckConstraint( "classification IN ('authoritative', 'corroborative', 'contextual', 'derived', 'experimental')", name="ck_source_registry_classification", ), CheckConstraint( "freshness_status IN ('unknown', 'current', 'due', 'stale', 'not_applicable', 'review_required')", name="ck_source_registry_freshness_status", ), CheckConstraint( "ingest_status IN ('registered', 'configured', 'not_configured', 'available', 'ingested', " "'failed', 'quarantined', 'legacy_unverified')", name="ck_source_registry_ingest_status", ), ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) source_key: Mapped[str] = mapped_column(String(120), nullable=False) display_name: Mapped[str] = mapped_column(String(255), nullable=False) classification: Mapped[str] = mapped_column(String(32), nullable=False) authority_name: Mapped[str] = mapped_column(String(255), nullable=False, default="unknown", server_default="unknown") authority_scope_json: Mapped[dict] = mapped_column(JSON, nullable=False, default=dict) provider_adapter_key: Mapped[str | None] = mapped_column(String(120), nullable=True) source_url: Mapped[str | None] = mapped_column(Text, nullable=True) license_name: Mapped[str] = mapped_column(String(255), nullable=False, default="unknown", server_default="unknown") license_url: Mapped[str | None] = mapped_column(Text, nullable=True) usage_restrictions: Mapped[str] = mapped_column(Text, nullable=False, default="unknown", server_default="unknown") default_crs: Mapped[str] = mapped_column(String(64), nullable=False, default="unknown", server_default="unknown") default_units: Mapped[str] = mapped_column(String(120), nullable=False, default="unknown", server_default="unknown") spatial_resolution_json: Mapped[dict] = mapped_column(JSON, nullable=False, default=dict) temporal_coverage_json: Mapped[dict] = mapped_column(JSON, nullable=False, default=dict) geographic_coverage_json: Mapped[dict] = mapped_column(JSON, nullable=False, default=dict) expected_geometry_types_json: Mapped[list] = mapped_column(JSON, nullable=False, default=list) expected_attributes_json: Mapped[dict] = mapped_column(JSON, nullable=False, default=dict) usage_policy_json: Mapped[dict] = mapped_column(JSON, nullable=False, default=dict) freshness_status: Mapped[str] = mapped_column( String(32), nullable=False, default="unknown", server_default="unknown" ) ingest_status: Mapped[str] = mapped_column( String(32), nullable=False, default="registered", server_default="registered" ) known_limitations_json: Mapped[list] = mapped_column(JSON, nullable=False, default=list) registry_metadata_json: Mapped[dict] = mapped_column(JSON, nullable=False, default=dict) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) snapshots: Mapped[list["SourceSnapshot"]] = relationship( "SourceSnapshot", back_populates="source_registry", cascade="all, delete-orphan" ) datasets: Mapped[list["Dataset"]] = relationship("Dataset", back_populates="source_registry") dataset_versions: Mapped[list["DatasetVersion"]] = relationship("DatasetVersion", back_populates="source_registry") class SourceSnapshot(Base): """Immutable source-version evidence recorded by governed ingestion.""" __tablename__ = "source_snapshots" __table_args__ = ( UniqueConstraint("source_registry_id", "snapshot_key", name="uq_source_snapshots_registry_key"), CheckConstraint( "freshness_status IN ('unknown', 'current', 'due', 'stale', 'not_applicable', 'review_required')", name="ck_source_snapshots_freshness_status", ), CheckConstraint( "ingest_status IN ('registered', 'configured', 'not_configured', 'available', 'ingested', " "'failed', 'quarantined', 'legacy_unverified')", name="ck_source_snapshots_ingest_status", ), CheckConstraint( "checksum_sha256 = lower(checksum_sha256) AND checksum_sha256 ~ '^[0-9a-f]{64}$'", name="ck_source_snapshots_checksum_sha256", ), Index("ix_source_snapshots_registry_fetched", "source_registry_id", "fetched_at"), Index("ix_source_snapshots_checksum", "checksum_sha256"), ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) source_registry_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("source_registry.id", ondelete="CASCADE"), nullable=False ) snapshot_key: Mapped[str] = mapped_column(String(255), nullable=False) source_version: Mapped[str | None] = mapped_column(String(120), nullable=True) snapshot_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) fetched_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now()) source_url: Mapped[str | None] = mapped_column(Text, nullable=True) checksum_sha256: Mapped[str] = mapped_column(String(64), nullable=False) crs: Mapped[str | None] = mapped_column(String(64), nullable=True) units: Mapped[str | None] = mapped_column(String(120), nullable=True) spatial_resolution_json: Mapped[dict] = mapped_column(JSON, nullable=False, default=dict) temporal_coverage_json: Mapped[dict] = mapped_column(JSON, nullable=False, default=dict) geographic_coverage_json: Mapped[dict] = mapped_column(JSON, nullable=False, default=dict) observed_schema_json: Mapped[dict] = mapped_column(JSON, nullable=False, default=dict) freshness_status: Mapped[str] = mapped_column( String(32), nullable=False, default="unknown", server_default="unknown" ) ingest_status: Mapped[str] = mapped_column( String(32), nullable=False, default="registered", server_default="registered" ) known_limitations_json: Mapped[list] = mapped_column(JSON, nullable=False, default=list) snapshot_metadata_json: Mapped[dict] = mapped_column(JSON, nullable=False, default=dict) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) source_registry: Mapped[SourceRegistry] = relationship("SourceRegistry", back_populates="snapshots") datasets: Mapped[list["Dataset"]] = relationship("Dataset", back_populates="source_snapshot") dataset_versions: Mapped[list["DatasetVersion"]] = relationship("DatasetVersion", back_populates="source_snapshot") quarantines: Mapped[list["DatasetQuarantine"]] = relationship("DatasetQuarantine", back_populates="source_snapshot") class Dataset(Base): __tablename__ = "datasets" __table_args__ = ( CheckConstraint( "valid_to IS NULL OR valid_from IS NULL OR valid_to >= valid_from", name="ck_datasets_temporal_valid_range", ), CheckConstraint( "validation_status IN ('not_validated', 'passed', 'failed')", name="ck_datasets_validation_status", ), CheckConstraint( "provenance_status IN ('complete', 'incomplete', 'not_applicable')", name="ck_datasets_provenance_status", ), CheckConstraint( "lineage_status IN ('complete', 'incomplete', 'not_applicable')", name="ck_datasets_lineage_status", ), CheckConstraint( "quarantine_status IN ('not_quarantined', 'quarantined')", name="ck_datasets_quarantine_status", ), CheckConstraint( "ingest_key IS NULL OR btrim(ingest_key) <> ''", name="ck_datasets_ingest_key_not_blank", ), UniqueConstraint("project_id", "ingest_key", name="uq_datasets_project_ingest_key"), Index( "ix_datasets_project_temporal_series_observed", "project_id", "temporal_series_key", "observed_at", ), ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) project_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False) area_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("areas.id", ondelete="SET NULL"), nullable=True) name: Mapped[str] = mapped_column(String(255), nullable=False) dataset_type: Mapped[str] = mapped_column(String(64), nullable=False) source: Mapped[str] = mapped_column(String(120), nullable=False) storage_path: Mapped[str | None] = mapped_column(String(500), nullable=True) original_filename: Mapped[str | None] = mapped_column(String(255), nullable=True) stored_filename: Mapped[str | None] = mapped_column(String(255), nullable=True) content_type: Mapped[str | None] = mapped_column(String(120), nullable=True) size_bytes: Mapped[int | None] = mapped_column(Integer, nullable=True) checksum_sha256: Mapped[str | None] = mapped_column(String(64), nullable=True) ingest_key: Mapped[str | None] = mapped_column(String(255), nullable=True) derived_from_dataset_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("datasets.id", ondelete="SET NULL"), nullable=True, ) crs: Mapped[str | None] = mapped_column(String(64), nullable=True) bounds_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) resolution_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) bands_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) dataset_role: Mapped[str] = mapped_column(String(32), nullable=False, default="source", server_default="source") source_name: Mapped[str | None] = mapped_column(String(120), nullable=True) reference_layer_name: Mapped[str | None] = mapped_column(String(120), nullable=True) source_metadata: Mapped[dict | None] = mapped_column(JSON, nullable=True) provenance_metadata: Mapped[dict | None] = mapped_column(JSON, nullable=True) source_registry_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("source_registry.id", ondelete="SET NULL"), nullable=True ) source_snapshot_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("source_snapshots.id", ondelete="SET NULL"), nullable=True ) data_contract_key: Mapped[str | None] = mapped_column(String(120), nullable=True) data_contract_version: Mapped[str | None] = mapped_column(String(64), nullable=True) validation_status: Mapped[str] = mapped_column( String(32), nullable=False, default="not_validated", server_default="not_validated", comment="not_validated | passed | failed", ) validation_report_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) provenance_status: Mapped[str] = mapped_column( String(32), nullable=False, default="incomplete", server_default="incomplete", comment="complete | incomplete | not_applicable", ) lineage_status: Mapped[str] = mapped_column( String(32), nullable=False, default="incomplete", server_default="incomplete", comment="complete | incomplete | not_applicable", ) quarantine_status: Mapped[str] = mapped_column( String(32), nullable=False, default="not_quarantined", server_default="not_quarantined", comment="not_quarantined | quarantined", ) imported_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) temporal_series_key: Mapped[str | None] = mapped_column(String(255), nullable=True) observed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) valid_from: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) valid_to: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) temporal_granularity: Mapped[str | None] = mapped_column(String(32), nullable=True) source_version: Mapped[str | None] = mapped_column(String(120), nullable=True) status: Mapped[str] = mapped_column(String(32), default="uploaded") created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) project: Mapped[Project] = relationship("Project", back_populates="datasets") versions: Mapped[list["DatasetVersion"]] = relationship( "DatasetVersion", back_populates="dataset", cascade="all, delete-orphan", ) vector_features: Mapped[list["VectorFeature"]] = relationship( "VectorFeature", back_populates="dataset", cascade="all, delete-orphan", ) source_registry: Mapped[SourceRegistry | None] = relationship("SourceRegistry", back_populates="datasets") source_snapshot: Mapped[SourceSnapshot | None] = relationship("SourceSnapshot", back_populates="datasets") parent_lineage_edges: Mapped[list["DatasetLineageEdge"]] = relationship( "DatasetLineageEdge", foreign_keys="DatasetLineageEdge.parent_dataset_id", back_populates="parent_dataset", cascade="all, delete-orphan", ) child_lineage_edges: Mapped[list["DatasetLineageEdge"]] = relationship( "DatasetLineageEdge", foreign_keys="DatasetLineageEdge.child_dataset_id", back_populates="child_dataset", cascade="all, delete-orphan", ) quarantines: Mapped[list["DatasetQuarantine"]] = relationship( "DatasetQuarantine", back_populates="dataset", cascade="all, delete-orphan" ) class DatasetVersion(Base): __tablename__ = "dataset_versions" __table_args__ = ( CheckConstraint( "valid_to IS NULL OR valid_from IS NULL OR valid_to >= valid_from", name="ck_dataset_versions_temporal_valid_range", ), CheckConstraint( "validation_status IN ('not_validated', 'passed', 'failed')", name="ck_dataset_versions_validation_status", ), CheckConstraint( "provenance_status IN ('complete', 'incomplete', 'not_applicable')", name="ck_dataset_versions_provenance_status", ), CheckConstraint( "lineage_status IN ('complete', 'incomplete', 'not_applicable')", name="ck_dataset_versions_lineage_status", ), CheckConstraint( "ingest_key IS NULL OR btrim(ingest_key) <> ''", name="ck_dataset_versions_ingest_key_not_blank", ), Index("ix_dataset_versions_dataset_version", "dataset_id", "version", unique=True), UniqueConstraint("dataset_id", "ingest_key", name="uq_dataset_versions_dataset_ingest_key"), ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) dataset_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("datasets.id", ondelete="CASCADE"), nullable=False) version: Mapped[int] = mapped_column(Integer, default=1) storage_path: Mapped[str | None] = mapped_column(String(500), nullable=True) source_version: Mapped[str | None] = mapped_column(String(120), nullable=True) observed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) valid_from: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) valid_to: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) checksum_sha256: Mapped[str | None] = mapped_column(String(64), nullable=True) ingest_key: Mapped[str | None] = mapped_column(String(255), nullable=True) source_metadata: Mapped[dict | None] = mapped_column(JSON, nullable=True) provenance_metadata: Mapped[dict | None] = mapped_column(JSON, nullable=True) source_registry_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("source_registry.id", ondelete="SET NULL"), nullable=True ) source_snapshot_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("source_snapshots.id", ondelete="SET NULL"), nullable=True ) data_contract_key: Mapped[str | None] = mapped_column(String(120), nullable=True) data_contract_version: Mapped[str | None] = mapped_column(String(64), nullable=True) validation_status: Mapped[str] = mapped_column( String(32), nullable=False, default="not_validated", server_default="not_validated", comment="not_validated | passed | failed", ) validation_report_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) provenance_status: Mapped[str] = mapped_column( String(32), nullable=False, default="incomplete", server_default="incomplete", comment="complete | incomplete | not_applicable", ) lineage_status: Mapped[str] = mapped_column( String(32), nullable=False, default="incomplete", server_default="incomplete", comment="complete | incomplete | not_applicable", ) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) dataset: Mapped[Dataset] = relationship("Dataset", back_populates="versions") source_registry: Mapped[SourceRegistry | None] = relationship("SourceRegistry", back_populates="dataset_versions") source_snapshot: Mapped[SourceSnapshot | None] = relationship("SourceSnapshot", back_populates="dataset_versions") quarantines: Mapped[list["DatasetQuarantine"]] = relationship("DatasetQuarantine", back_populates="dataset_version") class DatasetLineageEdge(Base): """Immutable relationship between input/output datasets and transforms.""" __tablename__ = "dataset_lineage_edges" __table_args__ = ( CheckConstraint("parent_dataset_id <> child_dataset_id", name="ck_dataset_lineage_edges_distinct_datasets"), UniqueConstraint( "parent_dataset_id", "child_dataset_id", "relation_type", "transformation_name", name="uq_dataset_lineage_edges_relation", ), Index("ix_dataset_lineage_edges_parent", "parent_dataset_id"), Index("ix_dataset_lineage_edges_child", "child_dataset_id"), ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) parent_dataset_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("datasets.id", ondelete="CASCADE"), nullable=False ) child_dataset_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("datasets.id", ondelete="CASCADE"), nullable=False ) parent_dataset_version_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("dataset_versions.id", ondelete="SET NULL"), nullable=True ) child_dataset_version_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("dataset_versions.id", ondelete="SET NULL"), nullable=True ) relation_type: Mapped[str] = mapped_column(String(64), nullable=False) transformation_name: Mapped[str] = mapped_column(String(255), nullable=False) transformation_version: Mapped[str | None] = mapped_column(String(120), nullable=True) parameters_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) input_checksum_sha256: Mapped[str | None] = mapped_column(String(64), nullable=True) output_checksum_sha256: Mapped[str | None] = mapped_column(String(64), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) parent_dataset: Mapped[Dataset] = relationship( "Dataset", foreign_keys=[parent_dataset_id], back_populates="parent_lineage_edges" ) child_dataset: Mapped[Dataset] = relationship( "Dataset", foreign_keys=[child_dataset_id], back_populates="child_lineage_edges" ) class DatasetQuarantine(Base): """Durable fail-closed record for rejected or doubtful source artifacts.""" __tablename__ = "dataset_quarantines" __table_args__ = ( CheckConstraint( "dataset_id IS NOT NULL OR dataset_version_id IS NOT NULL OR source_snapshot_id IS NOT NULL", name="ck_dataset_quarantines_target_present", ), CheckConstraint( "status IN ('quarantined', 'released', 'rejected')", name="ck_dataset_quarantines_status", ), Index("ix_dataset_quarantines_dataset_status", "dataset_id", "status"), Index("ix_dataset_quarantines_snapshot_status", "source_snapshot_id", "status"), ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) dataset_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("datasets.id", ondelete="SET NULL"), nullable=True ) dataset_version_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("dataset_versions.id", ondelete="SET NULL"), nullable=True ) source_snapshot_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("source_snapshots.id", ondelete="SET NULL"), nullable=True ) stage: Mapped[str] = mapped_column(String(64), nullable=False) reason_code: Mapped[str] = mapped_column(String(120), nullable=False) details_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) artifact_path: Mapped[str | None] = mapped_column(Text, nullable=True) artifact_checksum_sha256: Mapped[str | None] = mapped_column(String(64), nullable=True) status: Mapped[str] = mapped_column( String(32), nullable=False, default="quarantined", server_default="quarantined" ) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) resolved_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) resolved_by: Mapped[str | None] = mapped_column(String(120), nullable=True) dataset: Mapped[Dataset | None] = relationship("Dataset", back_populates="quarantines") dataset_version: Mapped[DatasetVersion | None] = relationship("DatasetVersion", back_populates="quarantines") source_snapshot: Mapped[SourceSnapshot | None] = relationship("SourceSnapshot", back_populates="quarantines") class VectorFeature(Base): __tablename__ = "vector_features" __table_args__ = ( Index("ix_vector_features_dataset_id", "dataset_id"), Index("ix_vector_features_geometry", "geometry", postgresql_using="gist"), Index("ix_vector_features_dataset_source_feature", "dataset_id", "source_feature_id"), Index( "ix_vector_features_dataset_municipality", "dataset_id", text("(properties_json ->> 'municipality')"), ), ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) dataset_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("datasets.id", ondelete="CASCADE"), nullable=False) feature_class: Mapped[str | None] = mapped_column(String(120), nullable=True) source_feature_id: Mapped[str | None] = mapped_column(String(255), nullable=True) properties_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) geometry: Mapped[str] = mapped_column(Geometry("Geometry", srid=4326, spatial_index=False), nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) dataset: Mapped[Dataset] = relationship("Dataset", back_populates="vector_features") class AnalysisRun(Base): __tablename__ = "analysis_runs" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) project_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False) area_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("areas.id", ondelete="SET NULL"), nullable=True) dataset_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("datasets.id", ondelete="SET NULL"), nullable=True) job_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("jobs.id", ondelete="SET NULL"), nullable=True) analysis_type: Mapped[str] = mapped_column(String(64), nullable=False) status: Mapped[str] = mapped_column(String(32), nullable=False) model_name: Mapped[str | None] = mapped_column(String(255), nullable=True) model_version: Mapped[str | None] = mapped_column(String(120), nullable=True) parameters_json: Mapped[dict] = mapped_column(JSON, nullable=False) result_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) error_message: Mapped[str | None] = mapped_column(Text, nullable=True) class Detection(Base): __tablename__ = "detections" __table_args__ = ( Index("ix_detections_project_id", "project_id"), Index("ix_detections_dataset_id", "dataset_id"), Index("ix_detections_analysis_run_id", "analysis_run_id"), Index("ix_detections_class_name", "class_name"), Index("ix_detections_geometry", "geometry", postgresql_using="gist"), ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) project_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False) dataset_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("datasets.id", ondelete="SET NULL"), nullable=True) analysis_run_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("analysis_runs.id", ondelete="SET NULL"), nullable=True) job_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("jobs.id", ondelete="SET NULL"), nullable=True) model_name: Mapped[str] = mapped_column(String(255), nullable=False) model_version: Mapped[str | None] = mapped_column(String(120), nullable=True) class_name: Mapped[str] = mapped_column(String(120), nullable=False) confidence: Mapped[float] = mapped_column(Float, nullable=False) geometry: Mapped[str] = mapped_column(Geometry("Geometry", srid=4326, spatial_index=False), nullable=False) bbox_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) source_tile_path: Mapped[str | None] = mapped_column(String(500), nullable=True) properties_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) class Segmentation(Base): __tablename__ = "segmentations" __table_args__ = ( Index("ix_segmentations_project_id", "project_id"), Index("ix_segmentations_dataset_id", "dataset_id"), Index("ix_segmentations_analysis_run_id", "analysis_run_id"), Index("ix_segmentations_job_id", "job_id"), Index("ix_segmentations_class_name", "class_name"), Index("ix_segmentations_geometry", "geometry", postgresql_using="gist"), ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) project_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False) dataset_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("datasets.id", ondelete="SET NULL"), nullable=True) job_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("jobs.id", ondelete="SET NULL"), nullable=True) analysis_run_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("analysis_runs.id", ondelete="SET NULL"), nullable=True) model_name: Mapped[str] = mapped_column(String(255), nullable=False) model_version: Mapped[str | None] = mapped_column(String(120), nullable=True) class_name: Mapped[str] = mapped_column(String(120), nullable=False) confidence: Mapped[float | None] = mapped_column(Float, nullable=True) geometry: Mapped[str] = mapped_column(Geometry("MultiPolygon", srid=4326, spatial_index=False), nullable=False) bbox_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) area_m2: Mapped[float | None] = mapped_column(Float, nullable=True) mask_path: Mapped[str | None] = mapped_column(Text, nullable=True) source_tile_path: Mapped[str | None] = mapped_column(String(500), nullable=True) tile_index: Mapped[int | None] = mapped_column(Integer, nullable=True) properties_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) provenance_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) class QualityCheck(Base): __tablename__ = "quality_checks" __table_args__ = ( Index("ix_quality_checks_project_id", "project_id"), Index("ix_quality_checks_reference_dataset_id", "reference_dataset_id"), Index("ix_quality_checks_candidate_dataset_id", "candidate_dataset_id"), Index("ix_quality_checks_analysis_run_id", "analysis_run_id"), ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) project_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False) job_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("jobs.id", ondelete="SET NULL"), nullable=True) analysis_run_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("analysis_runs.id", ondelete="SET NULL"), nullable=True) candidate_dataset_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("datasets.id", ondelete="SET NULL"), nullable=True) reference_dataset_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("datasets.id", ondelete="CASCADE"), nullable=False) check_type: Mapped[str] = mapped_column(String(120), nullable=False) status: Mapped[str] = mapped_column(String(32), nullable=False) score: Mapped[float | None] = mapped_column(Float, nullable=True) parameters_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) findings_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) class Metric(Base): __tablename__ = "metrics" __table_args__ = ( Index("ix_metrics_quality_check_id", "quality_check_id"), Index("ix_metrics_analysis_run_id", "analysis_run_id"), ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) quality_check_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("quality_checks.id", ondelete="CASCADE"), nullable=True) analysis_run_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("analysis_runs.id", ondelete="SET NULL"), nullable=True) metric_key: Mapped[str] = mapped_column(String(120), nullable=False) metric_value: Mapped[float | None] = mapped_column(Float, nullable=True) metric_unit: Mapped[str | None] = mapped_column(String(64), nullable=True) label: Mapped[str | None] = mapped_column(String(120), nullable=True) metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) class DetectionReview(Base): __tablename__ = "detection_reviews" __table_args__ = ( CheckConstraint( "evidence_role IN ('false_positive', 'false_negative')", name="ck_detection_reviews_evidence_role", ), CheckConstraint( "decision IN ('confirmed_model_false_positive', 'confirmed_model_false_negative', " "'reference_gap_or_change', 'qa_alignment_mismatch', " "'imagery_obscured_or_uncertain', 'uncertain', 'unreviewed')", name="ck_detection_reviews_decision", ), UniqueConstraint( "quality_check_id", "evidence_role", "evidence_feature_id", name="uq_detection_reviews_evidence", ), Index("ix_detection_reviews_project_id", "project_id"), Index("ix_detection_reviews_quality_check_id", "quality_check_id"), Index("ix_detection_reviews_analysis_run_id", "analysis_run_id"), Index("ix_detection_reviews_decision", "decision"), ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) project_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False) quality_check_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("quality_checks.id", ondelete="CASCADE"), nullable=False, ) analysis_run_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("analysis_runs.id", ondelete="SET NULL"), nullable=True, ) evidence_role: Mapped[str] = mapped_column(String(32), nullable=False) evidence_feature_id: Mapped[str] = mapped_column(String(255), nullable=False) detection_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("detections.id", ondelete="SET NULL"), nullable=True, ) reference_feature_id: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("vector_features.id", ondelete="SET NULL"), nullable=True, ) decision: Mapped[str] = mapped_column(String(64), nullable=False, default="unreviewed", server_default="unreviewed") notes: Mapped[str | None] = mapped_column(Text, nullable=True) reviewed_by: Mapped[str] = mapped_column(String(120), nullable=False, default="operator", server_default="operator") created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) class Export(Base): __tablename__ = "exports" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) project_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False) analysis_run_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("analysis_runs.id", ondelete="SET NULL"), nullable=True) export_type: Mapped[str] = mapped_column(String(64), nullable=False) storage_path: Mapped[str] = mapped_column(String(500), nullable=False) metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) class Job(Base): __tablename__ = "jobs" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) job_type: Mapped[str] = mapped_column(String(128), nullable=False) status: Mapped[str] = mapped_column(String(32), nullable=False, default="queued") project_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False) dataset_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("datasets.id", ondelete="SET NULL"), nullable=True) input_dataset_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("datasets.id", ondelete="SET NULL"), nullable=True) output_dataset_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("datasets.id", ondelete="SET NULL"), nullable=True) parameters_json: Mapped[dict] = mapped_column(JSON, nullable=False, default=dict) result_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) error_message: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) class AoiOperation(Base): __tablename__ = "aoi_operations" __table_args__ = ( CheckConstraint( "status IN ('queued', 'running', 'partial', 'success', 'failed', 'cancelled')", name="ck_aoi_operations_status", ), Index("ix_aoi_operations_project_status", "project_id", "status"), Index("ix_aoi_operations_geometry", "geometry", postgresql_using="gist"), ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) project_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False) area_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("areas.id", ondelete="SET NULL"), nullable=True) parent_job_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("jobs.id", ondelete="SET NULL"), nullable=True) operation_type: Mapped[str] = mapped_column(String(128), nullable=False) status: Mapped[str] = mapped_column(String(32), nullable=False, default="queued") geometry: Mapped[str] = mapped_column(Geometry("MultiPolygon", srid=4326, spatial_index=False), nullable=False) request_json: Mapped[dict] = mapped_column(JSON, nullable=False, default=dict) plan_json: Mapped[dict] = mapped_column(JSON, nullable=False, default=dict) result_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) error_message: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) class AoiOperationPartition(Base): __tablename__ = "aoi_operation_partitions" __table_args__ = ( CheckConstraint( "status IN ('queued', 'running', 'success', 'failed', 'skipped')", name="ck_aoi_operation_partitions_status", ), UniqueConstraint("operation_id", "partition_key", name="uq_aoi_operation_partition_key"), Index("ix_aoi_operation_partitions_operation_status", "operation_id", "status"), Index("ix_aoi_operation_partitions_geometry", "geometry", postgresql_using="gist"), ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) operation_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("aoi_operations.id", ondelete="CASCADE"), nullable=False) child_job_id: Mapped[uuid.UUID | None] = mapped_column(UUID(as_uuid=True), ForeignKey("jobs.id", ondelete="SET NULL"), nullable=True) partition_key: Mapped[str] = mapped_column(String(255), nullable=False) provider_key: Mapped[str] = mapped_column(String(120), nullable=False) product_key: Mapped[str] = mapped_column(String(120), nullable=False) ordinal: Mapped[int] = mapped_column(Integer, nullable=False) status: Mapped[str] = mapped_column(String(32), nullable=False, default="queued") geometry: Mapped[str] = mapped_column(Geometry("MultiPolygon", srid=4326, spatial_index=False), nullable=False) attempt_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0) max_attempts: Mapped[int] = mapped_column(Integer, nullable=False, default=3) checkpoint_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) result_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) error_message: Mapped[str | None] = mapped_column(Text, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())