"""The durable EXIF projection per asset and stage (concept §3, US07-03). One row per ``(asset_id, stage)``: what the stage wanted written, what the file looked like afterwards, and whether anything outside the stage's ownership moved. ``state = divergent`` is the whole point of the table — it survives restarts, keeps the asset out of stages that require verified metadata, and gives a human something to look at instead of a silent repair. """ from __future__ import annotations from datetime import datetime from sqlalchemy import DateTime, ForeignKey, Integer, String from sqlalchemy.orm import Mapped, mapped_column from photo_pipeline.db import Base class ExifProjection(Base): __tablename__ = "exif_projections" asset_id: Mapped[str] = mapped_column(ForeignKey("assets.id"), primary_key=True) stage: Mapped[str] = mapped_column(String, primary_key=True) # safety | analysis id: Mapped[str] = mapped_column(String, nullable=False) projection_version: Mapped[int] = mapped_column(Integer, nullable=False, default=0) desired_json: Mapped[str | None] = mapped_column(String) divergent_fields: Mapped[str | None] = mapped_column(String) # JSON array result_file_sha256: Mapped[str | None] = mapped_column(String) state: Mapped[str] = mapped_column(String, nullable=False) # verified|divergent|failed error_code: Mapped[str | None] = mapped_column(String) verified_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True)) updated_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))