Files
cyclone/docs/superpowers/plans/2026-06-20-cyclone-edi-features.md

14 KiB

Sub-project 3 — Backend EDI Features: Implementation Plan

Date: 2026-06-20 Status: Approved Branch: edi-features Spec: docs/superpowers/specs/2026-06-20-cyclone-edi-features-design.md

30 tasks across 5 phases. Follows the same task pattern as the SP2 plan ([ ] Step 1 → Step N → commit).

Phase 1 — 837P extra validation rules

Note on rule IDs: R032 and R033 are already used in validator.py for CLM05-2 facility code qualifier and CLM05-1 place of service code respectively. The two new rules in this phase use R034 (REF*G1 enforcement) and R035 (BHT06 transaction type code).

Task 1: REF*G1 enforcement rule (R034)

Files:

  • Modify: backend/src/cyclone/parsers/validator.py

Detection mechanism: ClaimOutput.raw_segments is already populated 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

Append to backend/tests/test_validator.py:

def test_r034_ref_g1_required_when_freq_7_no_ref_g1_emits_error(strict_ref_g1_payer):
    # ... claim with frequency_code=7, no REF*G1 in raw_segments
    # expect validation error

def test_r034_ref_g1_required_when_freq_8_no_ref_g1_emits_error(strict_ref_g1_payer):
    # ... claim with frequency_code=8, no REF*G1 in raw_segments
    # expect validation error

def test_r034_ref_g1_required_when_freq_7_with_ref_g1_passes(strict_ref_g1_payer):
    # ... claim with frequency_code=7, REF*G1 present in raw_segments
    # expect no error

def test_r034_ref_g1_required_when_freq_1_no_ref_g1_passes(strict_ref_g1_payer):
    # ... claim with frequency_code=1, no REF*G1
    # 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
    • 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

Task 2: BHT06 transaction-type-code rule (R035)

Files:

  • Modify: backend/src/cyclone/parsers/validator.py
  • Modify: backend/src/cyclone/parsers/models.py (add ClaimOutput.transaction_type_code: str | None = None)
  • Modify: backend/src/cyclone/parsers/parse_837.py (populate ClaimOutput.transaction_type_code from BHT06 in the per-claim builder)

Why on ClaimOutput, not Envelope? The current ClaimOutput shape already mirrors a few envelope fields (control_number, transaction_date) because the per-claim flow is easier when the validator only needs to look at one object. Following the same convention keeps R035's lookup one-liner clean and avoids threading the Envelope into every validation rule signature.

Note on naming: BHT06 in the 837P 005010X222A1 implementation is the "Transaction Type Code" element (qualifier 640) — not the "Transaction 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
    • 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

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)

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

Task 5: Phase 1 commit + full suite

.venv/bin/pytest -q

Expected: 205 + 6 = ~211 pass.


Phase 2 — 835 CAS deep-parsing (reason-code lookup)

Task 6: parsers/cas_codes.py with CARC dict

Files:

  • 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

Task 7: Extend GET /api/remittances/{id} to surface adjustments

Files:

  • Modify: backend/src/cyclone/store.py

  • 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

Task 8: Frontend Remit type + Remittances page adjustments expansion

Files:

  • 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

Task 9: Phase 2 commit + full suite

.venv/bin/pytest -q && npm test -- --run

Expected: ~215 backend, ~9 frontend.


Phase 3 — 999 ACK transaction set

Task 10: New Pydantic models for 999

Files:

  • 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

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

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

Task 13: acks table + ORM + migration

Files:

  • 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

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

Task 15: Auto-generate 999 on 837 ingest via ?ack=true

Files:

  • 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

Task 16: Frontend /acks page + sidebar entry

Files:

  • Modify: src/lib/api.ts

  • Modify: src/hooks/ (new useAcks.ts)

  • Modify: src/types/index.ts

  • Create: src/pages/Acks.tsx

  • 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

Task 17: Phase 3 commit + full suite

.venv/bin/pytest -q && npm test -- --run

Expected: ~225 backend, ~10 frontend.


Phase 4 — 270/271 eligibility

Task 18: 270 request models

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

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

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

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

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

Task 23: POST /api/eligibility/request endpoint

Files:

  • 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

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

Task 25: Fixtures + cross-parser round-trip test

Files:

  • 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

Task 26: Phase 4 commit + full suite

.venv/bin/pytest -q

Expected: ~229 backend, ~10 frontend.


Phase 5 — Docs + smoke

Task 27: Spec doc already committed (T0)

Verify docs/superpowers/specs/2026-06-20-cyclone-edi-features-design.md is on branch.

Task 28: Update root README

Files:

  • Modify: README.md

  • Step 1: Update Roadmap section to mark SP3 shipped and describe 4 areas

  • 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

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

Test count targets

Phase New backend New frontend Running total
Start 205 8
P1 end +6 0 211 / 8
P2 end +4 +1 215 / 9
P3 end +10 +2 225 / 11
P4 end +4 0 229 / 11

Risk reminders

  • 270/271 scope creep — keep tight to EQ/EB surface.
  • 999 auto-gen semantics — "rejected" = validation errors.
  • CARC codes drift — LAST_UPDATED constant for tracking.
  • Migration ordering — 0002_acks.sql after 0001_initial.sql, idempotent.