feat: add measured detection review loop
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-15 03:00:08 +02:00
parent 94ecd377b7
commit d22abe8e7b
27 changed files with 1578 additions and 29 deletions
+57 -1
View File
@@ -4,7 +4,7 @@ import uuid
from datetime import datetime
from geoalchemy2 import Geometry
from sqlalchemy import CheckConstraint, DateTime, ForeignKey, Float, Index, JSON, String, Text, func
from sqlalchemy import CheckConstraint, DateTime, ForeignKey, Float, Index, JSON, String, Text, UniqueConstraint, func
from sqlalchemy.sql.sqltypes import Integer
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
@@ -272,6 +272,62 @@ class Metric(Base):
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"