generated from coulomb/repo-seed
Add persisted Suggestion entities with WSJF ranking, relevance bumps, REST/MCP write surfaces, /suggestions dashboard page, and daily triage digest integration. Document the cluster operating model, archive the completed migration workplan, and seed WARDEN-WP-0012 routing scenarios.
120 lines
4.7 KiB
Python
120 lines
4.7 KiB
Python
import enum
|
|
import uuid
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import DateTime, Enum, Float, ForeignKey, Integer, String, Text
|
|
from sqlalchemy.dialects.postgresql import UUID
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
from sqlalchemy.sql import func
|
|
|
|
from api.models.base import Base, TimestampMixin, new_uuid
|
|
|
|
|
|
class SuggestionStage(str, enum.Enum):
|
|
suggestion = "suggestion"
|
|
requirement = "requirement"
|
|
promoted = "promoted"
|
|
declined = "declined"
|
|
|
|
|
|
OPEN_SUGGESTION_STAGES = (SuggestionStage.suggestion, SuggestionStage.requirement)
|
|
|
|
|
|
class Suggestion(Base, TimestampMixin):
|
|
__tablename__ = "suggestions"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True), primary_key=True, default=new_uuid
|
|
)
|
|
domain_id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True),
|
|
ForeignKey("domains.id", ondelete="RESTRICT"),
|
|
nullable=False,
|
|
index=True,
|
|
)
|
|
topic_id: Mapped[uuid.UUID | None] = mapped_column(
|
|
UUID(as_uuid=True), ForeignKey("topics.id", ondelete="SET NULL"), nullable=True
|
|
)
|
|
workplan_id: Mapped[uuid.UUID | None] = mapped_column(
|
|
UUID(as_uuid=True), ForeignKey("workplans.id", ondelete="SET NULL"), nullable=True
|
|
)
|
|
title: Mapped[str] = mapped_column(String(500), nullable=False)
|
|
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
origin: Mapped[str | None] = mapped_column(String(200), nullable=True)
|
|
origin_ref: Mapped[str | None] = mapped_column(String(200), nullable=True, index=True)
|
|
stage: Mapped[SuggestionStage] = mapped_column(
|
|
Enum(SuggestionStage, name="suggestionstage"),
|
|
nullable=False,
|
|
default=SuggestionStage.suggestion,
|
|
index=True,
|
|
)
|
|
relevance: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
|
|
relevance_events: Mapped[int] = mapped_column(
|
|
Integer, nullable=False, default=0, server_default="0"
|
|
)
|
|
last_requested_at: Mapped[datetime | None] = mapped_column(
|
|
DateTime(timezone=True), nullable=True
|
|
)
|
|
base_value: Mapped[float] = mapped_column(Float, nullable=False, default=3.0, server_default="3")
|
|
job_size: Mapped[float] = mapped_column(Float, nullable=False, default=3.0, server_default="3")
|
|
relevance_weight: Mapped[float] = mapped_column(
|
|
Float, nullable=False, default=1.0, server_default="1"
|
|
)
|
|
promoted_task_id: Mapped[uuid.UUID | None] = mapped_column(
|
|
UUID(as_uuid=True), ForeignKey("tasks.id", ondelete="SET NULL"), nullable=True
|
|
)
|
|
|
|
domain: Mapped["Domain"] = relationship("Domain", lazy="selectin") # noqa: F821
|
|
topic: Mapped["Topic | None"] = relationship("Topic", lazy="selectin") # noqa: F821
|
|
workplan: Mapped["Workplan | None"] = relationship("Workplan", lazy="selectin") # noqa: F821
|
|
promoted_task: Mapped["Task | None"] = relationship("Task", lazy="selectin") # noqa: F821
|
|
notes: Mapped[list["SuggestionNote"]] = relationship(
|
|
"SuggestionNote",
|
|
back_populates="suggestion",
|
|
lazy="selectin",
|
|
order_by="SuggestionNote.created_at",
|
|
)
|
|
|
|
@property
|
|
def domain_slug(self) -> str:
|
|
return self.domain.slug if self.domain is not None else ""
|
|
|
|
|
|
class SuggestionNote(Base):
|
|
__tablename__ = "suggestion_notes"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=new_uuid)
|
|
suggestion_id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True),
|
|
ForeignKey("suggestions.id", ondelete="CASCADE"),
|
|
nullable=False,
|
|
index=True,
|
|
)
|
|
stage: Mapped[str] = mapped_column(String(30), nullable=False)
|
|
author: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
|
content: Mapped[str] = mapped_column(Text, nullable=False)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), server_default=func.now(), nullable=False
|
|
)
|
|
|
|
suggestion: Mapped["Suggestion"] = relationship("Suggestion", back_populates="notes")
|
|
|
|
|
|
class SuggestionRelevanceBump(Base):
|
|
"""Audit trail for relevance bumps; supports debounce lookups."""
|
|
|
|
__tablename__ = "suggestion_relevance_bumps"
|
|
|
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=new_uuid)
|
|
suggestion_id: Mapped[uuid.UUID] = mapped_column(
|
|
UUID(as_uuid=True),
|
|
ForeignKey("suggestions.id", ondelete="CASCADE"),
|
|
nullable=False,
|
|
index=True,
|
|
)
|
|
source: Mapped[str] = mapped_column(String(50), nullable=False)
|
|
source_key: Mapped[str] = mapped_column(String(200), nullable=False)
|
|
reason: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), server_default=func.now(), nullable=False
|
|
) |