You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

154 lines
6.0 KiB
Python

"""Phase 3 tables: llm_call_cache, review_items, event_labels.
Revision ID: 0002
Revises: 0001
Create Date: 2026-03-12
Tables created:
- llm_call_cache: DB-backed cache for LLM API calls
- review_items: human-in-the-loop review queue
- event_labels: forward-return labels for parsed events
"""
from __future__ import annotations
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects.postgresql import JSONB, UUID
revision: str = "0002"
down_revision: str | None = "0001"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
op.create_table(
"llm_call_cache",
sa.Column("cache_key", sa.Text, primary_key=True),
sa.Column("document_id", sa.Text, nullable=True),
sa.Column("model_name", sa.Text, nullable=False),
sa.Column("prompt_version", sa.Text, nullable=False),
sa.Column("schema_version", sa.Text, nullable=False),
sa.Column("raw_prompt", sa.Text, nullable=False),
sa.Column("raw_response", sa.Text, nullable=False),
sa.Column("normalized_json", JSONB, nullable=False),
sa.Column("token_usage_json", JSONB, nullable=False),
sa.Column("elapsed_ms", sa.Integer, nullable=False),
sa.Column(
"created_at_utc",
sa.DateTime(timezone=True),
nullable=False,
server_default=sa.text("now()"),
),
)
op.create_table(
"review_items",
sa.Column("review_id", UUID(as_uuid=True), primary_key=True),
sa.Column("entity_type", sa.Text, nullable=False),
sa.Column("entity_id", sa.Text, nullable=False),
sa.Column("priority", sa.Text, nullable=False),
sa.Column("reason_codes", JSONB, nullable=False),
sa.Column("status", sa.Text, nullable=False, server_default=sa.text("'open'")),
sa.Column("assigned_to", sa.Text, nullable=True),
sa.Column("snapshot_refs", JSONB, nullable=False, server_default=sa.text("'{}'")),
sa.Column("suggested_overrides", JSONB, nullable=True),
sa.Column("reviewer_id", sa.Text, nullable=True),
sa.Column("resolution_type", sa.Text, nullable=True),
sa.Column("field_overrides", JSONB, nullable=True),
sa.Column("root_cause", sa.Text, nullable=True),
sa.Column("notes", sa.Text, nullable=True),
sa.Column("resolved_at", sa.DateTime(timezone=True), nullable=True),
sa.Column(
"created_at_utc",
sa.DateTime(timezone=True),
nullable=False,
server_default=sa.text("now()"),
),
sa.Column(
"updated_at_utc",
sa.DateTime(timezone=True),
nullable=False,
server_default=sa.text("now()"),
),
)
op.create_index(
"ix_review_items_entity_open", "review_items", ["entity_type", "entity_id", "status"]
)
op.create_index(
"ix_review_items_status_priority", "review_items", ["status", "priority"]
)
# Partial unique index: only one open review per entity
op.create_index(
"uix_review_items_entity_open",
"review_items",
["entity_type", "entity_id"],
unique=True,
postgresql_where=sa.text("status = 'open'"),
)
op.create_table(
"event_labels",
sa.Column("label_id", sa.BigInteger, primary_key=True, autoincrement=True),
sa.Column(
"event_id",
sa.Text,
sa.ForeignKey("events.event_id"),
nullable=False,
),
sa.Column("entry_convention", sa.Text, nullable=False),
sa.Column("reaction_date", sa.Date, nullable=True),
sa.Column("entry_date", sa.Date, nullable=True),
sa.Column("entry_price", sa.Numeric, nullable=True),
sa.Column("fwd_return_1d", sa.Numeric, nullable=True),
sa.Column("fwd_return_3d", sa.Numeric, nullable=True),
sa.Column("fwd_return_5d", sa.Numeric, nullable=True),
sa.Column("hit_pos_1r_within_3d", sa.Boolean, nullable=True),
sa.Column("hit_neg_1r_within_3d", sa.Boolean, nullable=True),
sa.Column("close_up_after_3d", sa.Boolean, nullable=True),
sa.Column("close_up_after_5d", sa.Boolean, nullable=True),
sa.Column("mfe_3d", sa.Numeric, nullable=True),
sa.Column("mae_3d", sa.Numeric, nullable=True),
sa.Column("mfe_5d", sa.Numeric, nullable=True),
sa.Column("mae_5d", sa.Numeric, nullable=True),
sa.Column("bars_to_mfe_3d", sa.Integer, nullable=True),
sa.Column("bars_to_mae_3d", sa.Integer, nullable=True),
sa.Column("days_to_peak_close_5d", sa.Integer, nullable=True),
sa.Column("label_status", sa.Text, nullable=False),
sa.Column(
"invalid_event_for_labeling",
sa.Boolean,
nullable=False,
server_default=sa.text("false"),
),
sa.Column("risk_model_name", sa.Text, nullable=True),
sa.Column("initial_stop_price", sa.Numeric, nullable=True),
sa.Column("initial_r_value", sa.Numeric, nullable=True),
sa.Column("label_version", sa.Text, nullable=False),
sa.Column(
"created_at_utc",
sa.DateTime(timezone=True),
nullable=False,
server_default=sa.text("now()"),
),
sa.UniqueConstraint(
"event_id",
"entry_convention",
"label_version",
name="uq_event_labels_event_convention_version",
),
)
op.create_index("ix_event_labels_event_id", "event_labels", ["event_id"])
def downgrade() -> None:
op.drop_index("ix_event_labels_event_id", table_name="event_labels")
op.drop_table("event_labels")
op.drop_index("uix_review_items_entity_open", table_name="review_items")
op.drop_index("ix_review_items_status_priority", table_name="review_items")
op.drop_index("ix_review_items_entity_open", table_name="review_items")
op.drop_table("review_items")
op.drop_table("llm_call_cache")