Add basis workspace filters, stale-on-reimport, and variant usage metrics

This commit is contained in:
dwindown
2026-04-28 18:44:43 +07:00
parent 08a1352268
commit c3f7a4463b
7 changed files with 1144 additions and 92 deletions

View File

@@ -5,6 +5,7 @@ Exports all SQLAlchemy ORM models for use in the application.
"""
from app.database import Base
from app.models.ai_generation_run import AIGenerationRun
from app.models.item import Item
from app.models.session import Session
from app.models.tryout import Tryout
@@ -17,6 +18,7 @@ from app.models.website import Website
__all__ = [
"Base",
"AIGenerationRun",
"User",
"Website",
"Tryout",

View File

@@ -0,0 +1,72 @@
"""
AI generation run model.
Represents one admin generation request that can produce one or many variants.
"""
from datetime import datetime
from typing import Optional
from sqlalchemy import DateTime, ForeignKey, Integer, String, Text, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
class AIGenerationRun(Base):
__tablename__ = "ai_generation_runs"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
basis_item_id: Mapped[int] = mapped_column(
ForeignKey("items.id", ondelete="CASCADE", onupdate="CASCADE"),
nullable=False,
index=True,
comment="Basis item ID",
)
source_snapshot_question_id: Mapped[Optional[int]] = mapped_column(
ForeignKey("tryout_snapshot_questions.id", ondelete="SET NULL", onupdate="CASCADE"),
nullable=True,
index=True,
comment="Source snapshot question ID",
)
target_level: Mapped[str] = mapped_column(
String(50),
nullable=False,
comment="Target level (mudah/sulit)",
)
requested_count: Mapped[int] = mapped_column(
Integer,
nullable=False,
default=1,
comment="Requested output count",
)
model: Mapped[str] = mapped_column(
String(255),
nullable=False,
comment="Model identifier",
)
prompt_version: Mapped[str] = mapped_column(
String(50),
nullable=False,
default="v1",
comment="Prompt template version",
)
operator_notes: Mapped[Optional[str]] = mapped_column(
Text,
nullable=True,
comment="Optional admin notes",
)
created_by: Mapped[str] = mapped_column(
String(255),
nullable=False,
comment="Admin username",
)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, server_default=func.now()
)
generated_items: Mapped[list["Item"]] = relationship(
"Item",
back_populates="generation_run",
lazy="selectin",
)

View File

@@ -155,6 +155,39 @@ class Item(Base):
nullable=True,
comment="Original item ID (for AI variants)",
)
generation_run_id: Mapped[Union[int, None]] = mapped_column(
ForeignKey("ai_generation_runs.id", ondelete="SET NULL", onupdate="CASCADE"),
nullable=True,
index=True,
comment="AI generation run ID",
)
source_snapshot_question_id: Mapped[Union[int, None]] = mapped_column(
ForeignKey("tryout_snapshot_questions.id", ondelete="SET NULL", onupdate="CASCADE"),
nullable=True,
index=True,
comment="Source snapshot question ID",
)
variant_status: Mapped[str] = mapped_column(
String(50),
nullable=False,
default="active",
comment="Lifecycle status (active/draft/approved/rejected/archived/stale)",
)
reviewed_by: Mapped[Union[str, None]] = mapped_column(
String(255),
nullable=True,
comment="Reviewer username",
)
reviewed_at: Mapped[Union[datetime, None]] = mapped_column(
DateTime(timezone=True),
nullable=True,
comment="Review timestamp",
)
review_notes: Mapped[Union[str, None]] = mapped_column(
Text,
nullable=True,
comment="Review notes",
)
# Timestamps
created_at: Mapped[datetime] = mapped_column(
@@ -187,6 +220,11 @@ class Item(Base):
lazy="selectin",
cascade="all, delete-orphan",
)
generation_run: Mapped[Union["AIGenerationRun", None]] = relationship(
"AIGenerationRun",
back_populates="generated_items",
lazy="selectin",
)
# Constraints and indexes
__table_args__ = (
@@ -203,10 +241,11 @@ class Item(Base):
"website_id",
"slot",
"level",
unique=True,
unique=False,
),
Index("ix_items_calibrated", "calibrated"),
Index("ix_items_basis_item_id", "basis_item_id"),
Index("ix_items_variant_status", "variant_status"),
# IRT b parameter constraint [-3, +3]
CheckConstraint(
"irt_b IS NULL OR (irt_b >= -3 AND irt_b <= 3)",