Files
dotfiles/python/orm/richie/contact.py
2026-03-14 11:49:44 -04:00

169 lines
4.9 KiB
Python

"""Contact database models."""
from __future__ import annotations
from enum import StrEnum
from sqlalchemy import ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from python.orm.richie.base import RichieBase, TableBase
class RelationshipType(StrEnum):
"""Relationship types with default closeness weights.
Default weight is an integer 1-10 where 10 = closest relationship.
Users can override this per-relationship in the UI.
"""
SPOUSE = "spouse"
PARTNER = "partner"
PARENT = "parent"
CHILD = "child"
SIBLING = "sibling"
BEST_FRIEND = "best_friend"
GRANDPARENT = "grandparent"
GRANDCHILD = "grandchild"
AUNT_UNCLE = "aunt_uncle"
NIECE_NEPHEW = "niece_nephew"
COUSIN = "cousin"
IN_LAW = "in_law"
CLOSE_FRIEND = "close_friend"
FRIEND = "friend"
MENTOR = "mentor"
MENTEE = "mentee"
BUSINESS_PARTNER = "business_partner"
COLLEAGUE = "colleague"
MANAGER = "manager"
DIRECT_REPORT = "direct_report"
CLIENT = "client"
ACQUAINTANCE = "acquaintance"
NEIGHBOR = "neighbor"
EX = "ex"
OTHER = "other"
@property
def default_weight(self) -> int:
"""Return the default closeness weight (1-10) for this relationship type."""
weights = {
RelationshipType.SPOUSE: 10,
RelationshipType.PARTNER: 10,
RelationshipType.PARENT: 9,
RelationshipType.CHILD: 9,
RelationshipType.SIBLING: 9,
RelationshipType.BEST_FRIEND: 8,
RelationshipType.GRANDPARENT: 7,
RelationshipType.GRANDCHILD: 7,
RelationshipType.AUNT_UNCLE: 7,
RelationshipType.NIECE_NEPHEW: 7,
RelationshipType.COUSIN: 7,
RelationshipType.IN_LAW: 7,
RelationshipType.CLOSE_FRIEND: 6,
RelationshipType.FRIEND: 6,
RelationshipType.MENTOR: 5,
RelationshipType.MENTEE: 5,
RelationshipType.BUSINESS_PARTNER: 5,
RelationshipType.COLLEAGUE: 4,
RelationshipType.MANAGER: 4,
RelationshipType.DIRECT_REPORT: 4,
RelationshipType.CLIENT: 4,
RelationshipType.ACQUAINTANCE: 3,
RelationshipType.NEIGHBOR: 3,
RelationshipType.EX: 2,
RelationshipType.OTHER: 2,
}
return weights.get(self, 5)
@property
def display_name(self) -> str:
"""Return a human-readable display name."""
return self.value.replace("_", " ").title()
class ContactNeed(RichieBase):
"""Association table: Contact <-> Need."""
__tablename__ = "contact_need"
contact_id: Mapped[int] = mapped_column(
ForeignKey("main.contact.id", ondelete="CASCADE"),
primary_key=True,
)
need_id: Mapped[int] = mapped_column(
ForeignKey("main.need.id", ondelete="CASCADE"),
primary_key=True,
)
class ContactRelationship(RichieBase):
"""Association table: Contact <-> Contact with relationship type and weight."""
__tablename__ = "contact_relationship"
contact_id: Mapped[int] = mapped_column(
ForeignKey("main.contact.id", ondelete="CASCADE"),
primary_key=True,
)
related_contact_id: Mapped[int] = mapped_column(
ForeignKey("main.contact.id", ondelete="CASCADE"),
primary_key=True,
)
relationship_type: Mapped[str] = mapped_column(String(100))
closeness_weight: Mapped[int] = mapped_column(default=5)
class Contact(TableBase):
"""Contact model."""
__tablename__ = "contact"
name: Mapped[str]
age: Mapped[int | None]
bio: Mapped[str | None]
current_job: Mapped[str | None]
gender: Mapped[str | None]
goals: Mapped[str | None]
legal_name: Mapped[str | None]
profile_pic: Mapped[str | None]
safe_conversation_starters: Mapped[str | None]
self_sufficiency_score: Mapped[int | None]
social_structure_style: Mapped[str | None]
ssn: Mapped[str | None]
suffix: Mapped[str | None]
timezone: Mapped[str | None]
topics_to_avoid: Mapped[str | None]
needs: Mapped[list[Need]] = relationship(
"Need",
secondary=ContactNeed.__table__,
back_populates="contacts",
)
related_to: Mapped[list[ContactRelationship]] = relationship(
"ContactRelationship",
foreign_keys=[ContactRelationship.contact_id],
cascade="all, delete-orphan",
)
related_from: Mapped[list[ContactRelationship]] = relationship(
"ContactRelationship",
foreign_keys=[ContactRelationship.related_contact_id],
cascade="all, delete-orphan",
)
class Need(TableBase):
"""Need/accommodation model (e.g., light sensitive, ADHD)."""
__tablename__ = "need"
name: Mapped[str]
description: Mapped[str | None]
contacts: Mapped[list[Contact]] = relationship(
"Contact",
secondary=ContactNeed.__table__,
back_populates="needs",
)