healthcare: daily batched paper-filing fulfillment

Standard (no-login) CMS filings are mailed in one Priority Mail envelope per
destination agency, batched each postal working-day morning to save postage.

- migration 089: paper_filing_batches table + esign_records.paper_batch_id /
  filing_destination_key (idempotent: a filing is batched at most once).
- batch_cover_sheet.py: per-agency cover sheet (sender/dest/date/manifest) +
  merged print-job PDF (cover + all enclosed signed filings).
- daily_paper_batch.py worker: gather signed+unbatched cms855/cms10114 filings,
  group by destination (MAC by state via mac_routing; Fargo for CMS-10114),
  build cover+merged PDF per agency, persist batch, mark filings batched.
  Self-gates on postal working days (skips weekends + federal/USPS holidays).
  Phase 1 = human prints+mails; phase 2 = wire print-mail API.
- worker-crons: pw-paper-batch systemd timer (Mon-Fri 13:30 UTC, self-gated).
- test_paper_batch.py: 15/15 pass (working-day gating, routing, cover+merge).
This commit is contained in:
justin 2026-06-07 00:30:01 -05:00
parent 258d23bdc6
commit 138fec17e9
5 changed files with 542 additions and 0 deletions

View file

@ -0,0 +1,49 @@
-- 089: Daily paper-filing batch tracking for the Standard (no-login) CMS filing path.
--
-- When a provider e-signs a CMS-855 (or CMS-10114), the signed PDF is mailed to
-- the destination agency (the provider's MAC for 855s; the NPI Enumerator in
-- Fargo for NPPES updates). To save postage and handling we batch all signed,
-- not-yet-mailed filings each postal working-day morning, group them by
-- destination agency, and mail one Priority Mail envelope per agency.
--
-- This migration records, per signed filing, which daily batch it went out in,
-- so the batch worker is idempotent (never re-mails) and we can audit/track.
-- A paper-filing batch = one Priority Mail envelope to one destination agency
-- on one mailing day.
CREATE TABLE IF NOT EXISTS paper_filing_batches (
id SERIAL PRIMARY KEY,
batch_date DATE NOT NULL, -- the postal working day mailed
destination_key TEXT NOT NULL, -- mac_routing key, e.g. noridian_je / npi_enumerator
destination_name TEXT NOT NULL DEFAULT '', -- human-readable MAC/agency name
destination_address TEXT NOT NULL DEFAULT '', -- full mailing address block
item_count INTEGER NOT NULL DEFAULT 0, -- number of filings enclosed
cover_sheet_key TEXT, -- MinIO key for the batch cover sheet
merged_pdf_key TEXT, -- MinIO key for the merged print job
tracking_number TEXT, -- USPS tracking, filled when mailed
status TEXT NOT NULL DEFAULT 'prepared'
CHECK (status IN ('prepared', 'mailed', 'cancelled')),
created_at TIMESTAMPTZ DEFAULT NOW(),
mailed_at TIMESTAMPTZ
);
CREATE INDEX IF NOT EXISTS idx_paper_batch_date ON paper_filing_batches(batch_date);
CREATE UNIQUE INDEX IF NOT EXISTS idx_paper_batch_day_dest
ON paper_filing_batches(batch_date, destination_key);
-- Link each signed esign filing to the batch it shipped in. NULL = not yet
-- batched (the worker picks these up). Set once -> idempotent (worker skips
-- anything already assigned a batch).
ALTER TABLE esign_records
ADD COLUMN IF NOT EXISTS paper_batch_id INTEGER REFERENCES paper_filing_batches(id);
-- The destination MAC/agency for this filing, derived from the provider's
-- practice state at sign time (snapshot so later routing-table changes don't
-- retroactively move historical filings).
ALTER TABLE esign_records
ADD COLUMN IF NOT EXISTS filing_destination_key TEXT;
-- Index the work queue: signed, paper-path filings not yet batched.
CREATE INDEX IF NOT EXISTS idx_esign_unbatched_signed
ON esign_records(status)
WHERE status = 'signed' AND paper_batch_id IS NULL;