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:
@@ -180,3 +180,43 @@ class LotDisposal(Base):
|
||||
holding_days: Mapped[int] = mapped_column(Integer)
|
||||
ldv_eligible: Mapped[bool]
|
||||
"""Held 3+ years on an exchange-traded instrument (art. 219.1 NK)."""
|
||||
|
||||
|
||||
class FlowLinkKind(enum.StrEnum):
|
||||
auto = "auto"
|
||||
"""Produced by `ledger/matching.py`; rebuilt from scratch on every refresh."""
|
||||
manual = "manual"
|
||||
"""Confirmed by the user through the API; never touched by the matcher."""
|
||||
|
||||
|
||||
class FlowLink(TimestampMixin, Base):
|
||||
"""One ZenMoney transfer tied to the broker deposit/withdrawal it actually was (plan §1.6 C).
|
||||
|
||||
Without this pairing the same money is counted twice — once as the balance of the ZenMoney
|
||||
account that mirrors the broker, once as the broker's own cash — and a top-up looks like an
|
||||
expense in the cash flow. The unique constraints on both sides are what make the link a
|
||||
1:1 statement: one transfer, one broker event, never a fan-out.
|
||||
|
||||
The scoring fields are kept because a link is a *guess*: `amount_delta` and `day_gap` are
|
||||
what the reviewer needs in `GET /links/unmatched` to tell a good pairing from a lucky one.
|
||||
"""
|
||||
|
||||
__tablename__ = "flow_link"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
cash_txn_id: Mapped[int] = mapped_column(
|
||||
ForeignKey("cash_txn.id", ondelete="CASCADE"), unique=True
|
||||
)
|
||||
event_id: Mapped[int] = mapped_column(ForeignKey("event.id", ondelete="CASCADE"), unique=True)
|
||||
kind: Mapped[FlowLinkKind] = mapped_column(
|
||||
db_enum(FlowLinkKind, "flow_link_kind"), default=FlowLinkKind.auto
|
||||
)
|
||||
confidence: Mapped[Decimal]
|
||||
"""0..1; 1 means same day and the same kopeck."""
|
||||
amount_delta: Mapped[Decimal]
|
||||
"""|ZenMoney amount - broker amount|, in `currency` — both sides share it by construction."""
|
||||
currency: Mapped[str] = mapped_column(String(3))
|
||||
day_gap: Mapped[int] = mapped_column(Integer)
|
||||
"""Business days between the two dates: money does not reach a broker over a weekend."""
|
||||
note: Mapped[str | None] = mapped_column(Text)
|
||||
"""Why the pair was accepted (which route identified the broker account), for debugging."""
|
||||
|
||||
Reference in New Issue
Block a user