from sqlalchemy import ForeignKey, Index, Integer, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column from scribe.models import Base from scribe.models.base import CreatedAtMixin, TimestampMixin, SoftDeleteMixin class System(Base, TimestampMixin, SoftDeleteMixin): """A per-project, reusable, self-describing subsystem/area. Any record (note, task, or issue) can be associated with one or more systems via record_systems, so research notes, build-tasks, and corrective work line up under the same area — and recurring problem-areas become filterable. Self-describing (name + description) because a bare name is never enough to convey what a system is or how it's used. """ __tablename__ = "systems" id: Mapped[int] = mapped_column(primary_key=True) user_id: Mapped[int] = mapped_column( Integer, ForeignKey("users.id", ondelete="CASCADE") ) project_id: Mapped[int] = mapped_column( Integer, ForeignKey("projects.id", ondelete="CASCADE") ) name: Mapped[str] = mapped_column(Text, default="", server_default="") description: Mapped[str | None] = mapped_column(Text, nullable=True) color: Mapped[str | None] = mapped_column(Text, nullable=True) # active | archived — systems accumulate; archive rather than delete. status: Mapped[str] = mapped_column(Text, default="active", server_default="active") order_index: Mapped[int] = mapped_column(Integer, default=0, server_default="0") __table_args__ = ( Index("ix_systems_project_id", "project_id"), ) def to_dict(self) -> dict: return { "id": self.id, "user_id": self.user_id, "project_id": self.project_id, "name": self.name, "description": self.description, "color": self.color, "status": self.status, "order_index": self.order_index, "created_at": self.created_at.isoformat(), "updated_at": self.updated_at.isoformat(), } class RecordSystem(Base, CreatedAtMixin): """M2M association: a record (notes.id — any note/task/issue) <-> a System. Mutable over time; uniqueness keeps a record from linking the same system twice. Cascades on both sides, so deleting a record or a system clears its associations. """ __tablename__ = "record_systems" id: Mapped[int] = mapped_column(primary_key=True) note_id: Mapped[int] = mapped_column( Integer, ForeignKey("notes.id", ondelete="CASCADE") ) system_id: Mapped[int] = mapped_column( Integer, ForeignKey("systems.id", ondelete="CASCADE") ) __table_args__ = ( UniqueConstraint("note_id", "system_id", name="uq_record_systems_note_system"), Index("ix_record_systems_note_id", "note_id"), Index("ix_record_systems_system_id", "system_id"), )