Commit Graph

260 Commits

Author SHA1 Message Date
Tyler f3002e4cbe fix(api): regenerate raw_999_text in /api/acks/{id} detail response (SP3 P3 follow-up)
The P3 subagent shipped a Download button on /acks that fell back to
'no raw text' because the detail endpoint returned raw_json (the parsed
model dump) but never the regenerated X12.

Fix: detail endpoint now re-serializes raw_json via serialize_999 and
returns it as raw_999_text, so the operator can actually download the
999 file. List endpoint unchanged (keeps payload small).
2026-06-20 08:06:21 -06:00
Tyler fb2a98fc7a feat(parsers+api+ui+db): 999 ACK transaction set end-to-end (SP3 P3) 2026-06-20 08:03:37 -06:00
Tyler 7a20f732f2 fix(api): include adjustments array in /api/remittances list response (SP3 P2 follow-up)
The P2 subagent shipped a UI chevron+expansion in Remittances.tsx that
read adjustments from the list payload, but iter_remittances() didn't
include the CasAdjustment rows. Result: the expansion was always empty
when a remittance was opened from the list (the detail endpoint worked).

Fix: bulk-fetch all CasAdjustment rows for the remittance_ids in one
query, then attach them as the 'adjustments' array. N+1-free.
2026-06-20 07:49:20 -06:00
Tyler 2a853857b1 feat(parsers+api+ui): expose CARC-labeled CAS adjustments (SP3 P2) 2026-06-20 07:44:41 -06:00
Tyler 76eb33e029 chore(backend): track uv.lock for reproducible installs 2026-06-20 07:26:27 -06:00
Tyler 716bfb0a26 feat(parsers): R034 REF*G1 enforcement + R035 BHT06 transaction type code (SP3 P1)
- R034 _r034_ref_g1_required: when payer opts in (require_ref_g1_for_adjustments),
  emit error if frequency_code is 7/8 and no REF*G1 segment appears in raw_segments.
  CO Medicaid stays lenient in v1 (gate is False by default).
- R035 _r035_bht06_allowed: BHT06 transaction type code must be in cfg.allowed_bht06.
  Skipped silently when transaction_type_code is None.
- Add transaction_type_code (str|None) to Envelope and ClaimOutput.
- parse_837: read BHT06 (seg[6]) in _build_envelope; mirror onto each ClaimOutput
  via the existing model_copy(update={...}) call site.
- 9 new tests in test_validator.py: 5 R034 (incl. lenient no-op) + 4 R035.
  213 passed, 1 skipped (prodfile corpus present-conditional).
2026-06-20 07:25:29 -06:00
Tyler 763ca3d758 docs(sp3): put transaction_type_code on ClaimOutput (mirrors envelope convention) 2026-06-20 07:18:16 -06:00
Tyler 467087d683 docs(sp3): fix rule IDs (R034/R035) and BHT06 naming in plan+spec 2026-06-20 07:18:03 -06:00
Tyler b47818883a docs: add EDI features spec + implementation plan (sub-project 3, 30 tasks) 2026-06-20 07:14:14 -06:00
Tyler 8a289b8e80 smoke: end-to-end DB + reconciliation flow passes (sub-project 2) 2026-06-20 00:11:52 -06:00
Tyler b7f831e170 docs: tick off completed boxes in sub-project 1 plan (housekeeping) 2026-06-20 00:11:01 -06:00
Tyler 6ffa5e8c72 docs: document CYCLONE_DB_URL, backup recipe, sub-project 2 completion 2026-06-20 00:10:27 -06:00
Tyler 1de6e2e60b feat(frontend): /reconciliation page with two-column matching UI 2026-06-20 00:08:39 -06:00
Tyler 5a5f611c2f chore(frontend): exclude .worktrees from vitest discovery 2026-06-20 00:04:44 -06:00
Tyler 8412bdc090 feat(frontend): sidebar Reconciliation nav entry with badge count 2026-06-20 00:04:30 -06:00
Tyler 99c8635178 feat(frontend): render real adjustmentAmount on Remittances page 2026-06-20 00:03:56 -06:00
Tyler 35ffe31447 refactor(frontend): StatusBadge delegates to ClaimStateBadge; Claims uses API state 2026-06-20 00:03:23 -06:00
Tyler 5ddbbcfc2a feat(frontend): ClaimStateBadge primitive (7 states, same palette) 2026-06-20 00:02:36 -06:00
Tyler e8c251f9ca feat(frontend): useReconciliation hook with react-query invalidation cascade 2026-06-19 23:58:00 -06:00
Tyler 23ac7f6fda feat(frontend): add listUnmatched, matchRemit, unmatchClaim to api 2026-06-19 23:47:34 -06:00
Tyler d4a142aa83 feat(frontend): add ClaimState enum + Match/Unmatched types 2026-06-19 23:45:11 -06:00
Tyler 43a88a356c feat(backend): parse-835 JSON response includes reconciliation summary 2026-06-19 23:42:22 -06:00
Tyler 44fe88d694 feat(backend): POST /api/reconciliation/{match,unmatch} + InvalidStateError 2026-06-19 23:39:20 -06:00
Tyler a4c59587c6 feat(backend): GET /api/reconciliation/unmatched 2026-06-19 23:34:25 -06:00
Tyler 0d172d0682 feat(backend): call db.init_db() before uvicorn starts 2026-06-19 23:31:34 -06:00
Tyler 3318c9418a fix(backend): set Remittance.claim_id symmetrically in reconcile.run
T12's list_unmatched filters Remittance by claim_id IS NULL. T10's
reconcile.run was only setting Claim.matched_remittance_id, leaving
auto-matched remits visible in the orphan bucket. Mirror the FK on
both sides so both list_unmatched filters produce consistent results.

