sync: ActualBudget Meta deposit overlay (Phase C)
Adds daily sync of Meta payroll deposits from ActualBudget into payslip_ingest.external_meta_deposits, enabling the dashboard to overlay bank deposits against payslip net_pay and surface parser drift on net. - Migration 0007: new table external_meta_deposits, unique on actualbudget_tx_id, indexed on deposit_date. - payslip_ingest.sync.actualbudget: narrow client for the jhonderson/actual-http-api sidecar (list accounts + transactions). Filters on payee regex (META|FACEBOOK, word-boundary). Idempotent upsert — ON CONFLICT DO NOTHING on actualbudget_tx_id. Surfaces clear error if the transactions endpoint is missing so the operator can switch to a SQLite-mount fallback. - CLI command: `python -m payslip_ingest sync-meta-deposits` driven by 4 env vars (ACTUALBUDGET_HTTP_API_URL, API_KEY, ENCRYPTION_PASSWORD, BUDGET_SYNC_ID). - Tests: 5 — regex positive/negative, full sync insert, idempotency, 404-endpoint failure mode. Part of: code-860
This commit is contained in:
parent
3b9c69bfd3
commit
08f28ad581
6 changed files with 492 additions and 0 deletions
52
alembic/versions/0007_external_meta_deposits.py
Normal file
52
alembic/versions/0007_external_meta_deposits.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
"""Add external_meta_deposits for ActualBudget payroll reconciliation.
|
||||
|
||||
Daily sync pulls Meta payroll deposits from ActualBudget (the
|
||||
jhonderson/actual-http-api sidecar) so the dashboard can overlay bank-
|
||||
deposit reality with `payslip.net_pay`. If the delta exceeds a tolerance
|
||||
threshold, the payslip parser likely got the net_pay wrong — useful for
|
||||
catching parser regressions without manual audit.
|
||||
|
||||
Idempotent on `actualbudget_tx_id` — rerunning the sync only inserts new
|
||||
transactions. Deletions in ActualBudget are NOT propagated here (append-
|
||||
only — the audit trail matters more than a live mirror).
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
|
||||
revision = "0007"
|
||||
down_revision = "0006"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
SCHEMA = "payslip_ingest"
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"external_meta_deposits",
|
||||
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
|
||||
sa.Column("actualbudget_tx_id", sa.String(), nullable=False, unique=True),
|
||||
sa.Column("deposit_date", sa.Date(), nullable=False),
|
||||
sa.Column("amount", sa.Numeric(12, 2), nullable=False),
|
||||
sa.Column("payee", sa.String(), nullable=True),
|
||||
sa.Column("memo", sa.String(), nullable=True),
|
||||
sa.Column("synced_at",
|
||||
sa.TIMESTAMP(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False),
|
||||
schema=SCHEMA,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_external_meta_deposits_deposit_date",
|
||||
"external_meta_deposits",
|
||||
["deposit_date"],
|
||||
schema=SCHEMA,
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_external_meta_deposits_deposit_date",
|
||||
table_name="external_meta_deposits",
|
||||
schema=SCHEMA)
|
||||
op.drop_table("external_meta_deposits", schema=SCHEMA)
|
||||
Loading…
Add table
Add a link
Reference in a new issue