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:
@@ -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. |
|
| 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. |
|
| 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
|
## Per-Line Adjustment Audit
|
||||||
|
|
||||||
Every 835 CAS segment is tied back to the specific 837 service line it
|
Every 835 CAS segment is tied back to the specific 837 service line it
|
||||||
@@ -275,8 +320,13 @@ backup API).
|
|||||||
|
|
||||||
## Roadmap
|
## 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.**
|
- **Sub-project 3 (shipped) — More 837P/835 features.**
|
||||||
- **837P validation rules:** R034 enforces `REF*G1` on frequency-code
|
- **837P validation rules:** R034 enforces `REF*G1` on frequency-code
|
||||||
7/8 claims; R035 enforces `BHT06` transaction-type-code is in the
|
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.
|
and surfaces per-line CAS reasons + unmatched-line warnings.
|
||||||
- See the "Per-Line Adjustment Audit" section below for details.
|
- 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
|
### SP3 endpoints
|
||||||
|
|
||||||
- `POST /api/parse-837?ack=true` — existing 837 parse, plus optional
|
- `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
|
`matched_lines` + `total_lines` so the MatchedRemitCard can badge
|
||||||
partial matches.
|
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
|
## License
|
||||||
|
|
||||||
No license file yet; this is internal-use software. Add a `LICENSE` file
|
No license file yet; this is internal-use software. Add a `LICENSE` file
|
||||||
|
|||||||
Reference in New Issue
Block a user