T12 implementer flagged this as out-of-scope; addressed here as a
small follow-up so the Reconciliation page works correctly end-to-end.
2026-06-19 23:28:17 -06:00
Tyler aa7cd7654d feat(backend): implement list_unmatched + manual_match + manual_unmatch 2026-06-19 23:26:16 -06:00
Tyler cf2e52efb8 feat(backend): persist CasAdjustment rows from 835 SVC.adjustments 2026-06-19 23:07:48 -06:00
Tyler 6a65c2b750 fix(backend): spec-bug fixes for T10 — Match.claim_id non-unique + reconcile test dates
Three spec-bug fixes from T10 implementation:

- Match.claim_id: drop UNIQUE constraint (T4 schema error) — reversals add a 2nd row per claim (audit trail). Add explicit non-unique index ix_matches_claim_id to preserve query performance. Mirrored in ORM and migration 0001_initial.sql.
- test_run_orphan_remit_leaves_claim_unmatched: claim PCN-A does not match remit PCN-NEW, so unmatched_claims must be 1, not 0.
- test_run_reversal_flips_paid_to_reversed: claim service_date_from was 9 days before reversal remit service_date (outside default 7-day window) so no match occurred; changed to 5 days apart so match() picks the claim.
- test_match_unique_per_claim: in test_db_models.py asserted the now-removed UNIQUE behavior; renamed and inverted to assert the audit-trail design (two Match rows per claim allowed).
2026-06-19 22:59:20 -06:00
Tyler 8c92ef43bd feat(backend): add reconcile.run orchestrator with match + apply + CAS agg 2026-06-19 22:54:25 -06:00
Tyler c3306b882a chore(backend): move import json to module top in store.py 2026-06-19 22:45:25 -06:00
Tyler c8bc2c73e6 feat(backend): SQLAlchemy-backed CycloneStore (T9)
Replace the in-memory InMemoryStore with a SQLAlchemy-backed
CycloneStore that persists Batch, Claim, Remittance, Match, and
ActivityEvent rows to a configurable DB engine (sqlite by default,
overridable via CYCLONE_DB_URL).

Public API preserved: add / get_batch / iter_claims / iter_remittances
/ distinct_providers / recent_activity. New T10/T12 stubs:
list_unmatched, manual_match, manual_unmatch.

Backward-compat shims for tests that called ._batches.clear() or
acquired ._lock as a context manager.

Idempotency: add() does a per-row session.get(Claim, c.id) (and
s.get(Remittance, ...)) check before each insert; duplicates are
skipped with a warning. This makes re-uploading the same fixture
idempotent instead of raising IntegrityError on the PK.

Auto-init DB fixture (backend/tests/conftest.py) sets
CYCLONE_DB_URL to a per-test sqlite file and calls db.init_db()
once per test, replacing the old module-scoped in-memory fixture.
2026-06-19 22:37:12 -06:00
Tyler 00371248b3 feat(backend): add apply_payment, apply_reversal, split_unmatched pure fns 2026-06-19 22:10:44 -06:00
Tyler c13bbf9190 docs(backend): reconcile Protocol — patient_control_number is Optional[str] 2026-06-19 22:04:30 -06:00
Tyler c30416e68c feat(backend): add reconcile.match with date-window + multi-claim fallback 2026-06-19 21:59:23 -06:00
Tyler 05a49be964 feat(backend): add Match + ActivityEvent ORM models 2026-06-19 21:53:52 -06:00
Tyler 846944448a feat(backend): harden T5 — divergence comment + invariant tests 2026-06-19 21:51:43 -06:00
Tyler 0cc74dc173 feat(backend): add Remittance + CasAdjustment ORM models 2026-06-19 21:47:18 -06:00
Tyler 2019a651ed feat(backend): fix T4 index drift, lazy import, FK policy, expand test coverage 2026-06-19 21:42:11 -06:00
Tyler 0fdb760bc1 docs(backend): update db.py docstring to reflect migrations-first order 2026-06-19 21:36:26 -06:00
Tyler 5be6299b3a feat(backend): add Batch + Claim ORM models + ClaimState enum 2026-06-19 21:36:02 -06:00
Tyler 934d623d3a feat(backend): harden db_migrate — rollback test, non-sql filter test, doc convention 2026-06-19 21:25:15 -06:00
Tyler 1bd2334e40 feat(backend): add PRAGMA user_version migration runner + 0001_initial 2026-06-19 21:19:43 -06:00
Tyler 854078a726 feat(backend): add cyclone.db with engine, SessionLocal, Base, init_db 2026-06-19 21:03:21 -06:00
Tyler 4abc03a003 build(backend): add sqlalchemy>=2.0,<3 dependency 2026-06-19 20:58:57 -06:00
Tyler 53010b89dd docs: add DB + reconciliation implementation plan (30 tasks, 7 phases)
Implements the spec at docs/superpowers/specs/2026-06-19-cyclone-db-reconciliation-design.md.

