feat(db): flow_link, metric_cash_flow_broker и price_coverage
Три таблицы одной миграцией, а не тремя: автогенерация из трёх параллельных веток дала бы три ревизии с общим down_revision, то есть ручную разборку ветвления вместо экономии. price_coverage засевается прямо в миграции из price_daily: она отвечает на вопрос «с какой даты мы УЖЕ спрашивали ISS», и без засева первый же прогон moex перекачал бы историю всех 77 бумаг целиком. flow_link_kind снимается на откате явно. DROP TABLE оставляет тип в базе, и следующий upgrade упал бы на CREATE TYPE — то же, что уже сделано для остальных енумов домена.
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
"""связь потоков, брокерский cash flow и покрытие истории цен
|
||||
|
||||
Revision ID: 3e2977b9e577
|
||||
Revises: a99f438e0010
|
||||
Create Date: 2026-09-18 14:53:12.692145
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "3e2977b9e577"
|
||||
down_revision: str | None = "a99f438e0010"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"metric_cash_flow_broker",
|
||||
sa.Column("scope", sa.String(length=32), nullable=False),
|
||||
sa.Column("month", sa.Date(), nullable=False),
|
||||
sa.Column("deposits_rub", sa.Numeric(precision=24, scale=10), nullable=False),
|
||||
sa.Column("withdrawals_rub", sa.Numeric(precision=24, scale=10), nullable=False),
|
||||
sa.Column("net_rub", sa.Numeric(precision=24, scale=10), nullable=False),
|
||||
sa.Column("event_count", sa.Integer(), nullable=False),
|
||||
sa.Column(
|
||||
"computed_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.PrimaryKeyConstraint("scope", "month", name=op.f("pk_metric_cash_flow_broker")),
|
||||
)
|
||||
op.create_table(
|
||||
"price_coverage",
|
||||
sa.Column("instrument_id", sa.Integer(), nullable=False),
|
||||
sa.Column("source", sa.String(length=16), nullable=False),
|
||||
sa.Column("history_from", sa.Date(), nullable=False),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["instrument_id"],
|
||||
["instrument.id"],
|
||||
name=op.f("fk_price_coverage_instrument_id_instrument"),
|
||||
ondelete="CASCADE",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("instrument_id", "source", name=op.f("pk_price_coverage")),
|
||||
)
|
||||
# Seed from what is already stored: an instrument whose history already starts at the day it
|
||||
# was first held needs no backfill, and seeding spares the next run a full re-read of every
|
||||
# paper we have ever priced.
|
||||
op.execute(
|
||||
"""
|
||||
INSERT INTO price_coverage (instrument_id, source, history_from)
|
||||
SELECT instrument_id, source, MIN(d) FROM price_daily GROUP BY instrument_id, source
|
||||
"""
|
||||
)
|
||||
op.create_table(
|
||||
"flow_link",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("cash_txn_id", sa.Integer(), nullable=False),
|
||||
sa.Column("event_id", sa.Integer(), nullable=False),
|
||||
sa.Column("kind", sa.Enum("auto", "manual", name="flow_link_kind"), nullable=False),
|
||||
sa.Column("confidence", sa.Numeric(precision=24, scale=10), nullable=False),
|
||||
sa.Column("amount_delta", sa.Numeric(precision=24, scale=10), nullable=False),
|
||||
sa.Column("currency", sa.String(length=3), nullable=False),
|
||||
sa.Column("day_gap", sa.Integer(), nullable=False),
|
||||
sa.Column("note", sa.Text(), 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(
|
||||
["cash_txn_id"],
|
||||
["cash_txn.id"],
|
||||
name=op.f("fk_flow_link_cash_txn_id_cash_txn"),
|
||||
ondelete="CASCADE",
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["event_id"], ["event.id"], name=op.f("fk_flow_link_event_id_event"), ondelete="CASCADE"
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id", name=op.f("pk_flow_link")),
|
||||
sa.UniqueConstraint("cash_txn_id", name=op.f("uq_flow_link_cash_txn_id")),
|
||||
sa.UniqueConstraint("event_id", name=op.f("uq_flow_link_event_id")),
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table("flow_link")
|
||||
# the table's DROP leaves the type behind, and a later upgrade would fail on CREATE TYPE
|
||||
sa.Enum(name="flow_link_kind").drop(op.get_bind())
|
||||
op.drop_table("price_coverage")
|
||||
op.drop_table("metric_cash_flow_broker")
|
||||
# ### end Alembic commands ###
|
||||
Reference in New Issue
Block a user