docs(sp8): Outbound 837 Serializer section + roadmap update

- 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.
This commit is contained in:
Tyler
2026-06-20 20:55:39 -06:00
parent cb74bc9307
commit 02841d7e6e
+74 -5
View File
@@ -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=<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