docs: surface SP14 (Payer-Rejected UI + acknowledge) + SP15 (key rotation) #3
Reference in New Issue
Block a user
Delete Branch "docs/sp14-15-readme-sync"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Surface SP14 (5-lane Inbox UI + Payer-Rejected acknowledge action) and SP15 (SQLCipher key rotation) in the README. Both are merged into local
mainbut were not yet documented in the README; the previous doc pass covered up to SP13.The previous doc PR (#1) was effectively merged via
d4f6fddand is closed.Important: branch divergence
The
docs/sp14-15-readme-syncbranch is based on the localmaintip (1267a34), which is ahead oforigin/main(8a65baa) by 7 commits — the SP15 feature, the SP15 merge, the api_helpers refactor, the api_routers split, the SP14 doc commit, and the trailing-newlines refactor.The PR diff against
origin/maintherefore shows all 12 files in those 7 commits + this README commit, not just the README. The README change itself is one file / 85 + / 4 - (commitd25f00a).Two ways to land this cleanly:
maintoorigin/mainfirst (it's the SP15 + refactor work that's already in local main). The PR will then show just the README change.Changes (README-only)
Inbox
POST /api/inbox/payer-rejected/acknowledge(idempotent, audit-logged, returnstransitioned/already_acked/not_found/not_rejectedcounts).Encryption at Rest
### Key rotationsub-section explaining thePOST /api/admin/db/rotate-keyhandler, theNullPoolchoice (SQLCipher thread affinity), the module-levelthreading.Lock, the error code mapping (409/400/503).Tamper-Evident Audit Log
Roadmap
### SP14 endpointsand### SP15 endpointsreference sections at the end.Project layout
api_routers/(health.py, acks.py, ta1_acks.py) — FastAPI APIRouter subpackage extracted fromapi.py.api.pyline to note it now mountsapi_routers/*.Verification
pytest --collect-onlyinbackend/collects 759 tests (up from 733 at the last doc pass).git diff --checkclean.Step 2 (first half) of the architecture satisfaction loop. api.py shrank from 2595 to 2452 lines (-143) by extracting three read-only resource groups into cyclone.api_routers: - health.py: GET /api/health (1 endpoint) - acks.py: GET /api/acks, GET /api/acks/{ack_id} (2 endpoints) - ta1_acks.py: GET /api/ta1-acks, GET /api/ta1-acks/{ack_id} (2 endpoints) Each router owns its endpoint bodies + the small UI-shape helper that goes with them (_ack_to_ui, _ta1_to_ui, _serialize_ta1_from_row). The helpers stay in the router file rather than being shared because each is only used by its own endpoints. api.py now ends the app-wiring section with three include_router() calls. The new package is named cyclone.api_routers (not cyclone.api.routers) to avoid the Python package-vs-same-named-module ambiguity that would shadow the existing cyclone.api module. Verifies: 41 targeted tests (test_acks, test_health, test_api_gets) pass, full pytest is 8 failed / 735 passed / 16 skipped — identical to clean main baseline. Live curl against the running server: GET /api/health -> 200, GET /api/acks -> 200, GET /api/ta1-acks -> 200. See /tmp/refactor-cyclone.md for the full plan.The README's SP-specific endpoint reference blocks (SP3-SP15 in the Roadmap) cover the per-SP additions, but the pre-existing core operator surface was never documented as a single block. This pass adds the missing endpoints: - GET /api/batches, GET /api/batches/{batch_id} — batch list + detail. - GET /api/batch-diff?a=<id>&b=<id> — side-by-side diff between two batches (added/removed/changed claims + envelope metadata). - GET /api/reconciliation/unmatched — every claim with no paired remit and every remit with no paired claim; powers the reconciliation review UI. - POST /api/reconciliation/match — manual pair (claim_id, remit_id); 400/404/409 contract. - POST /api/reconciliation/unmatch — remove a match and reset the claim to 'submitted'. - GET /api/providers — distinct providers from the parsed claim stream. Distinct from /api/config/providers/{npi} (the SP9 config table endpoint). - GET /api/activity — recent activity events. Powers the Activity page; the streaming counterpart /api/activity/stream is already documented under 'Live updates'. These 7 routes are referenced by the UI (BatchesList, BatchDetail, BatchDiffView, Reconciliation page, Providers page, Activity page) but were missing from the README's route inventory. The new section sits between 'SFTP Wire-Up' and 'Persistence', with a one-paragraph pointer to the per-SP endpoint reference blocks for SP-specific routes. No code changes. No tests touched.Checkpoint 2b. Extracts 7 more endpoints (1 + 2 + 4) into three new routers: - providers.py: GET /api/providers (list distinct providers from claim rows; npi/state filter; NDJSON or paginated JSON). - clearhouse.py: GET /api/clearhouse + POST /api/clearhouse/submit. Carries the two serialize-from-raw helpers (_serialize_claim_for_submit, _serialize_claim_from_raw) since they're only used here. - config.py: GET /api/config/providers + GET /api/config/providers/{npi} + GET /api/config/payers + GET /api/config/payers/{payer_id}/configs. Payer configs endpoint merges YAML-loaded blocks with any DB-overridden live blocks per payer. api.py shrank from 2474 to 2310 lines (-164) and no longer has any single resource's full request/response shape — every remaining route now lives in a dedicated router module. Verifies: - Full pytest: 8 failed / 735 passed / 16 skipped — identical to clean main baseline (the 8 are pre-existing env/secret/sqlcipher). - Live smoke: /api/health, /api/providers, /api/clearhouse, /api/config/payers, /api/config/payers/co_medicaid/configs all 200. /api/config/providers/{npi-not-seeded} returns 404 as expected. See /tmp/refactor-cyclone.md for the full plan and progress.Extracts GET /api/batches and GET /api/batches/{batch_id} from api.py into cyclone.api_routers.batches. The _batch_summary_claim_count helper moves along (only used by list_batches). api.py: 2257 -> 2201 (-56). Net diff: +93 / -64. Pytest: 8 failed / 735 passed / 16 skipped — identical to baseline. Live smoke: /api/batches 200 JSON; /api/batches/{id} 404 on missing; NDJSON form returns the expected summary envelope.Four additional routers landed since the last doc pass: - activity.py (GET /api/activity, /api/activity/stream) - batches.py (GET /api/batches, /api/batches/{id}, /api/batch-diff) - providers.py (GET /api/providers) - clearhouse.py (GET /api/clearhouse, POST /api/clearhouse/submit) - config.py (/api/config/providers[/...], /api/config/payers[/...], POST /api/admin/reload-config) Update the Project layout block to list all 8 routers with the routes they own. No new functionality is introduced — the refactor is purely a code-organization move from api.py into the api_routers/ subpackage. No code changes; no tests touched.Extracts the 5 /api/claims endpoints from api.py into cyclone.api_routers.claims: GET /api/claims (paginated list) GET /api/claims/stream (NDJSON live-tail) GET /api/claims/{claim_id} (drawer detail) GET /api/claims/{claim_id}/serialize-837 (X12 regen) GET /api/claims/{claim_id}/line-reconciliation (per-line view) Plus the two private projection helpers _claim_line_dict and _svc_to_dict which are only used by line-reconciliation. claims_stream is re-exported at the cyclone.api module level so test_api_stream_live.py's direct import keeps working (same pattern as the activity_stream re-export). Route ordering preserved: /stream is declared before /{claim_id} in the router source so FastAPI's first-match routing doesn't swallow the literal 'stream' segment as a claim id. api.py: 2201 -> 1841 (-360). Net diff: +432 / -389. Pytest: 8 failed / 735 passed / 16 skipped — identical to baseline. 57/57 claims-targeted tests pass. Live smoke: all 5 routes return expected status codes (200 for list/stream, 404 for missing-claim detail/serialize/line-recon).Extracts the 3 /api/remittances endpoints from api.py into cyclone.api_routers.remittances: GET /api/remittances (paginated list with filters) GET /api/remittances/stream (NDJSON live-tail) GET /api/remittances/{remit_id} (detail with CAS adjustments) Mirror of the claims router for the 835 / Remittance resource. No private helpers — every endpoint is a thin wrapper over store.iter_remittances() / store.get_remittance(). remittances_stream is re-exported at the cyclone.api module level so test_api_stream_live.py's direct import keeps working. Route ordering preserved: /stream is declared before /{remit_id} in the router source so FastAPI's first-match routing doesn't swallow the literal 'stream' segment as a remittance id. api.py: 1841 -> 1753 (-88). Net diff: +170 / -96. Pytest: 8 failed / 735 passed / 16 skipped — identical to baseline. 11/11 remittance-targeted tests pass. Live smoke: all 3 routes return expected status codes (200 for list/stream, 404 for missing-remit detail).Extracts 10 endpoints from api.py into two new routers: cyclone.api_routers.inbox (6 ep): GET /api/inbox/lanes POST /api/inbox/candidates/{remit_id}/match POST /api/inbox/candidates/dismiss POST /api/inbox/payer-rejected/acknowledge POST /api/inbox/rejected/resubmit GET /api/inbox/export.csv cyclone.api_routers.reconciliation (4 ep): GET /api/reconciliation/unmatched GET /api/batch-diff POST /api/reconciliation/match POST /api/reconciliation/unmatch The two reconciliation/match endpoints delegate to store.manual_match / store.manual_unmatch; the router translates the store's exception hierarchy (AlreadyMatchedError, InvalidStateError, NotMatchedError, LookupError) into the HTTP error contract. State-access refactor: endpoints that previously read app.state.dismissed_pairs directly (lanes / dismiss / export.csv) now take an explicit request: Request parameter and read request.app.state.dismissed_pairs — the FastAPI-idiomatic pattern. No behavior change. Dead-import cleanup: 8 imports left dangling in api.py after extraction (csv, io, ClaimOutput, ParseResult, serialize_837, serialize_837_for_resubmit, SerializeError837, Response) — all removed. api.py: 1753 -> 1342 (-411). Net diff: +443 / -439. Pytest: 8 failed / 735 passed / 16 skipped — identical to baseline. 62/62 inbox + reconciliation tests pass. Live smoke: all 10 endpoints return expected status codes (200 for reads, 400 for invalid bodies, 404 for missing ids).Extracts 12 endpoints from api.py into two new routers: cyclone.api_routers.eligibility (2 ep): POST /api/eligibility/request POST /api/eligibility/parse-271 cyclone.api_routers.admin (10 ep): GET /api/admin/audit-log GET /api/admin/audit-log/verify POST /api/admin/db/rotate-key POST /api/admin/reload-config POST /api/admin/scheduler/start POST /api/admin/scheduler/stop GET /api/admin/scheduler/status POST /api/admin/scheduler/tick GET /api/admin/scheduler/processed-files Module-level _db_rotate_lock moved with the rotate-key endpoint. 8+ dead imports removed from api.py. No behaviour changes; routers wire identically to the inline versions. api.py: 1349 -> 949 lines (-400, ~64% reduction from start of refactor). eligibility.py: 220 lines, admin.py: 332 lines.Documentation pass (Jun 21 09:18 UTC): added SP16 Inbound MFT Scheduler section between SFTP Wire-Up and Batches, listing all 5 control-surface endpoints (
start/stop/status/tick/processed-files). Forward-compat note flags thatcyclone.schedulerandcyclone.db.ProcessedInboundFileare still in flight.Also expanded Project layout tree: api.py comment corrected to "app + middleware" (no inline route bodies), all 14 routers now listed including the 4
parse_*.pyfiles.Head bumped to
81aebf5. 47/47 endpoints documented across README + backend README. 735 tests pass; 8 pre-existing failures (sqlcipher/keychain/paramiko missing in dev env, plus a.DS_Storepollutingtest_prodfile_cross_pipeline_reconcileson macOS) are unrelated to doc changes.Pull request closed