"""Relationship — a typed, qualified edge between two Persons. Modeling parentage as qualified edges (rather than assuming two biological parents) is what makes adoption, donor conception, and blended families first-class. ``qualifier`` applies to parent_child edges; partnership events (marriage, divorce) attach to the Relationship via Event.relationship_id. """ import uuid from sqlalchemy import CheckConstraint, ForeignKey, 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 ParentChildQualifier, RelationshipType from app.models.mixins import SoftDelete, TenantScoped, Timestamps, UUIDPrimaryKey class Relationship(Base, UUIDPrimaryKey, TenantScoped, Timestamps, SoftDelete): __tablename__ = "relationships" __table_args__ = ( CheckConstraint("person_from_id <> person_to_id", name="different_persons"), ) type: Mapped[RelationshipType] = mapped_column( SAEnum(RelationshipType, name="relationship_type") ) # For parent_child: from = parent, to = child. For partnership/sibling: symmetric. person_from_id: Mapped[uuid.UUID] = mapped_column( ForeignKey("persons.id", ondelete="CASCADE"), index=True ) person_to_id: Mapped[uuid.UUID] = mapped_column( ForeignKey("persons.id", ondelete="CASCADE"), index=True ) # Only meaningful for parent_child edges. qualifier: Mapped[ParentChildQualifier | None] = mapped_column( SAEnum(ParentChildQualifier, name="parent_child_qualifier") ) notes: Mapped[str | None] = mapped_column(Text)