From 02841d7e6ee849adc59c46f286c79ef1d2eecd0b Mon Sep 17 00:00:00 2001 From: Tyler Date: Sat, 20 Jun 2026 20:55:39 -0600 Subject: [PATCH] docs(sp8): Outbound 837 Serializer section + roadmap update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Mark SP8 as shipped (Sub-projects 2 through 8) - Drop the 'Next up: outbound 837P serializer' trailer - New 'Outbound 837 Serializer' section above 'Per-Line Adjustment Audit': design rationale (Approach A full rebuild vs spec §3.1 hybrid), two-surface UX (single-claim drawer download + multi-claim inbox ZIP bundle), pointers to the serializer module, the two API endpoints, the frontend helpers, and the UI entry points. - New 'SP8 endpoints' subsection in the endpoint inventory. --- README.md | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 74 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ad22507..2614c71 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,51 @@ parses and rejects claims, the inbox reflects the new | POST | `/api/inbox/rejected/resubmit` | `{claim_ids: [...]}`. `200` with `conflicts` for non-rejected. | | GET | `/api/inbox/export.csv?lane=` | Streams CSV of the lane's rows. | +## Outbound 837 Serializer + +The same canonical `ClaimOutput` model that powers 837 ingestion also +drives **outbound** 837P regeneration. Operators can take a claim +that's been parsed, edited, or rejected and emit a byte-faithful X12 +837P file — closing the resubmit loop without leaving the app. + +Two surfaces: + +- **Single claim → .x12 download** from the ClaimDrawer header. Click + the **Download 837** icon, get a `claim-{id}.x12` attachment. The + file is a complete ISA/GS/ST envelope + hierarchy + CLM/SV1 lines, + regenerated from the canonical `ClaimOutput` fields (not by patching + `raw_segments`). +- **Multi-claim → .zip bundle** from the Inbox rejected lane. Select + N>1 rejected claims, hit **Resubmit**, choose **Resubmit + Download** + in the confirm modal, and the backend hands you + `resubmit-{N}-claims.zip` with one `.x12` per successfully + resubmitted claim. Each file gets a unique ISA13/GS06 so back-to-back + claims don't collide on control numbers. + +Design spec at `docs/superpowers/specs/2026-06-20-cyclone-serialize-837-design.md`. +Implementation note: the spec proposed a hybrid "patch `raw_segments`" +approach, but `raw_segments` only captures post-CLM segments (CLM, +REF*G1, HI, LX, SV1 pairs) — the envelope and hierarchy segments +(NM1, N3, N4, HL, PRV, SBR, DMG) are not captured, so a pass-through +serializer cannot regenerate them byte-for-byte. The shipped +implementation rebuilds the entire file from canonical fields +("Approach A" in the plan). The round-trip guarantee still holds: +all 113 prodfiles in `docs/prodfiles/claims/*.x12` parse → serialize → +parse back to the same claim id. + +### Where to find it + +- Backend serializer: `backend/src/cyclone/parsers/serialize_837.py` +- Single-claim download API: `GET /api/claims/{id}/serialize-837` +- Bundle API: `POST /api/inbox/rejected/resubmit?download=true` +- Frontend helper: `src/lib/api.ts:serializeClaim837`, + `src/lib/inbox-api.ts:resubmitRejectedWithDownload` +- Browser download utility: `src/lib/download.ts` (`downloadTextFile`, + `downloadBlob`) +- UI: + - `src/components/ClaimDrawer/ClaimDrawerHeader.tsx` — Download icon + - `src/pages/Inbox.tsx` — Resubmit bundle modal + progress overlay + ## Per-Line Adjustment Audit Every 835 CAS segment is tied back to the specific 837 service line it @@ -275,8 +320,13 @@ backup API). ## Roadmap -Sub-projects 2 through 7 are **shipped**. Next up: +Sub-projects 2 through 8 are **shipped**. Next up: +- **Sub-project 8 (shipped) — Outbound 837P serializer.** Closes the + resubmit loop: rejected claims can be regenerated back to an X12 837P + file (single download from the claim drawer, or ZIP bundle from the + inbox rejected lane). See the "Outbound 837 Serializer" section + below for details. - **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 @@ -370,10 +420,6 @@ Sub-projects 2 through 7 are **shipped**. Next up: and surfaces per-line CAS reasons + unmatched-line warnings. - See the "Per-Line Adjustment Audit" section below for details. -**Next up:** outbound 837P serializer — closes the resubmit loop -(rejected claim → corrected 837P back to payer). Design spec at -`docs/superpowers/specs/2026-06-20-cyclone-serialize-837-design.md`. - ### SP3 endpoints - `POST /api/parse-837?ack=true` — existing 837 parse, plus optional @@ -441,6 +487,29 @@ ACKs and lets you download the regenerated 999 text. `matched_lines` + `total_lines` so the MatchedRemitCard can badge partial matches. +### SP8 endpoints (outbound 837P) + +- `GET /api/claims/{id}/serialize-837` — regenerate the persisted + claim as a byte-faithful X12 837P file (`text/x12` body, + `Content-Disposition: attachment; filename="claim-{id}.x12"`). + 404 if the claim doesn't exist, 422 if the stored `raw_json` + can't be revalidated as a `ClaimOutput`. Drives the **Download 837** + icon in the ClaimDrawer header. +- `POST /api/inbox/rejected/resubmit?download=true` — same mass-resubmit + endpoint as SP6, but the response is a ZIP archive (`application/zip`) + containing one `claim-{id}.x12` per successfully resubmitted claim. + Each file uses `serialize_837_for_resubmit` so back-to-back files in + the bundle get unique ISA13/GS06 control numbers (back-to-back + resubmits would otherwise all share `000000001`). Conflicts and + missing ids are deliberately omitted from the ZIP — the user already + saw them in the JSON response on prior calls. Per-claim regenerate + failures (rare — usually means `raw_json` is corrupted for one claim) + are surfaced via the `X-Cyclone-Serialize-Errors` response header + (JSON-encoded array) so the UI can show "10 resubmitted, 2 couldn't + be regenerated" without parsing the binary. Drives the **Resubmit + + Download** button in the Inbox rejected-lane BulkBar (N>1 modal + prompt). + ## License No license file yet; this is internal-use software. Add a `LICENSE` file