Checkpoint React frontend migration
This commit is contained in:
280
backend/alembic/versions/20260331_000001_initial_schema.py
Normal file
280
backend/alembic/versions/20260331_000001_initial_schema.py
Normal file
@@ -0,0 +1,280 @@
|
||||
"""initial schema
|
||||
|
||||
Revision ID: 20260331_000001
|
||||
Revises:
|
||||
Create Date: 2026-03-31 12:30:00
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import context, op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "20260331_000001"
|
||||
down_revision: Union[str, None] = None
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def _table_exists(name: str) -> bool:
|
||||
if context.is_offline_mode():
|
||||
return False
|
||||
return sa.inspect(op.get_bind()).has_table(name)
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
if not _table_exists("websites"):
|
||||
op.create_table(
|
||||
"websites",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("site_url", sa.String(length=512), nullable=False),
|
||||
sa.Column("site_name", sa.String(length=255), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index("ix_websites_site_url", "websites", ["site_url"], unique=True)
|
||||
|
||||
if not _table_exists("tryouts"):
|
||||
op.create_table(
|
||||
"tryouts",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("website_id", sa.Integer(), nullable=False),
|
||||
sa.Column("tryout_id", sa.String(length=255), nullable=False),
|
||||
sa.Column("name", sa.String(length=255), nullable=False),
|
||||
sa.Column("description", sa.String(length=1000), nullable=True),
|
||||
sa.Column("scoring_mode", sa.String(length=50), nullable=False),
|
||||
sa.Column("selection_mode", sa.String(length=50), nullable=False),
|
||||
sa.Column("normalization_mode", sa.String(length=50), nullable=False),
|
||||
sa.Column("min_sample_for_dynamic", sa.Integer(), nullable=False),
|
||||
sa.Column("static_rataan", sa.Float(), nullable=False),
|
||||
sa.Column("static_sb", sa.Float(), nullable=False),
|
||||
sa.Column("ai_generation_enabled", sa.Boolean(), nullable=False),
|
||||
sa.Column("hybrid_transition_slot", sa.Integer(), nullable=True),
|
||||
sa.Column("min_calibration_sample", sa.Integer(), nullable=False),
|
||||
sa.Column("theta_estimation_method", sa.String(length=50), nullable=False),
|
||||
sa.Column("fallback_to_ctt_on_error", sa.Boolean(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.ForeignKeyConstraint(["website_id"], ["websites.id"], ondelete="CASCADE", onupdate="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("website_id", "tryout_id", name="uq_tryouts_website_id_tryout_id"),
|
||||
sa.CheckConstraint("min_sample_for_dynamic > 0", name="ck_min_sample_positive"),
|
||||
sa.CheckConstraint("static_rataan > 0", name="ck_static_rataan_positive"),
|
||||
sa.CheckConstraint("static_sb > 0", name="ck_static_sb_positive"),
|
||||
sa.CheckConstraint("min_calibration_sample > 0", name="ck_min_calibration_positive"),
|
||||
)
|
||||
op.create_index("ix_tryouts_website_id", "tryouts", ["website_id"], unique=False)
|
||||
op.create_index("ix_tryouts_tryout_id", "tryouts", ["tryout_id"], unique=False)
|
||||
|
||||
if not _table_exists("users"):
|
||||
op.create_table(
|
||||
"users",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("wp_user_id", sa.String(length=255), nullable=False),
|
||||
sa.Column("website_id", sa.Integer(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.ForeignKeyConstraint(["website_id"], ["websites.id"], ondelete="CASCADE", onupdate="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("wp_user_id", "website_id", name="uq_users_wp_user_id_website_id"),
|
||||
)
|
||||
op.create_index("ix_users_wp_user_id", "users", ["wp_user_id"], unique=False)
|
||||
op.create_index("ix_users_website_id", "users", ["website_id"], unique=False)
|
||||
|
||||
if not _table_exists("items"):
|
||||
op.create_table(
|
||||
"items",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("tryout_id", sa.String(length=255), nullable=False),
|
||||
sa.Column("website_id", sa.Integer(), nullable=False),
|
||||
sa.Column("slot", sa.Integer(), nullable=False),
|
||||
sa.Column("level", sa.String(length=50), nullable=False),
|
||||
sa.Column("stem", sa.Text(), nullable=False),
|
||||
sa.Column("options", sa.JSON(), nullable=False),
|
||||
sa.Column("correct_answer", sa.String(length=10), nullable=False),
|
||||
sa.Column("explanation", sa.Text(), nullable=True),
|
||||
sa.Column("ctt_p", sa.Float(), nullable=True),
|
||||
sa.Column("ctt_bobot", sa.Float(), nullable=True),
|
||||
sa.Column("ctt_category", sa.String(length=50), nullable=True),
|
||||
sa.Column("irt_b", sa.Float(), nullable=True),
|
||||
sa.Column("irt_se", sa.Float(), nullable=True),
|
||||
sa.Column("calibrated", sa.Boolean(), nullable=False),
|
||||
sa.Column("calibration_sample_size", sa.Integer(), nullable=False),
|
||||
sa.Column("generated_by", sa.String(length=50), nullable=False),
|
||||
sa.Column("ai_model", sa.String(length=255), nullable=True),
|
||||
sa.Column("basis_item_id", sa.Integer(), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.ForeignKeyConstraint(["website_id"], ["websites.id"], ondelete="CASCADE", onupdate="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["basis_item_id"], ["items.id"], ondelete="SET NULL", onupdate="CASCADE"),
|
||||
sa.ForeignKeyConstraint(
|
||||
["website_id", "tryout_id"],
|
||||
["tryouts.website_id", "tryouts.tryout_id"],
|
||||
name="fk_items_tryout",
|
||||
ondelete="CASCADE",
|
||||
onupdate="CASCADE",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.CheckConstraint("irt_b IS NULL OR (irt_b >= -3 AND irt_b <= 3)", name="ck_irt_b_range"),
|
||||
sa.CheckConstraint("ctt_p IS NULL OR (ctt_p >= 0 AND ctt_p <= 1)", name="ck_ctt_p_range"),
|
||||
sa.CheckConstraint("ctt_bobot IS NULL OR (ctt_bobot >= 0 AND ctt_bobot <= 1)", name="ck_ctt_bobot_range"),
|
||||
sa.CheckConstraint("slot > 0", name="ck_slot_positive"),
|
||||
)
|
||||
op.create_index("ix_items_tryout_id", "items", ["tryout_id"], unique=False)
|
||||
op.create_index("ix_items_website_id", "items", ["website_id"], unique=False)
|
||||
op.create_index("ix_items_basis_item_id", "items", ["basis_item_id"], unique=False)
|
||||
op.create_index("ix_items_calibrated", "items", ["calibrated"], unique=False)
|
||||
op.create_index(
|
||||
"ix_items_tryout_id_website_id_slot",
|
||||
"items",
|
||||
["tryout_id", "website_id", "slot", "level"],
|
||||
unique=True,
|
||||
)
|
||||
|
||||
if not _table_exists("sessions"):
|
||||
op.create_table(
|
||||
"sessions",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("session_id", sa.String(length=255), nullable=False),
|
||||
sa.Column("wp_user_id", sa.String(length=255), nullable=False),
|
||||
sa.Column("website_id", sa.Integer(), nullable=False),
|
||||
sa.Column("tryout_id", sa.String(length=255), nullable=False),
|
||||
sa.Column("start_time", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("end_time", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("is_completed", sa.Boolean(), nullable=False),
|
||||
sa.Column("scoring_mode_used", sa.String(length=50), nullable=False),
|
||||
sa.Column("total_benar", sa.Integer(), nullable=False),
|
||||
sa.Column("total_bobot_earned", sa.Float(), nullable=False),
|
||||
sa.Column("NM", sa.Integer(), quote=True, nullable=True),
|
||||
sa.Column("NN", sa.Integer(), quote=True, nullable=True),
|
||||
sa.Column("theta", sa.Float(), nullable=True),
|
||||
sa.Column("theta_se", sa.Float(), nullable=True),
|
||||
sa.Column("rataan_used", sa.Float(), nullable=True),
|
||||
sa.Column("sb_used", sa.Float(), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.ForeignKeyConstraint(["website_id"], ["websites.id"], ondelete="CASCADE", onupdate="CASCADE"),
|
||||
sa.ForeignKeyConstraint(
|
||||
["website_id", "tryout_id"],
|
||||
["tryouts.website_id", "tryouts.tryout_id"],
|
||||
name="fk_sessions_tryout",
|
||||
ondelete="CASCADE",
|
||||
onupdate="CASCADE",
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["wp_user_id", "website_id"],
|
||||
["users.wp_user_id", "users.website_id"],
|
||||
name="fk_sessions_user",
|
||||
ondelete="CASCADE",
|
||||
onupdate="CASCADE",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("session_id"),
|
||||
sa.CheckConstraint('"NM" IS NULL OR ("NM" >= 0 AND "NM" <= 1000)', name="ck_nm_range"),
|
||||
sa.CheckConstraint('"NN" IS NULL OR ("NN" >= 0 AND "NN" <= 1000)', name="ck_nn_range"),
|
||||
sa.CheckConstraint("theta IS NULL OR (theta >= -3 AND theta <= 3)", name="ck_theta_range"),
|
||||
sa.CheckConstraint("total_benar >= 0", name="ck_total_benar_non_negative"),
|
||||
sa.CheckConstraint("total_bobot_earned >= 0", name="ck_total_bobot_non_negative"),
|
||||
)
|
||||
op.create_index("ix_sessions_session_id", "sessions", ["session_id"], unique=True)
|
||||
op.create_index("ix_sessions_wp_user_id", "sessions", ["wp_user_id"], unique=False)
|
||||
op.create_index("ix_sessions_website_id", "sessions", ["website_id"], unique=False)
|
||||
op.create_index("ix_sessions_tryout_id", "sessions", ["tryout_id"], unique=False)
|
||||
op.create_index("ix_sessions_is_completed", "sessions", ["is_completed"], unique=False)
|
||||
|
||||
if not _table_exists("tryout_stats"):
|
||||
op.create_table(
|
||||
"tryout_stats",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("website_id", sa.Integer(), nullable=False),
|
||||
sa.Column("tryout_id", sa.String(length=255), nullable=False),
|
||||
sa.Column("participant_count", sa.Integer(), nullable=False),
|
||||
sa.Column("total_nm_sum", sa.Float(), nullable=False),
|
||||
sa.Column("total_nm_sq_sum", sa.Float(), nullable=False),
|
||||
sa.Column("rataan", sa.Float(), nullable=True),
|
||||
sa.Column("sb", sa.Float(), nullable=True),
|
||||
sa.Column("min_nm", sa.Integer(), nullable=True),
|
||||
sa.Column("max_nm", sa.Integer(), nullable=True),
|
||||
sa.Column("last_calculated", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.ForeignKeyConstraint(["website_id"], ["websites.id"], ondelete="CASCADE", onupdate="CASCADE"),
|
||||
sa.ForeignKeyConstraint(
|
||||
["website_id", "tryout_id"],
|
||||
["tryouts.website_id", "tryouts.tryout_id"],
|
||||
name="fk_tryout_stats_tryout",
|
||||
ondelete="CASCADE",
|
||||
onupdate="CASCADE",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.CheckConstraint("participant_count >= 0", name="ck_participant_count_non_negative"),
|
||||
sa.CheckConstraint("min_nm IS NULL OR (min_nm >= 0 AND min_nm <= 1000)", name="ck_min_nm_range"),
|
||||
sa.CheckConstraint("max_nm IS NULL OR (max_nm >= 0 AND max_nm <= 1000)", name="ck_max_nm_range"),
|
||||
sa.CheckConstraint(
|
||||
"min_nm IS NULL OR max_nm IS NULL OR min_nm <= max_nm",
|
||||
name="ck_min_max_nm_order",
|
||||
),
|
||||
)
|
||||
op.create_index("ix_tryout_stats_website_id", "tryout_stats", ["website_id"], unique=False)
|
||||
op.create_index("ix_tryout_stats_tryout_id", "tryout_stats", ["tryout_id"], unique=False)
|
||||
op.create_index(
|
||||
"ix_tryout_stats_website_id_tryout_id",
|
||||
"tryout_stats",
|
||||
["website_id", "tryout_id"],
|
||||
unique=True,
|
||||
)
|
||||
|
||||
if not _table_exists("user_answers"):
|
||||
op.create_table(
|
||||
"user_answers",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("session_id", sa.String(length=255), nullable=False),
|
||||
sa.Column("wp_user_id", sa.String(length=255), nullable=False),
|
||||
sa.Column("website_id", sa.Integer(), nullable=False),
|
||||
sa.Column("tryout_id", sa.String(length=255), nullable=False),
|
||||
sa.Column("item_id", sa.Integer(), nullable=False),
|
||||
sa.Column("response", sa.String(length=10), nullable=False),
|
||||
sa.Column("is_correct", sa.Boolean(), nullable=False),
|
||||
sa.Column("time_spent", sa.Integer(), nullable=False),
|
||||
sa.Column("scoring_mode_used", sa.String(length=50), nullable=False),
|
||||
sa.Column("bobot_earned", sa.Float(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.ForeignKeyConstraint(["session_id"], ["sessions.session_id"], ondelete="CASCADE", onupdate="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["website_id"], ["websites.id"], ondelete="CASCADE", onupdate="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["item_id"], ["items.id"], ondelete="CASCADE", onupdate="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.CheckConstraint("time_spent >= 0", name="ck_time_spent_non_negative"),
|
||||
sa.CheckConstraint("bobot_earned >= 0", name="ck_bobot_earned_non_negative"),
|
||||
)
|
||||
op.create_index("ix_user_answers_session_id", "user_answers", ["session_id"], unique=False)
|
||||
op.create_index("ix_user_answers_wp_user_id", "user_answers", ["wp_user_id"], unique=False)
|
||||
op.create_index("ix_user_answers_website_id", "user_answers", ["website_id"], unique=False)
|
||||
op.create_index("ix_user_answers_tryout_id", "user_answers", ["tryout_id"], unique=False)
|
||||
op.create_index("ix_user_answers_item_id", "user_answers", ["item_id"], unique=False)
|
||||
op.create_index(
|
||||
"ix_user_answers_session_id_item_id",
|
||||
"user_answers",
|
||||
["session_id", "item_id"],
|
||||
unique=True,
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
if _table_exists("user_answers"):
|
||||
op.drop_table("user_answers")
|
||||
if _table_exists("tryout_stats"):
|
||||
op.drop_table("tryout_stats")
|
||||
if _table_exists("sessions"):
|
||||
op.drop_table("sessions")
|
||||
if _table_exists("items"):
|
||||
op.drop_table("items")
|
||||
if _table_exists("users"):
|
||||
op.drop_table("users")
|
||||
if _table_exists("tryouts"):
|
||||
op.drop_table("tryouts")
|
||||
if _table_exists("websites"):
|
||||
op.drop_table("websites")
|
||||
@@ -0,0 +1,118 @@
|
||||
"""add tryout JSON snapshot tables
|
||||
|
||||
Revision ID: 20260402_000002
|
||||
Revises: 20260331_000001
|
||||
Create Date: 2026-04-02 11:30:00
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
revision: str = "20260402_000002"
|
||||
down_revision: Union[str, None] = "20260331_000001"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"tryout_import_snapshots",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("website_id", sa.Integer(), nullable=False),
|
||||
sa.Column("source_tryout_id", sa.String(length=255), nullable=False),
|
||||
sa.Column("source_key", sa.String(length=255), nullable=False),
|
||||
sa.Column("title", sa.String(length=255), nullable=False),
|
||||
sa.Column("source_permalink", sa.String(length=1024), nullable=True),
|
||||
sa.Column("source_status", sa.String(length=50), nullable=True),
|
||||
sa.Column("exported_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("source_created_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("source_modified_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("exported_by", sa.String(length=255), nullable=True),
|
||||
sa.Column("question_count", sa.Integer(), nullable=False),
|
||||
sa.Column("result_count", sa.Integer(), nullable=False),
|
||||
sa.Column("payload_checksum", sa.String(length=64), nullable=False),
|
||||
sa.Column("raw_payload", sa.JSON(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.ForeignKeyConstraint(["website_id"], ["websites.id"], ondelete="CASCADE", onupdate="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_tryout_import_snapshots_website_id",
|
||||
"tryout_import_snapshots",
|
||||
["website_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_tryout_import_snapshots_source_tryout_id",
|
||||
"tryout_import_snapshots",
|
||||
["source_tryout_id"],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
"tryout_snapshot_questions",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("website_id", sa.Integer(), nullable=False),
|
||||
sa.Column("source_tryout_id", sa.String(length=255), nullable=False),
|
||||
sa.Column("source_question_id", sa.String(length=255), nullable=False),
|
||||
sa.Column("latest_snapshot_id", sa.Integer(), nullable=True),
|
||||
sa.Column("question_title", sa.Text(), nullable=False),
|
||||
sa.Column("question_html", sa.Text(), nullable=False),
|
||||
sa.Column("explanation_html", sa.Text(), nullable=True),
|
||||
sa.Column("raw_options", sa.JSON(), nullable=False),
|
||||
sa.Column("correct_answer", sa.String(length=10), nullable=False),
|
||||
sa.Column("category_id", sa.Integer(), nullable=True),
|
||||
sa.Column("category_name", sa.String(length=255), nullable=True),
|
||||
sa.Column("category_code", sa.String(length=255), nullable=True),
|
||||
sa.Column("option_count", sa.Integer(), nullable=False),
|
||||
sa.Column("has_option_labels", sa.Boolean(), nullable=False),
|
||||
sa.Column("is_active", sa.Boolean(), nullable=False),
|
||||
sa.Column("content_checksum", sa.String(length=64), nullable=False),
|
||||
sa.Column("raw_payload", sa.JSON(), nullable=False),
|
||||
sa.Column("first_seen_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("last_seen_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.ForeignKeyConstraint(["website_id"], ["websites.id"], ondelete="CASCADE", onupdate="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["latest_snapshot_id"], ["tryout_import_snapshots.id"], ondelete="SET NULL", onupdate="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint(
|
||||
"website_id",
|
||||
"source_tryout_id",
|
||||
"source_question_id",
|
||||
name="uq_snapshot_questions_website_tryout_question",
|
||||
),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_tryout_snapshot_questions_website_id",
|
||||
"tryout_snapshot_questions",
|
||||
["website_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_tryout_snapshot_questions_source_tryout_id",
|
||||
"tryout_snapshot_questions",
|
||||
["source_tryout_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_tryout_snapshot_questions_latest_snapshot_id",
|
||||
"tryout_snapshot_questions",
|
||||
["latest_snapshot_id"],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_tryout_snapshot_questions_latest_snapshot_id", table_name="tryout_snapshot_questions")
|
||||
op.drop_index("ix_tryout_snapshot_questions_source_tryout_id", table_name="tryout_snapshot_questions")
|
||||
op.drop_index("ix_tryout_snapshot_questions_website_id", table_name="tryout_snapshot_questions")
|
||||
op.drop_table("tryout_snapshot_questions")
|
||||
|
||||
op.drop_index("ix_tryout_import_snapshots_source_tryout_id", table_name="tryout_import_snapshots")
|
||||
op.drop_index("ix_tryout_import_snapshots_website_id", table_name="tryout_import_snapshots")
|
||||
op.drop_table("tryout_import_snapshots")
|
||||
@@ -0,0 +1,118 @@
|
||||
"""add ai generation runs and item variant lifecycle fields
|
||||
|
||||
Revision ID: 20260404_000003
|
||||
Revises: 20260402_000002
|
||||
Create Date: 2026-04-04 10:10:00
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
revision: str = "20260404_000003"
|
||||
down_revision: Union[str, None] = "20260402_000002"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"ai_generation_runs",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("basis_item_id", sa.Integer(), nullable=False),
|
||||
sa.Column("source_snapshot_question_id", sa.Integer(), nullable=True),
|
||||
sa.Column("target_level", sa.String(length=50), nullable=False),
|
||||
sa.Column("requested_count", sa.Integer(), nullable=False, server_default="1"),
|
||||
sa.Column("model", sa.String(length=255), nullable=False),
|
||||
sa.Column("prompt_version", sa.String(length=50), nullable=False, server_default="v1"),
|
||||
sa.Column("operator_notes", sa.Text(), nullable=True),
|
||||
sa.Column("created_by", sa.String(length=255), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.ForeignKeyConstraint(["basis_item_id"], ["items.id"], ondelete="CASCADE", onupdate="CASCADE"),
|
||||
sa.ForeignKeyConstraint(
|
||||
["source_snapshot_question_id"],
|
||||
["tryout_snapshot_questions.id"],
|
||||
ondelete="SET NULL",
|
||||
onupdate="CASCADE",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index("ix_ai_generation_runs_basis_item_id", "ai_generation_runs", ["basis_item_id"], unique=False)
|
||||
op.create_index(
|
||||
"ix_ai_generation_runs_source_snapshot_question_id",
|
||||
"ai_generation_runs",
|
||||
["source_snapshot_question_id"],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
op.add_column("items", sa.Column("generation_run_id", sa.Integer(), nullable=True))
|
||||
op.add_column("items", sa.Column("source_snapshot_question_id", sa.Integer(), nullable=True))
|
||||
op.add_column("items", sa.Column("variant_status", sa.String(length=50), nullable=False, server_default="active"))
|
||||
op.add_column("items", sa.Column("reviewed_by", sa.String(length=255), nullable=True))
|
||||
op.add_column("items", sa.Column("reviewed_at", sa.DateTime(timezone=True), nullable=True))
|
||||
op.add_column("items", sa.Column("review_notes", sa.Text(), nullable=True))
|
||||
|
||||
op.create_foreign_key(
|
||||
"fk_items_generation_run_id",
|
||||
"items",
|
||||
"ai_generation_runs",
|
||||
["generation_run_id"],
|
||||
["id"],
|
||||
ondelete="SET NULL",
|
||||
onupdate="CASCADE",
|
||||
)
|
||||
op.create_foreign_key(
|
||||
"fk_items_source_snapshot_question_id",
|
||||
"items",
|
||||
"tryout_snapshot_questions",
|
||||
["source_snapshot_question_id"],
|
||||
["id"],
|
||||
ondelete="SET NULL",
|
||||
onupdate="CASCADE",
|
||||
)
|
||||
op.create_index("ix_items_generation_run_id", "items", ["generation_run_id"], unique=False)
|
||||
op.create_index(
|
||||
"ix_items_source_snapshot_question_id",
|
||||
"items",
|
||||
["source_snapshot_question_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index("ix_items_variant_status", "items", ["variant_status"], unique=False)
|
||||
|
||||
op.drop_index("ix_items_tryout_id_website_id_slot", table_name="items")
|
||||
op.create_index(
|
||||
"ix_items_tryout_id_website_id_slot",
|
||||
"items",
|
||||
["tryout_id", "website_id", "slot", "level"],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
op.alter_column("items", "variant_status", server_default=None)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_items_tryout_id_website_id_slot", table_name="items")
|
||||
op.create_index(
|
||||
"ix_items_tryout_id_website_id_slot",
|
||||
"items",
|
||||
["tryout_id", "website_id", "slot", "level"],
|
||||
unique=True,
|
||||
)
|
||||
|
||||
op.drop_index("ix_items_variant_status", table_name="items")
|
||||
op.drop_index("ix_items_source_snapshot_question_id", table_name="items")
|
||||
op.drop_index("ix_items_generation_run_id", table_name="items")
|
||||
op.drop_constraint("fk_items_source_snapshot_question_id", "items", type_="foreignkey")
|
||||
op.drop_constraint("fk_items_generation_run_id", "items", type_="foreignkey")
|
||||
op.drop_column("items", "review_notes")
|
||||
op.drop_column("items", "reviewed_at")
|
||||
op.drop_column("items", "reviewed_by")
|
||||
op.drop_column("items", "variant_status")
|
||||
op.drop_column("items", "source_snapshot_question_id")
|
||||
op.drop_column("items", "generation_run_id")
|
||||
|
||||
op.drop_index("ix_ai_generation_runs_source_snapshot_question_id", table_name="ai_generation_runs")
|
||||
op.drop_index("ix_ai_generation_runs_basis_item_id", table_name="ai_generation_runs")
|
||||
op.drop_table("ai_generation_runs")
|
||||
53
backend/alembic/versions/20260405_000004_report_schedules.py
Normal file
53
backend/alembic/versions/20260405_000004_report_schedules.py
Normal file
@@ -0,0 +1,53 @@
|
||||
"""add persistent report schedules
|
||||
|
||||
Revision ID: 20260405_000004
|
||||
Revises: 20260404_000003
|
||||
Create Date: 2026-04-05 09:00:00
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
revision: str = "20260405_000004"
|
||||
down_revision: Union[str, None] = "20260404_000003"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"report_schedules",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("schedule_id", sa.String(length=36), nullable=False),
|
||||
sa.Column("report_type", sa.String(length=50), nullable=False),
|
||||
sa.Column("schedule", sa.String(length=20), nullable=False),
|
||||
sa.Column("tryout_ids", sa.JSON(), nullable=False),
|
||||
sa.Column("website_id", sa.Integer(), nullable=False),
|
||||
sa.Column("recipients", sa.JSON(), nullable=False),
|
||||
sa.Column("format", sa.String(length=10), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("last_run", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("next_run", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("is_active", sa.Boolean(), nullable=False),
|
||||
sa.ForeignKeyConstraint(["website_id"], ["websites.id"], ondelete="CASCADE", onupdate="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("schedule_id"),
|
||||
)
|
||||
op.create_index("ix_report_schedules_schedule_id", "report_schedules", ["schedule_id"], unique=True)
|
||||
op.create_index("ix_report_schedules_website_id", "report_schedules", ["website_id"], unique=False)
|
||||
op.create_index(
|
||||
"ix_report_schedules_website_active",
|
||||
"report_schedules",
|
||||
["website_id", "is_active"],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_report_schedules_website_active", table_name="report_schedules")
|
||||
op.drop_index("ix_report_schedules_website_id", table_name="report_schedules")
|
||||
op.drop_index("ix_report_schedules_schedule_id", table_name="report_schedules")
|
||||
op.drop_table("report_schedules")
|
||||
@@ -0,0 +1,26 @@
|
||||
"""add session expires at
|
||||
|
||||
Revision ID: 20260617_000005
|
||||
Revises: 20260405_000004
|
||||
Create Date: 2026-06-17 15:00:00
|
||||
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
revision: str = "20260617_000005"
|
||||
down_revision: Union[str, None] = "20260405_000004"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column("sessions", sa.Column("expires_at", sa.DateTime(timezone=True), nullable=True))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("sessions", "expires_at")
|
||||
Reference in New Issue
Block a user