"""Source and Citation — the first-class provenance spine. A Source is a reusable record of an origin; a Citation links one Source to one specific fact (a Person, Name, Event, or Relationship — and OwnershipEvent once property lands). A CHECK enforces exactly one target so a citation always points at a single fact. """ import uuid from sqlalchemy import CheckConstraint, ForeignKey, String, Text from sqlalchemy import Enum as SAEnum from sqlalchemy.orm import Mapped, mapped_column from app.models.base import Base from app.models.enums import CitationConfidence from app.models.mixins import SoftDelete, TenantScoped, Timestamps, UUIDPrimaryKey class Source(Base, UUIDPrimaryKey, TenantScoped, Timestamps, SoftDelete): __tablename__ = "sources" title: Mapped[str] = mapped_column(String(512)) author: Mapped[str | None] = mapped_column(String(255)) source_type: Mapped[str | None] = mapped_column(String(64)) # book, census, deed, ... repository: Mapped[str | None] = mapped_column(String(255)) url: Mapped[str | None] = mapped_column(String(1024)) citation_text: Mapped[str | None] = mapped_column(Text) publication_info: Mapped[str | None] = mapped_column(Text) quality_note: Mapped[str | None] = mapped_column(String(255)) class Citation(Base, UUIDPrimaryKey, TenantScoped, Timestamps, SoftDelete): __tablename__ = "citations" __table_args__ = ( CheckConstraint( "(person_id IS NOT NULL)::int + (event_id IS NOT NULL)::int " "+ (name_id IS NOT NULL)::int + (relationship_id IS NOT NULL)::int = 1", name="exactly_one_target", ), ) source_id: Mapped[uuid.UUID] = mapped_column( ForeignKey("sources.id", ondelete="CASCADE"), index=True ) # Exactly one of these is set (see CHECK above). person_id: Mapped[uuid.UUID | None] = mapped_column( ForeignKey("persons.id", ondelete="CASCADE"), index=True ) event_id: Mapped[uuid.UUID | None] = mapped_column( ForeignKey("events.id", ondelete="CASCADE"), index=True ) name_id: Mapped[uuid.UUID | None] = mapped_column( ForeignKey("names.id", ondelete="CASCADE"), index=True ) relationship_id: Mapped[uuid.UUID | None] = mapped_column( ForeignKey("relationships.id", ondelete="CASCADE"), index=True ) # Locality within the source. page: Mapped[str | None] = mapped_column(String(255)) detail: Mapped[str | None] = mapped_column(Text) # entry, line, free notes confidence: Mapped[CitationConfidence | None] = mapped_column( SAEnum(CitationConfidence, name="citation_confidence") )