Files
mousehold/apps/api/mousehold_api/models.py
2026-06-28 13:48:15 +02:00

644 lines
28 KiB
Python

from __future__ import annotations
from datetime import UTC, date, datetime
from decimal import Decimal
from uuid import uuid4
from mousehold_domain import (
AccountType,
EventObservationRelation,
FinancialEventStatus,
FinancialEventType,
ImportConnectionStatus,
ImportConnectionType,
ObservationStatus,
PaymentLineStatus,
PaymentStatus,
PlanningStatus,
ProjectStatus,
ReconciliationStatus,
RecurringCommitmentStatus,
RecurringInterval,
RelationType,
SettlementStatus,
SourceType,
VisibilityMode,
)
from sqlalchemy import (
Boolean,
Date,
DateTime,
ForeignKey,
Index,
Integer,
Numeric,
String,
Text,
UniqueConstraint,
)
from sqlalchemy import Enum as SqlEnum
from sqlalchemy.dialects.sqlite import JSON
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .db import Base
def new_id() -> str:
return str(uuid4())
def utcnow() -> datetime:
return datetime.now(UTC)
Money = Numeric(14, 2)
class Household(Base):
__tablename__ = "households"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
name: Mapped[str] = mapped_column(String(200), nullable=False)
default_currency: Mapped[str] = mapped_column(String(3), default="EUR", nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, nullable=False
)
users: Mapped[list[User]] = relationship(
back_populates="household", cascade="all, delete-orphan"
)
accounts: Mapped[list[Account]] = relationship(
back_populates="household", cascade="all, delete-orphan"
)
class User(Base):
__tablename__ = "users"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
household_id: Mapped[str] = mapped_column(
ForeignKey("households.id"), nullable=False, index=True
)
display_name: Mapped[str] = mapped_column(String(200), nullable=False)
email: Mapped[str | None] = mapped_column(String(320), nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, nullable=False
)
household: Mapped[Household] = relationship(back_populates="users")
login_identities: Mapped[list[LoginIdentity]] = relationship(
back_populates="user", cascade="all, delete-orphan"
)
class LoginIdentity(Base):
__tablename__ = "login_identities"
__table_args__ = (
UniqueConstraint("provider", "provider_subject", name="uq_login_provider_subject"),
)
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
user_id: Mapped[str] = mapped_column(ForeignKey("users.id"), nullable=False, index=True)
provider: Mapped[str] = mapped_column(String(80), nullable=False)
provider_subject: Mapped[str] = mapped_column(String(320), nullable=False)
email: Mapped[str | None] = mapped_column(String(320), nullable=True)
display_name: Mapped[str | None] = mapped_column(String(200), nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, nullable=False
)
user: Mapped[User] = relationship(back_populates="login_identities")
class LocalSession(Base):
__tablename__ = "local_sessions"
__table_args__ = (
Index("ix_local_session_token", "token_hash"),
Index("ix_local_session_user", "user_id", "expires_at"),
)
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
user_id: Mapped[str] = mapped_column(ForeignKey("users.id"), nullable=False, index=True)
household_id: Mapped[str] = mapped_column(ForeignKey("households.id"), nullable=False, index=True)
token_hash: Mapped[str] = mapped_column(String(64), nullable=False, unique=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, nullable=False
)
last_seen_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, nullable=False
)
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
class Account(Base):
__tablename__ = "accounts"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
household_id: Mapped[str] = mapped_column(
ForeignKey("households.id"), nullable=False, index=True
)
owner_user_id: Mapped[str | None] = mapped_column(
ForeignKey("users.id"), nullable=True, index=True
)
name: Mapped[str] = mapped_column(String(200), nullable=False)
account_type: Mapped[AccountType] = mapped_column(SqlEnum(AccountType), nullable=False)
institution_name: Mapped[str | None] = mapped_column(String(200), nullable=True)
currency: Mapped[str] = mapped_column(String(3), default="EUR", nullable=False)
visibility_mode: Mapped[VisibilityMode] = mapped_column(SqlEnum(VisibilityMode), nullable=False)
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, nullable=False
)
household: Mapped[Household] = relationship(back_populates="accounts")
class RawObservation(Base):
__tablename__ = "raw_observations"
__table_args__ = (
Index("ix_observation_dedupe", "household_id", "source_type", "raw_hash"),
Index("ix_observation_external_refs", "provider_transaction_id", "message_id"),
)
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
household_id: Mapped[str] = mapped_column(
ForeignKey("households.id"), nullable=False, index=True
)
source_type: Mapped[SourceType] = mapped_column(SqlEnum(SourceType), nullable=False, index=True)
source_account_id: Mapped[str | None] = mapped_column(
ForeignKey("accounts.id"), nullable=True, index=True
)
owner_user_id: Mapped[str | None] = mapped_column(
ForeignKey("users.id"), nullable=True, index=True
)
observed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
received_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, nullable=False
)
raw_hash: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
source_reference: Mapped[str | None] = mapped_column(String(500), nullable=True)
provider_transaction_id: Mapped[str | None] = mapped_column(
String(200), nullable=True, index=True
)
message_id: Mapped[str | None] = mapped_column(String(500), nullable=True, index=True)
raw_text: Mapped[str | None] = mapped_column(Text, nullable=True)
raw_file_path: Mapped[str | None] = mapped_column(String(1000), nullable=True)
raw_payload_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
parsed_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
parser_name: Mapped[str | None] = mapped_column(String(120), nullable=True)
parser_version: Mapped[str | None] = mapped_column(String(40), nullable=True)
confidence: Mapped[Decimal] = mapped_column(Money, default=Decimal("1.00"), nullable=False)
status: Mapped[ObservationStatus] = mapped_column(
SqlEnum(ObservationStatus), default=ObservationStatus.NEW, nullable=False
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, nullable=False
)
event_links: Mapped[list[EventObservationLink]] = relationship(
back_populates="observation", cascade="all, delete-orphan"
)
class FinancialEvent(Base):
__tablename__ = "financial_events"
__table_args__ = (
Index("ix_events_household_status_date", "household_id", "status", "event_date"),
)
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
household_id: Mapped[str] = mapped_column(
ForeignKey("households.id"), nullable=False, index=True
)
source_account_id: Mapped[str | None] = mapped_column(
ForeignKey("accounts.id"), nullable=True, index=True
)
payment_account_id: Mapped[str | None] = mapped_column(
ForeignKey("accounts.id"), nullable=True, index=True
)
payer_user_id: Mapped[str | None] = mapped_column(
ForeignKey("users.id"), nullable=True, index=True
)
event_type: Mapped[FinancialEventType] = mapped_column(
SqlEnum(FinancialEventType), nullable=False
)
status: Mapped[FinancialEventStatus] = mapped_column(
SqlEnum(FinancialEventStatus), default=FinancialEventStatus.CANDIDATE, nullable=False
)
event_date: Mapped[date] = mapped_column(Date, nullable=False)
booking_date: Mapped[date | None] = mapped_column(Date, nullable=True)
due_date: Mapped[date | None] = mapped_column(Date, nullable=True)
amount: Mapped[Decimal] = mapped_column(Money, nullable=False)
currency: Mapped[str] = mapped_column(String(3), default="EUR", nullable=False)
merchant: Mapped[str | None] = mapped_column(String(300), nullable=True)
counterparty: Mapped[str | None] = mapped_column(String(300), nullable=True)
description: Mapped[str | None] = mapped_column(Text, nullable=True)
category_id: Mapped[str | None] = mapped_column(
ForeignKey("categories.id"), nullable=True, index=True
)
project_id: Mapped[str | None] = mapped_column(
ForeignKey("projects.id"), nullable=True, index=True
)
created_by_user_id: Mapped[str | None] = mapped_column(
ForeignKey("users.id"), nullable=True, index=True
)
fingerprint: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, nullable=False
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, onupdate=utcnow, nullable=False
)
observation_links: Mapped[list[EventObservationLink]] = relationship(
back_populates="event", cascade="all, delete-orphan"
)
split_lines: Mapped[list[SplitLine]] = relationship(
back_populates="event", cascade="all, delete-orphan"
)
class EventObservationLink(Base):
__tablename__ = "event_observation_links"
__table_args__ = (
UniqueConstraint(
"event_id", "observation_id", "relation", name="uq_event_observation_relation"
),
)
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
event_id: Mapped[str] = mapped_column(
ForeignKey("financial_events.id"), nullable=False, index=True
)
observation_id: Mapped[str] = mapped_column(
ForeignKey("raw_observations.id"), nullable=False, index=True
)
relation: Mapped[EventObservationRelation] = mapped_column(
SqlEnum(EventObservationRelation), nullable=False
)
confidence: Mapped[Decimal] = mapped_column(Money, default=Decimal("1.00"), nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, nullable=False
)
event: Mapped[FinancialEvent] = relationship(back_populates="observation_links")
observation: Mapped[RawObservation] = relationship(back_populates="event_links")
class EventRelation(Base):
__tablename__ = "event_relations"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
from_event_id: Mapped[str] = mapped_column(
ForeignKey("financial_events.id"), nullable=False, index=True
)
to_event_id: Mapped[str] = mapped_column(
ForeignKey("financial_events.id"), nullable=False, index=True
)
relation_type: Mapped[RelationType] = mapped_column(SqlEnum(RelationType), nullable=False)
confidence: Mapped[Decimal] = mapped_column(Money, default=Decimal("1.00"), nullable=False)
created_by: Mapped[str] = mapped_column(String(40), default="system", nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, nullable=False
)
class ReconciliationDecision(Base):
__tablename__ = "reconciliation_decisions"
__table_args__ = (
UniqueConstraint("household_id", "suggestion_id", name="uq_reconciliation_decision"),
Index("ix_reconciliation_decision_household", "household_id", "decision"),
)
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
household_id: Mapped[str] = mapped_column(
ForeignKey("households.id"), nullable=False, index=True
)
suggestion_id: Mapped[str] = mapped_column(String(160), nullable=False)
decision: Mapped[str] = mapped_column(String(40), nullable=False)
kind: Mapped[str] = mapped_column(String(80), nullable=False)
relation_type: Mapped[RelationType] = mapped_column(SqlEnum(RelationType), nullable=False)
primary_event_id: Mapped[str] = mapped_column(
ForeignKey("financial_events.id"), nullable=False, index=True
)
confirming_event_id: Mapped[str] = mapped_column(
ForeignKey("financial_events.id"), nullable=False, index=True
)
confidence: Mapped[Decimal] = mapped_column(Money, default=Decimal("1.00"), nullable=False)
actor_user_id: Mapped[str | None] = mapped_column(ForeignKey("users.id"), nullable=True)
reason: Mapped[str | None] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, nullable=False
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, onupdate=utcnow, nullable=False
)
class SplitLine(Base):
__tablename__ = "split_lines"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
event_id: Mapped[str] = mapped_column(
ForeignKey("financial_events.id"), nullable=False, index=True
)
user_id: Mapped[str] = mapped_column(ForeignKey("users.id"), nullable=False, index=True)
share_amount: Mapped[Decimal] = mapped_column(Money, nullable=False)
share_percent: Mapped[Decimal | None] = mapped_column(Money, nullable=True)
reason: Mapped[str] = mapped_column(String(80), default="manual", nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, nullable=False
)
event: Mapped[FinancialEvent] = relationship(back_populates="split_lines")
class Category(Base):
__tablename__ = "categories"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
household_id: Mapped[str] = mapped_column(
ForeignKey("households.id"), nullable=False, index=True
)
parent_id: Mapped[str | None] = mapped_column(ForeignKey("categories.id"), nullable=True)
name: Mapped[str] = mapped_column(String(160), nullable=False)
path: Mapped[str] = mapped_column(String(500), nullable=False)
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
class Tag(Base):
__tablename__ = "tags"
__table_args__ = (UniqueConstraint("household_id", "name", name="uq_tag_household_name"),)
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
household_id: Mapped[str] = mapped_column(
ForeignKey("households.id"), nullable=False, index=True
)
name: Mapped[str] = mapped_column(String(120), nullable=False)
class EventTag(Base):
__tablename__ = "event_tags"
__table_args__ = (UniqueConstraint("event_id", "tag_id", name="uq_event_tag"),)
event_id: Mapped[str] = mapped_column(ForeignKey("financial_events.id"), primary_key=True)
tag_id: Mapped[str] = mapped_column(ForeignKey("tags.id"), primary_key=True)
class Project(Base):
__tablename__ = "projects"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
household_id: Mapped[str] = mapped_column(
ForeignKey("households.id"), nullable=False, index=True
)
name: Mapped[str] = mapped_column(String(220), nullable=False)
description: Mapped[str | None] = mapped_column(Text, nullable=True)
start_date: Mapped[date | None] = mapped_column(Date, nullable=True)
end_date: Mapped[date | None] = mapped_column(Date, nullable=True)
default_split_rule_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
status: Mapped[ProjectStatus] = mapped_column(
SqlEnum(ProjectStatus), default=ProjectStatus.PLANNING, nullable=False
)
currency: Mapped[str] = mapped_column(String(3), default="EUR", nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, nullable=False
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, onupdate=utcnow, nullable=False
)
class ProjectParticipant(Base):
__tablename__ = "project_participants"
__table_args__ = (UniqueConstraint("project_id", "user_id", name="uq_project_participant"),)
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
project_id: Mapped[str] = mapped_column(ForeignKey("projects.id"), nullable=False, index=True)
user_id: Mapped[str] = mapped_column(ForeignKey("users.id"), nullable=False, index=True)
role: Mapped[str] = mapped_column(String(80), default="participant", nullable=False)
default_share_percent: Mapped[Decimal | None] = mapped_column(Money, nullable=True)
class ProjectBudgetLine(Base):
__tablename__ = "project_budget_lines"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
project_id: Mapped[str] = mapped_column(ForeignKey("projects.id"), nullable=False, index=True)
category_id: Mapped[str | None] = mapped_column(ForeignKey("categories.id"), nullable=True)
name: Mapped[str] = mapped_column(String(180), nullable=False)
budgeted_amount: Mapped[Decimal] = mapped_column(Money, default=Decimal("0.00"), nullable=False)
committed_amount_cached: Mapped[Decimal | None] = mapped_column(Money, nullable=True)
paid_amount_cached: Mapped[Decimal | None] = mapped_column(Money, nullable=True)
sort_order: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
class ProjectPlannedItem(Base):
__tablename__ = "project_planned_items"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
project_id: Mapped[str] = mapped_column(ForeignKey("projects.id"), nullable=False, index=True)
budget_line_id: Mapped[str | None] = mapped_column(
ForeignKey("project_budget_lines.id"), nullable=True, index=True
)
description: Mapped[str] = mapped_column(String(300), nullable=False)
estimated_amount: Mapped[Decimal] = mapped_column(
Money, default=Decimal("0.00"), nullable=False
)
committed_amount: Mapped[Decimal | None] = mapped_column(Money, nullable=True)
expected_date: Mapped[date | None] = mapped_column(Date, nullable=True)
due_date: Mapped[date | None] = mapped_column(Date, nullable=True)
expected_payer_account_id: Mapped[str | None] = mapped_column(
ForeignKey("accounts.id"), nullable=True
)
split_rule_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
planning_status: Mapped[PlanningStatus] = mapped_column(
SqlEnum(PlanningStatus), default=PlanningStatus.IDEA, nullable=False
)
payment_status: Mapped[PaymentStatus] = mapped_column(
SqlEnum(PaymentStatus), default=PaymentStatus.UNPAID, nullable=False
)
reconciliation_status: Mapped[ReconciliationStatus] = mapped_column(
SqlEnum(ReconciliationStatus), default=ReconciliationStatus.NO_EVIDENCE, nullable=False
)
settlement_status: Mapped[SettlementStatus] = mapped_column(
SqlEnum(SettlementStatus), default=SettlementStatus.UNSETTLED, nullable=False
)
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, nullable=False
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, onupdate=utcnow, nullable=False
)
class PaymentLine(Base):
__tablename__ = "payment_lines"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
planned_item_id: Mapped[str | None] = mapped_column(
ForeignKey("project_planned_items.id"), nullable=True, index=True
)
event_id: Mapped[str | None] = mapped_column(
ForeignKey("financial_events.id"), nullable=True, index=True
)
amount: Mapped[Decimal] = mapped_column(Money, nullable=False)
currency: Mapped[str] = mapped_column(String(3), default="EUR", nullable=False)
date: Mapped[date] = mapped_column(Date, nullable=False)
paid_by_user_id: Mapped[str | None] = mapped_column(
ForeignKey("users.id"), nullable=True, index=True
)
payment_account_id: Mapped[str | None] = mapped_column(
ForeignKey("accounts.id"), nullable=True, index=True
)
source_observation_id: Mapped[str | None] = mapped_column(
ForeignKey("raw_observations.id"), nullable=True
)
status: Mapped[PaymentLineStatus] = mapped_column(
SqlEnum(PaymentLineStatus), default=PaymentLineStatus.EXPECTED, nullable=False
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, nullable=False
)
class RecurringCommitment(Base):
__tablename__ = "recurring_commitments"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
household_id: Mapped[str] = mapped_column(
ForeignKey("households.id"), nullable=False, index=True
)
provider: Mapped[str] = mapped_column(String(200), nullable=False)
description: Mapped[str | None] = mapped_column(Text, nullable=True)
amount_expected: Mapped[Decimal] = mapped_column(Money, nullable=False)
amount_min: Mapped[Decimal | None] = mapped_column(Money, nullable=True)
amount_max: Mapped[Decimal | None] = mapped_column(Money, nullable=True)
currency: Mapped[str] = mapped_column(String(3), default="EUR", nullable=False)
interval: Mapped[RecurringInterval] = mapped_column(SqlEnum(RecurringInterval), nullable=False)
next_due_date: Mapped[date] = mapped_column(Date, nullable=False)
payer_account_id: Mapped[str | None] = mapped_column(ForeignKey("accounts.id"), nullable=True)
split_rule_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
category_id: Mapped[str | None] = mapped_column(ForeignKey("categories.id"), nullable=True)
project_id: Mapped[str | None] = mapped_column(ForeignKey("projects.id"), nullable=True)
notice_period: Mapped[str | None] = mapped_column(String(120), nullable=True)
renewal_date: Mapped[date | None] = mapped_column(Date, nullable=True)
status: Mapped[RecurringCommitmentStatus] = mapped_column(
SqlEnum(RecurringCommitmentStatus), default=RecurringCommitmentStatus.ACTIVE, nullable=False
)
matching_rule_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, nullable=False
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, onupdate=utcnow, nullable=False
)
class PromisedPayment(Base):
__tablename__ = "promised_payments"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
household_id: Mapped[str] = mapped_column(
ForeignKey("households.id"), nullable=False, index=True
)
related_event_id: Mapped[str | None] = mapped_column(
ForeignKey("financial_events.id"), nullable=True, index=True
)
provider: Mapped[str] = mapped_column(String(120), nullable=False)
amount: Mapped[Decimal] = mapped_column(Money, nullable=False)
currency: Mapped[str] = mapped_column(String(3), default="EUR", nullable=False)
created_date: Mapped[date] = mapped_column(Date, nullable=False)
due_date: Mapped[date] = mapped_column(Date, nullable=False, index=True)
expected_funding_account_id: Mapped[str | None] = mapped_column(
ForeignKey("accounts.id"), nullable=True
)
responsible_user_id: Mapped[str | None] = mapped_column(ForeignKey("users.id"), nullable=True)
status: Mapped[str] = mapped_column(String(40), default="expected", nullable=False)
source_observation_id: Mapped[str | None] = mapped_column(
ForeignKey("raw_observations.id"), nullable=True
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, nullable=False
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, onupdate=utcnow, nullable=False
)
class ImportConnection(Base):
__tablename__ = "import_connections"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
household_id: Mapped[str] = mapped_column(
ForeignKey("households.id"), nullable=False, index=True
)
owner_user_id: Mapped[str] = mapped_column(ForeignKey("users.id"), nullable=False, index=True)
connection_type: Mapped[ImportConnectionType] = mapped_column(
SqlEnum(ImportConnectionType), nullable=False
)
display_name: Mapped[str] = mapped_column(String(200), nullable=False)
status: Mapped[ImportConnectionStatus] = mapped_column(
SqlEnum(ImportConnectionStatus), default=ImportConnectionStatus.NEEDS_AUTH, nullable=False
)
last_sync_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
last_error: Mapped[str | None] = mapped_column(Text, nullable=True)
secret_storage_ref: Mapped[str | None] = mapped_column(String(500), nullable=True)
runs_locally: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
connector_config_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, nullable=False
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, onupdate=utcnow, nullable=False
)
class ImportRun(Base):
__tablename__ = "import_runs"
__table_args__ = (
Index("ix_import_runs_household_started", "household_id", "started_at"),
Index("ix_import_runs_connection_started", "connection_id", "started_at"),
)
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
household_id: Mapped[str] = mapped_column(
ForeignKey("households.id"), nullable=False, index=True
)
connection_id: Mapped[str | None] = mapped_column(
ForeignKey("import_connections.id"), nullable=True, index=True
)
actor_user_id: Mapped[str | None] = mapped_column(ForeignKey("users.id"), nullable=True)
source_type: Mapped[str | None] = mapped_column(String(80), nullable=True)
status: Mapped[str] = mapped_column(String(40), default="running", nullable=False)
imported_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
duplicate_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
created_event_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
skipped_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
error: Mapped[str | None] = mapped_column(Text, nullable=True)
summary_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
started_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, nullable=False
)
finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
class AuditLog(Base):
__tablename__ = "audit_log"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_id)
household_id: Mapped[str] = mapped_column(
ForeignKey("households.id"), nullable=False, index=True
)
actor_user_id: Mapped[str | None] = mapped_column(ForeignKey("users.id"), nullable=True)
action: Mapped[str] = mapped_column(String(120), nullable=False)
entity_type: Mapped[str] = mapped_column(String(120), nullable=False)
entity_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
details_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=utcnow, nullable=False
)