"""Read the deployed payslip-ingest schema for income + RSU vest cadence. Read-only: we never write to `payslip_ingest.*`. The DB role `pg-fire-planner` only needs SELECT on payslip_ingest.payslip and payslip_ingest.rsu_vest_events. Outputs feed scenario calibration: - savings_per_year_gbp: median monthly net_pay × 12 less the £100k baseline spend (the planner allocates the surplus to portfolio). - annual_rsu_gross_gbp: median annual RSU vest value, used to validate the savings rate against expected gross compensation. """ from __future__ import annotations from dataclasses import dataclass from datetime import date from decimal import Decimal from sqlalchemy import text from sqlalchemy.ext.asyncio import AsyncSession @dataclass(frozen=True) class IncomeSummary: median_monthly_net_pay_gbp: Decimal median_annual_rsu_gbp: Decimal earliest_date: date | None latest_date: date | None payslip_count: int rsu_count: int async def read_income_summary(session: AsyncSession, months: int = 24) -> IncomeSummary: """Aggregate the most-recent `months` of payslips + RSU vests.""" payslip_rows = (await session.execute( text( """ SELECT pay_date, net_pay FROM payslip_ingest.payslip WHERE pay_date >= CURRENT_DATE - (:months || ' months')::interval ORDER BY pay_date DESC """, ), {"months": months}, )).all() rsu_rows = (await session.execute( text( """ SELECT vest_date, gross_value_gbp FROM payslip_ingest.rsu_vest_events WHERE vest_date >= CURRENT_DATE - (:months || ' months')::interval ORDER BY vest_date DESC """, ), {"months": months}, )).all() monthly_nets = sorted(Decimal(str(r[1] or 0)) for r in payslip_rows) median_monthly_net = (monthly_nets[len(monthly_nets) // 2] if monthly_nets else Decimal("0")) rsu_total_gbp = sum((Decimal(str(r[1] or 0)) for r in rsu_rows), start=Decimal("0")) months_span = max(1, months) annual_rsu = rsu_total_gbp * 12 / months_span pay_dates = [r[0] for r in payslip_rows] rsu_dates = [r[0] for r in rsu_rows] all_dates = pay_dates + rsu_dates earliest = min(all_dates) if all_dates else None latest = max(all_dates) if all_dates else None return IncomeSummary( median_monthly_net_pay_gbp=median_monthly_net, median_annual_rsu_gbp=annual_rsu, earliest_date=earliest, latest_date=latest, payslip_count=len(payslip_rows), rsu_count=len(rsu_rows), )