payslip-ingest/alembic/versions/0008_rsu_vest_events.py

62 lines
2.3 KiB
Python
Raw Normal View History

"""Add rsu_vest_events for Schwab vest ground-truth reconciliation.
Schwab emails a "Release Confirmation" on each RSU vest, listing the vest
date, shares released at FMV, shares sold to cover tax, and the USD
withholding amount. broker-sync will parse these emails and populate
this table; Panel 15 of the dashboard reconciles
(payslip.rsu_vest, payslip.rsu_income_tax)
(rsu_vest_events.gross_value_gbp, rsu_vest_events.tax_withheld_gbp)
to validate the parser's RSU-split correctness.
Idempotent on `external_id` re-running the IMAP sync doesn't create
duplicates. USD-denominated raw values are retained alongside the
GBP-converted values for audit.
"""
import sqlalchemy as sa
from alembic import op
revision = "0008"
down_revision = "0007"
branch_labels = None
depends_on = None
SCHEMA = "payslip_ingest"
def upgrade() -> None:
op.create_table(
"rsu_vest_events",
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
sa.Column("external_id", sa.String(), nullable=False, unique=True),
sa.Column("vest_date", sa.Date(), nullable=False),
sa.Column("ticker", sa.String(), nullable=False),
sa.Column("shares_vested", sa.Numeric(14, 4), nullable=False),
sa.Column("shares_sold_to_cover", sa.Numeric(14, 4), nullable=True),
sa.Column("fmv_at_vest_usd", sa.Numeric(12, 4), nullable=False),
sa.Column("tax_withheld_usd", sa.Numeric(12, 2), nullable=True),
sa.Column("fx_rate_gbp", sa.Numeric(10, 6), nullable=True),
sa.Column("gross_value_gbp", sa.Numeric(12, 2), nullable=True),
sa.Column("tax_withheld_gbp", sa.Numeric(12, 2), nullable=True),
sa.Column("source", sa.String(length=32), nullable=False),
sa.Column("raw_extraction", sa.JSON(), nullable=True),
sa.Column("created_at",
sa.TIMESTAMP(timezone=True),
server_default=sa.text("now()"),
nullable=False),
schema=SCHEMA,
)
op.create_index(
"ix_rsu_vest_events_vest_date",
"rsu_vest_events",
["vest_date"],
schema=SCHEMA,
)
def downgrade() -> None:
op.drop_index("ix_rsu_vest_events_vest_date",
table_name="rsu_vest_events",
schema=SCHEMA)
op.drop_table("rsu_vest_events", schema=SCHEMA)