diff --git a/README.md b/README.md index 49f8978..da1550f 100644 --- a/README.md +++ b/README.md @@ -123,16 +123,45 @@ backup API). ## Roadmap -Sub-project 2 (DB + reconciliation) is **shipped**. The other two: +Sub-projects 2 and 3 are **shipped**. Next up: -- **Sub-project 3 — More 837P/835 features.** Additional 837P validation - rules (REF*G1 enforcement, BHT06), 835 CAS deep-parsing, 999 ACK, and - 270/271 eligibility. +- **Sub-project 3 (shipped) — More 837P/835 features.** + - **837P validation rules:** R034 enforces `REF*G1` on frequency-code + 7/8 claims; R035 enforces `BHT06` transaction-type-code is in the + allowed set per payer config. + - **835 CAS deep-parsing:** every CAS adjustment now surfaces with its + CARC reason code + label (e.g. `CO-29: The time limit for filing has + expired`), surfaced via `GET /api/remittances/{id}` and rendered as + an expansion row on the Remittances page. + - **999 ACK transaction set:** full inbound parser + outbound + serializer. Auto-generated on 837 ingest when `?ack=true` is passed + to `POST /api/parse-837`. Persisted to the `acks` table, browsable + on a new `/acks` page, and downloadable as the regenerated raw + 999 text. + - **270/271 eligibility (API-only):** `POST /api/eligibility/request` + builds a 270 from a JSON payload (subscriber, provider, payer, + service type); `POST /api/eligibility/parse-271` ingests the + response and returns structured `coverage_benefits`. - **Sub-project 4 — Frontend features.** Per-claim detail drawer, batch diff view, real-time streaming of NDJSON GET responses, advanced filters (date range, multi-status, saved filter sets), keyboard-driven navigation. +### SP3 endpoints + +- `POST /api/parse-837?ack=true` — existing 837 parse, plus optional + auto-generated 999 ACK persisted alongside the batch. +- `POST /api/parse-999` — parse an inbound 999 ACK and persist it. +- `GET /api/acks` — list ACKs. +- `GET /api/acks/{id}` — ACK detail, including the regenerated + `raw_999_text`. +- `POST /api/eligibility/request` — build a 270 from JSON. +- `POST /api/eligibility/parse-271` — ingest a 271 and return parsed + coverage benefits. + +The UI gains a new **Acks** page (sidebar entry) that lists persisted +ACKs and lets you download the regenerated 999 text. + ## License No license file yet; this is internal-use software. Add a `LICENSE` file diff --git a/docs/superpowers/plans/2026-06-20-cyclone-edi-features.md b/docs/superpowers/plans/2026-06-20-cyclone-edi-features.md index ec34b39..0d2ba44 100644 --- a/docs/superpowers/plans/2026-06-20-cyclone-edi-features.md +++ b/docs/superpowers/plans/2026-06-20-cyclone-edi-features.md @@ -23,7 +23,7 @@ by the parser, so R034 scans `raw_segments` for any segment whose first two elements are `["REF", "G1", ...]`. No new model field is required. -- [ ] **Step 1: Add failing tests** +- [x] **Step 1: Add failing tests** Append to `backend/tests/test_validator.py`: @@ -45,12 +45,12 @@ def test_r034_ref_g1_required_when_freq_1_no_ref_g1_passes(strict_ref_g1_payer): # expect no error (rule only fires on 7/8) ``` -- [ ] **Step 2: Run tests to verify they fail** -- [ ] **Step 3: Implement `_r034_ref_g1_required` in `validator.py`** +- [x] **Step 2: Run tests to verify they fail** +- [x] **Step 3: Implement `_r034_ref_g1_required` in `validator.py`** - Scan `claim.raw_segments` for `["REF", "G1", *]`; only check if claim frequency is 7 or 8; only emit if `cfg.require_ref_g1_for_adjustments`. -- [ ] **Step 4: Register in `_RULES` list** -- [ ] **Step 5: Commit** +- [x] **Step 4: Register in `_RULES` list** +- [x] **Step 5: Commit** ### Task 2: BHT06 transaction-type-code rule (R035) @@ -71,34 +71,34 @@ def test_r034_ref_g1_required_when_freq_1_no_ref_g1_passes(strict_ref_g1_payer): > Set Purpose Code" (which is BHT02, qualifier 353). The plan originally > conflated these. Use `transaction_type_code` for the new `Envelope` field. -- [ ] **Step 1: Add failing tests** — frequency_code=7/8 with no REF*G1 in raw_segments should NOT fire R034 (R034 is gated on `cfg.require_ref_g1_for_adjustments`, which is False for CO). Test must use a "strict" config. -- [ ] **Step 2: Run tests to verify they fail** -- [ ] **Step 3: Add `transaction_type_code: str | None = None` to `Envelope` model** -- [ ] **Step 4: Populate it in `_build_envelope` from `seg[6]` when present** -- [ ] **Step 5: Implement `_r035_bht06_allowed` in `validator.py`** +- [x] **Step 1: Add failing tests** — frequency_code=7/8 with no REF*G1 in raw_segments should NOT fire R034 (R034 is gated on `cfg.require_ref_g1_for_adjustments`, which is False for CO). Test must use a "strict" config. +- [x] **Step 2: Run tests to verify they fail** +- [x] **Step 3: Add `transaction_type_code: str | None = None` to `Envelope` model** +- [x] **Step 4: Populate it in `_build_envelope` from `seg[6]` when present** +- [x] **Step 5: Implement `_r035_bht06_allowed` in `validator.py`** - If `claim.envelope.transaction_type_code` is set and not in `cfg.allowed_bht06`, emit error. -- [ ] **Step 6: Register in `_RULES` list** -- [ ] **Step 7: Commit** +- [x] **Step 6: Register in `_RULES` list** +- [x] **Step 7: Commit** ### Task 3: Verify existing R031 (REF*G1 optional) keeps no-op behavior **Files:** - Verify: `backend/src/cyclone/parsers/validator.py:54-57` -- [ ] **Step 1: Read current `_r031_ref_g1_optional`** -- [ ] **Step 2: Confirm it stays as a no-op (returns empty tuple)** -- [ ] **Step 3: (No commit — verification task)** +- [x] **Step 1: Read current `_r031_ref_g1_optional`** +- [x] **Step 2: Confirm it stays as a no-op (returns empty tuple)** +- [x] **Step 3: (No commit — verification task)** ### Task 4: Wire `require_ref_g1_for_adjustments` into CO Medicaid factory **Files:** - Verify: `backend/src/cyclone/parsers/payer.py:67` -- [ ] **Step 1: Confirm factory sets the flag for CO (currently `False` for v1 leniency)** -- [ ] **Step 2: Run existing co_medicaid fixture through new R034** -- [ ] **Step 3: Adjust fixture or factory if false positives appear** -- [ ] **Step 4: Commit if any change** +- [x] **Step 1: Confirm factory sets the flag for CO (currently `False` for v1 leniency)** +- [x] **Step 2: Run existing co_medicaid fixture through new R034** +- [x] **Step 3: Adjust fixture or factory if false positives appear** +- [x] **Step 4: Commit if any change** ### Task 5: Phase 1 commit + full suite @@ -117,11 +117,11 @@ Expected: 205 + 6 = ~211 pass. - Create: `backend/src/cyclone/parsers/cas_codes.py` - Create: `backend/tests/test_cas_codes.py` -- [ ] **Step 1: Add failing tests** for known/unknown lookup, count > 100 -- [ ] **Step 2: Run tests to verify they fail** -- [ ] **Step 3: Implement `reason_label` + `all_known_codes` with ~150 CARC codes** -- [ ] **Step 4: Run tests to verify they pass** -- [ ] **Step 5: Commit** +- [x] **Step 1: Add failing tests** for known/unknown lookup, count > 100 +- [x] **Step 2: Run tests to verify they fail** +- [x] **Step 3: Implement `reason_label` + `all_known_codes` with ~150 CARC codes** +- [x] **Step 4: Run tests to verify they pass** +- [x] **Step 5: Commit** ### Task 7: Extend `GET /api/remittances/{id}` to surface `adjustments` @@ -130,13 +130,13 @@ Expected: 205 + 6 = ~211 pass. - Modify: `backend/src/cyclone/api.py` - Modify: `backend/tests/test_api_gets.py` -- [ ] **Step 1: Add failing tests** (empty adjustments returns `[]`, populated returns `{group, reason, label, amount, quantity}`) -- [ ] **Step 2: Run tests to verify they fail** -- [ ] **Step 3: Add a `get_remit_with_adjustments` method to `CycloneStore`** -- [ ] **Step 4: Update `to_ui_remittance` (or add `to_ui_remittance_detail`) to include adjustments array** -- [ ] **Step 5: Update endpoint to use the new mapper** -- [ ] **Step 6: Run tests to verify they pass** -- [ ] **Step 7: Commit** +- [x] **Step 1: Add failing tests** (empty adjustments returns `[]`, populated returns `{group, reason, label, amount, quantity}`) +- [x] **Step 2: Run tests to verify they fail** +- [x] **Step 3: Add a `get_remit_with_adjustments` method to `CycloneStore`** +- [x] **Step 4: Update `to_ui_remittance` (or add `to_ui_remittance_detail`) to include adjustments array** +- [x] **Step 5: Update endpoint to use the new mapper** +- [x] **Step 6: Run tests to verify they pass** +- [x] **Step 7: Commit** ### Task 8: Frontend Remit type + Remittances page adjustments expansion @@ -144,11 +144,11 @@ Expected: 205 + 6 = ~211 pass. - Modify: `src/types/index.ts` - Modify: `src/pages/Remittances.tsx` -- [ ] **Step 1: Add `adjustments?: { group, reason, label, amount, quantity }[]` to `Remittance`** -- [ ] **Step 2: Update Remittances page to render adjustments expansion** -- [ ] **Step 3: Add test** in Remittances.test.tsx -- [ ] **Step 4: Verify build + tests pass** -- [ ] **Step 5: Commit** +- [x] **Step 1: Add `adjustments?: { group, reason, label, amount, quantity }[]` to `Remittance`** +- [x] **Step 2: Update Remittances page to render adjustments expansion** +- [x] **Step 3: Add test** in Remittances.test.tsx +- [x] **Step 4: Verify build + tests pass** +- [x] **Step 5: Commit** ### Task 9: Phase 2 commit + full suite @@ -167,32 +167,32 @@ Expected: ~215 backend, ~9 frontend. - Create: `backend/src/cyclone/parsers/models_999.py` - Modify: `backend/src/cyclone/parsers/__init__.py` -- [ ] **Step 1: Implement model classes per spec §4.1** -- [ ] **Step 2: Wire lazy re-exports** -- [ ] **Step 3: Verify import + simple instantiation works** (1 sanity test) -- [ ] **Step 4: Commit** +- [x] **Step 1: Implement model classes per spec §4.1** +- [x] **Step 2: Wire lazy re-exports** +- [x] **Step 3: Verify import + simple instantiation works** (1 sanity test) +- [x] **Step 4: Commit** ### Task 11: 999 parser **Files:** - Create: `backend/src/cyclone/parsers/parse_999.py` -- [ ] **Step 1: Add failing tests** with minimal_999.txt fixture (basic happy path) -- [ ] **Step 2: Run tests to verify they fail** -- [ ] **Step 3: Implement orchestrator** following `parse_835.py` pattern -- [ ] **Step 4: Run tests to verify they pass** -- [ ] **Step 5: Commit** +- [x] **Step 1: Add failing tests** with minimal_999.txt fixture (basic happy path) +- [x] **Step 2: Run tests to verify they fail** +- [x] **Step 3: Implement orchestrator** following `parse_835.py` pattern +- [x] **Step 4: Run tests to verify they pass** +- [x] **Step 5: Commit** ### Task 12: 999 serializer (outbound) **Files:** - Create: `backend/src/cyclone/parsers/serialize_999.py` -- [ ] **Step 1: Add failing round-trip test** (parse → serialize → parse → assert equal) -- [ ] **Step 2: Run test to verify it fails** -- [ ] **Step 3: Implement `serialize_999`** (ISA/GS/ST/AK1/[AK2/AK3/AK4/AK5*]/AK9/SE/GE/IEA) -- [ ] **Step 4: Run test to verify it passes** -- [ ] **Step 5: Commit** +- [x] **Step 1: Add failing round-trip test** (parse → serialize → parse → assert equal) +- [x] **Step 2: Run test to verify it fails** +- [x] **Step 3: Implement `serialize_999`** (ISA/GS/ST/AK1/[AK2/AK3/AK4/AK5*]/AK9/SE/GE/IEA) +- [x] **Step 4: Run test to verify it passes** +- [x] **Step 5: Commit** ### Task 13: `acks` table + ORM + migration @@ -200,22 +200,22 @@ Expected: ~215 backend, ~9 frontend. - Create: `backend/src/cyclone/migrations/0002_acks.sql` - Modify: `backend/src/cyclone/db.py` -- [ ] **Step 1: Add migration SQL** per spec §4.4 -- [ ] **Step 2: Add ORM model `Ack` to `db.py`** -- [ ] **Step 3: Add `CycloneStore.add_ack(...)` method** -- [ ] **Step 4: Run migration on a fresh DB; verify table exists** -- [ ] **Step 5: Commit** +- [x] **Step 1: Add migration SQL** per spec §4.4 +- [x] **Step 2: Add ORM model `Ack` to `db.py`** +- [x] **Step 3: Add `CycloneStore.add_ack(...)` method** +- [x] **Step 4: Run migration on a fresh DB; verify table exists** +- [x] **Step 5: Commit** ### Task 14: `POST /api/parse-999` endpoint **Files:** - Modify: `backend/src/cyclone/api.py` -- [ ] **Step 1: Add failing tests** (parse + persist, returns summary) -- [ ] **Step 2: Run tests to verify they fail** -- [ ] **Step 3: Add endpoint** mirroring `parse-835` JSON path -- [ ] **Step 4: Run tests to verify they pass** -- [ ] **Step 5: Commit** +- [x] **Step 1: Add failing tests** (parse + persist, returns summary) +- [x] **Step 2: Run tests to verify they fail** +- [x] **Step 3: Add endpoint** mirroring `parse-835` JSON path +- [x] **Step 4: Run tests to verify they pass** +- [x] **Step 5: Commit** ### Task 15: Auto-generate 999 on 837 ingest via `?ack=true` @@ -223,13 +223,13 @@ Expected: ~215 backend, ~9 frontend. - Modify: `backend/src/cyclone/api.py` - Modify: `backend/tests/test_api_parse_persists.py` -- [ ] **Step 1: Add 3 failing tests** (default no ack, ack=true happy path, ack=true with rejected claims) -- [ ] **Step 2: Run tests to verify they fail** -- [ ] **Step 3: Add `ack: bool = Query(False)` param to `parse_837_endpoint`** -- [ ] **Step 4: Build `BatchAckBuilder` that converts validation report to 999** -- [ ] **Step 5: Persist `Ack` row, attach to response body** -- [ ] **Step 6: Run tests to verify they pass** -- [ ] **Step 7: Commit** +- [x] **Step 1: Add 3 failing tests** (default no ack, ack=true happy path, ack=true with rejected claims) +- [x] **Step 2: Run tests to verify they fail** +- [x] **Step 3: Add `ack: bool = Query(False)` param to `parse_837_endpoint`** +- [x] **Step 4: Build `BatchAckBuilder` that converts validation report to 999** +- [x] **Step 5: Persist `Ack` row, attach to response body** +- [x] **Step 6: Run tests to verify they pass** +- [x] **Step 7: Commit** ### Task 16: Frontend `/acks` page + sidebar entry @@ -241,14 +241,14 @@ Expected: ~215 backend, ~9 frontend. - Create: `src/pages/Acks.test.tsx` - Modify: `src/components/Sidebar.tsx` -- [ ] **Step 1: Add `Ack` type to `src/types/index.ts`** -- [ ] **Step 2: Add `api.listAcks()` + `api.getAck(id)` to `src/lib/api.ts`** -- [ ] **Step 3: Create `useAcks` hook** -- [ ] **Step 4: Create `Acks.tsx` page** with empty state, populated list, download button -- [ ] **Step 5: Add sidebar nav entry with `CheckCircle2` icon** -- [ ] **Step 6: Add tests** (1-2 cases: empty + populated) -- [ ] **Step 7: Verify typecheck + build + tests pass** -- [ ] **Step 8: Commit** +- [x] **Step 1: Add `Ack` type to `src/types/index.ts`** +- [x] **Step 2: Add `api.listAcks()` + `api.getAck(id)` to `src/lib/api.ts`** +- [x] **Step 3: Create `useAcks` hook** +- [x] **Step 4: Create `Acks.tsx` page** with empty state, populated list, download button +- [x] **Step 5: Add sidebar nav entry with `CheckCircle2` icon** +- [x] **Step 6: Add tests** (1-2 cases: empty + populated) +- [x] **Step 7: Verify typecheck + build + tests pass** +- [x] **Step 8: Commit** ### Task 17: Phase 3 commit + full suite @@ -266,54 +266,54 @@ Expected: ~225 backend, ~10 frontend. **Files:** - Create: `backend/src/cyclone/parsers/models_270.py` -- [ ] **Step 1: Implement model classes per spec §4.2** -- [ ] **Step 2: Wire lazy re-exports** -- [ ] **Step 3: Sanity test** -- [ ] **Step 4: Commit** +- [x] **Step 1: Implement model classes per spec §4.2** +- [x] **Step 2: Wire lazy re-exports** +- [x] **Step 3: Sanity test** +- [x] **Step 4: Commit** ### Task 19: 271 response models + service-type-code dict **Files:** - Create: `backend/src/cyclone/parsers/models_271.py` -- [ ] **Step 1: Implement model classes per spec §4.3** -- [ ] **Step 2: Add inline `SERVICE_TYPE_CODES` dict** (~50 codes) -- [ ] **Step 3: Add `service_type_description(code)` helper** -- [ ] **Step 4: Sanity test** -- [ ] **Step 5: Commit** +- [x] **Step 1: Implement model classes per spec §4.3** +- [x] **Step 2: Add inline `SERVICE_TYPE_CODES` dict** (~50 codes) +- [x] **Step 3: Add `service_type_description(code)` helper** +- [x] **Step 4: Sanity test** +- [x] **Step 5: Commit** ### Task 20: 270 request parser **Files:** - Create: `backend/src/cyclone/parsers/parse_270.py` -- [ ] **Step 1: Add failing tests** with minimal_270.txt fixture -- [ ] **Step 2: Run tests to verify they fail** -- [ ] **Step 3: Implement orchestrator** -- [ ] **Step 4: Run tests to verify they pass** -- [ ] **Step 5: Commit** +- [x] **Step 1: Add failing tests** with minimal_270.txt fixture +- [x] **Step 2: Run tests to verify they fail** +- [x] **Step 3: Implement orchestrator** +- [x] **Step 4: Run tests to verify they pass** +- [x] **Step 5: Commit** ### Task 21: 271 response parser **Files:** - Create: `backend/src/cyclone/parsers/parse_271.py` -- [ ] **Step 1: Add failing tests** with minimal_271.txt fixture (2 EB segments) -- [ ] **Step 2: Run tests to verify they fail** -- [ ] **Step 3: Implement orchestrator** with EB segment handling -- [ ] **Step 4: Run tests to verify they pass** -- [ ] **Step 5: Commit** +- [x] **Step 1: Add failing tests** with minimal_271.txt fixture (2 EB segments) +- [x] **Step 2: Run tests to verify they fail** +- [x] **Step 3: Implement orchestrator** with EB segment handling +- [x] **Step 4: Run tests to verify they pass** +- [x] **Step 5: Commit** ### Task 22: 270 request serializer (outbound) **Files:** - Create: `backend/src/cyclone/parsers/serialize_270.py` -- [ ] **Step 1: Add failing round-trip test** -- [ ] **Step 2: Run test to verify it fails** -- [ ] **Step 3: Implement `serialize_270`** -- [ ] **Step 4: Run test to verify it passes** -- [ ] **Step 5: Commit** +- [x] **Step 1: Add failing round-trip test** +- [x] **Step 2: Run test to verify it fails** +- [x] **Step 3: Implement `serialize_270`** +- [x] **Step 4: Run test to verify it passes** +- [x] **Step 5: Commit** ### Task 23: `POST /api/eligibility/request` endpoint @@ -321,22 +321,22 @@ Expected: ~225 backend, ~10 frontend. - Modify: `backend/src/cyclone/api.py` - Create: `backend/tests/test_api_eligibility.py` -- [ ] **Step 1: Add failing test** -- [ ] **Step 2: Run test to verify it fails** -- [ ] **Step 3: Implement endpoint** -- [ ] **Step 4: Run test to verify it passes** -- [ ] **Step 5: Commit** +- [x] **Step 1: Add failing test** +- [x] **Step 2: Run test to verify it fails** +- [x] **Step 3: Implement endpoint** +- [x] **Step 4: Run test to verify it passes** +- [x] **Step 5: Commit** ### Task 24: `POST /api/eligibility/parse-271` endpoint **Files:** - Modify: `backend/src/cyclone/api.py` -- [ ] **Step 1: Add 2 failing tests** (happy path + parse error) -- [ ] **Step 2: Run tests to verify they fail** -- [ ] **Step 3: Implement endpoint** -- [ ] **Step 4: Run tests to verify they pass** -- [ ] **Step 5: Commit** +- [x] **Step 1: Add 2 failing tests** (happy path + parse error) +- [x] **Step 2: Run tests to verify they fail** +- [x] **Step 3: Implement endpoint** +- [x] **Step 4: Run tests to verify they pass** +- [x] **Step 5: Commit** ### Task 25: Fixtures + cross-parser round-trip test @@ -344,11 +344,11 @@ Expected: ~225 backend, ~10 frontend. - Create: `backend/tests/fixtures/minimal_270.txt` - Create: `backend/tests/fixtures/minimal_271.txt` -- [ ] **Step 1: Write minimal_270.txt fixture** (small valid 270 request) -- [ ] **Step 2: Write minimal_271.txt fixture** (small valid 271 response with 2 EB segments) -- [ ] **Step 3: Add round-trip integration test** (parse 270 → serialize → parse → assert equal) -- [ ] **Step 4: Verify tests pass** -- [ ] **Step 5: Commit** +- [x] **Step 1: Write minimal_270.txt fixture** (small valid 270 request) +- [x] **Step 2: Write minimal_271.txt fixture** (small valid 271 response with 2 EB segments) +- [x] **Step 3: Add round-trip integration test** (parse 270 → serialize → parse → assert equal) +- [x] **Step 4: Verify tests pass** +- [x] **Step 5: Commit** ### Task 26: Phase 4 commit + full suite @@ -370,23 +370,23 @@ Verify `docs/superpowers/specs/2026-06-20-cyclone-edi-features-design.md` is on **Files:** - Modify: `README.md` -- [ ] **Step 1: Update Roadmap section** to mark SP3 shipped and describe 4 areas -- [ ] **Step 2: Commit** +- [x] **Step 1: Update Roadmap section** to mark SP3 shipped and describe 4 areas +- [x] **Step 2: Commit** ### Task 29: End-to-end smoke -- [ ] **Step 1: Clean DB**, start server, parse 837 with `?ack=true` -- [ ] **Step 2: Confirm 999 in response + `acks` row in DB** -- [ ] **Step 3: Parse 999 fixture**, confirm endpoint round-trips -- [ ] **Step 4: Build 270 request** via API, paste into parser -- [ ] **Step 5: Commit empty** `smoke: end-to-end SP3 flow passes` +- [x] **Step 1: Clean DB**, start server, parse 837 with `?ack=true` +- [x] **Step 2: Confirm 999 in response + `acks` row in DB** +- [x] **Step 3: Parse 999 fixture**, confirm endpoint round-trips +- [x] **Step 4: Build 270 request** via API, paste into parser +- [x] **Step 5: Commit empty** `smoke: end-to-end SP3 flow passes` ### Task 30: Final cleanup + merge worktree to main -- [ ] **Step 1: Verify worktree is clean** -- [ ] **Step 2: Discard main's stale uncommitted changes** (if any) -- [ ] **Step 3: Fast-forward merge `edi-features` into `main`** -- [ ] **Step 4: Remove worktree + delete branch** +- [x] **Step 1: Verify worktree is clean** +- [x] **Step 2: Discard main's stale uncommitted changes** (if any) +- [x] **Step 3: Fast-forward merge `edi-features` into `main`** +- [x] **Step 4: Remove worktree + delete branch** ---