Phase 1 — Backend foundation (T1-T3): SQLAlchemy dep, db.py skeleton,
PRAGMA user_version migration runner, 0001_initial.sql with 6 tables.
Phase 2 — ORM models (T4-T6): Batch, Claim, Remittance, CasAdjustment,
Match, ActivityEvent + ClaimState enum.
Phase 3 — Reconciliation pure functions (T7-T8): match() with date
window + multi-claim fallback, apply_payment/apply_reversal,
split_unmatched.
Phase 4 — Store facade refactor (T9-T12): CycloneStore replaces
InMemoryStore with same public API + list_unmatched / manual_match /
manual_unmatch; reconcile.run orchestrator wired into store.add().
Phase 5 — API additions (T13-T17): db.init() in startup,
GET /api/reconciliation/unmatched, POST /match, POST /unmatch,
parse-835 response includes reconciliation summary; TODO marker
removal verification.
Phase 6 — Frontend (T18-T26): ClaimState types, api.listUnmatched/
matchRemit/unmatchClaim, useReconciliation hook, ClaimStateBadge
primitive (7 states, same palette), Reconciliation page, sidebar
nav entry, vitest worktree exclude.
Phase 7 — Docs + smoke (T27-T30): README persistence section + DB
URL docs, tick off sub-project 1 plan boxes, full end-to-end smoke
procedure, merge to main.

Total: 30 tasks across 7 phases. TDD discipline per task with
~32 new backend tests + ~4 new frontend tests. Target after
completion: 210 backend + 7 frontend tests passing.
2026-06-19 20:51:31 -06:00
Tyler cf9af8e1d9 docs: add DB + reconciliation design spec (sub-project 2 of 4)
Replaces the in-memory store with SQLite via SQLAlchemy 2.0; adds
automatic 837P ↔ 835 reconciliation on every 835 parse; ships a
manual /reconciliation page; extends the claim lifecycle to a
7-state model with reversal handling.

Key decisions (locked from brainstorming):
- SQLite at ~/.local/share/cyclone/cyclone.db, overridable via
  CYCLONE_DB_URL (Postgres escape hatch)
- SQLAlchemy 2.0 sync ORM; PRAGMA user_version migration runner
- Strict auto-match on patient_control_number (CLM01 == CLP01)
  within +/-7 days of service_date
- 7-state claim model with reversal that sets Claim.state=REVERSED
  and preserves prior state on Match.prior_claim_state
- Reconciliation triggered inside store.add() after ERA persist;
  fail-soft (logged + activity event, never blocks parse)
- 3 new API endpoints; new /reconciliation page; same palette
  and aesthetic voice as sub-project 1

Spec is approved-section-by-section and self-reviewed.
2026-06-19 20:42:44 -06:00
Tyler 87cd51e1fc fix(frontend): replay row-flash on every refetch via dataUpdatedAt key
The previous static key={c.id} let React reuse the same <TableRow>
element across refetches, so animate-row-flash only played on
initial mount and on the first render of a brand-new claim_id.
Updated rows whose claim_id was unchanged never re-flashed.

Re-keying on `${c.id}-${dataUpdatedAt}` re-mounts the row on
every refetch (initial load, filter change, parse invalidation),
so the 1.2s accent-tint flash replays each time.

Also: README's 'Frontend unit tests (when added)' is stale — npm
test now exists and runs 3 passing tests.

hooks: add dataUpdatedAt: 0 to the !isConfigured fallback return
so the pages type-check.
2026-06-19 20:00:05 -06:00
Tyler fbd03cd321 docs: rewrite root README for sub-project 1 2026-06-19 19:49:48 -06:00
Tyler 307a8d3b69 docs: add co-medicaid reference note 2026-06-19 19:49:34 -06:00