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
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")
|