Files
FabledGrimoire/alembic/versions/0111_lesson_rule_links.py
T

54 lines
2.4 KiB
Python

"""lesson_rule_links — a lesson points at the rule it is an instance of
Revision ID: 0111
Revises: 0110
Create Date: 2026-10-01
One row per (lesson, rule) pair with a state: `suggested` while evidence
accumulates, `confirmed` or `rejected` once a judgment is made. Only a
confirmed link changes what surfaces; a rejected one is kept so the pair is not
proposed again. CASCADE on both ends — a link to a record that no longer exists
says nothing. No backfill: no lesson has ever been linked, and inventing a link
would assert a judgment nobody made.
"""
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
from alembic import op
revision = "0111"
down_revision = "0110"
branch_labels = None
depends_on = None
# One place, so the CHECK and the model's LINK_STATES cannot drift (a new
# value later means DROP + ADD CONSTRAINT in the same migration).
_STATES = ("suggested", "confirmed", "rejected")
def upgrade() -> None:
op.create_table(
"lesson_rule_links",
sa.Column("id", sa.BigInteger(), primary_key=True),
sa.Column("lesson_id", sa.Integer(), sa.ForeignKey("notes.id", ondelete="CASCADE"), nullable=False),
sa.Column("rule_id", sa.BigInteger(), sa.ForeignKey("rules.id", ondelete="CASCADE"), nullable=False),
sa.Column("state", sa.Text(), nullable=False, server_default="suggested"),
sa.Column("note", sa.Text(), nullable=True),
sa.Column("evidence", postgresql.JSONB(), nullable=True),
sa.Column("judged_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.text("now()")),
sa.UniqueConstraint("lesson_id", "rule_id", name="uq_lesson_rule_links_pair"),
)
op.create_check_constraint(
"ck_lesson_rule_links_state", "lesson_rule_links",
"state IN (" + ", ".join(f"'{s}'" for s in _STATES) + ")",
)
op.create_index("ix_lesson_rule_links_lesson_id", "lesson_rule_links", ["lesson_id"])
op.create_index("ix_lesson_rule_links_rule_id", "lesson_rule_links", ["rule_id"])
def downgrade() -> None:
op.drop_index("ix_lesson_rule_links_rule_id", table_name="lesson_rule_links")
op.drop_index("ix_lesson_rule_links_lesson_id", table_name="lesson_rule_links")
op.drop_constraint("ck_lesson_rule_links_state", "lesson_rule_links", type_="check")
op.drop_table("lesson_rule_links")