feat(backend): FastAPI endpoint with NDJSON streaming and CORS for Vite frontend

This commit is contained in:
Tyler
2026-06-19 16:35:53 -06:00
parent ab4d8c318e
commit 07a8fbb720
5 changed files with 473 additions and 2 deletions
+87 -1
View File
@@ -2,7 +2,7 @@
Python module + CLI for parsing X12 837P professional claim files into one
validated JSON per claim. Output is consumed by the Cyclone Vite/React
frontend (later via a FastAPI service).
frontend via a FastAPI service (see **REST API** below).
## Install
@@ -62,6 +62,92 @@ pytest -v
The CLI smoke test against `docs/prodfiles/837p-from-axiscare/*.txt` is
auto-skipped if no production files are present.
## REST API
The Cyclone Vite/React frontend (default dev origin `http://localhost:5173`)
talks to the parser over HTTP. The API module is `cyclone.api:app`.
### Run the server
```bash
uvicorn cyclone.api:app --reload --port 8000
# or, equivalently:
python -m cyclone serve
```
### Endpoints
| Method | Path | Purpose |
| ------ | ---------------- | -------------------------------------------------- |
| GET | `/api/health` | Liveness probe: `{"status": "ok", "version": ...}` |
| POST | `/api/parse-837` | Upload an X12 file, get parsed claims back |
`POST /api/parse-837` accepts `multipart/form-data` with a single `file`
field. Optional query parameters:
- `payer``co_medicaid` (default) or `generic_837p`
- `include_raw_segments``true` (default) or `false`
- `strict``false` (default) or `true` (promote warnings to errors)
The response shape depends on the request's `Accept` header:
- `Accept: application/json` → a single `ParseResult`-shaped JSON object
(envelope, claims, summary).
- No `Accept` header (or anything that isn't `application/json`) →
`application/x-ndjson` streaming, one JSON object per line in this order:
`{"type": "envelope", "data": {...}}`, one `{"type": "claim", "data": {...}}`
per claim, then `{"type": "summary", "data": {...}}`.
Error responses are JSON of the form `{"error": "...", "detail": "..."}`:
- `400` — file-level parse error (`CycloneParseError`) or unknown `payer`
- `422` — at least one claim failed validation (body still includes the
full `ParseResult` so the client can render the errors)
- `500` — unexpected internal error
CORS is configured to allow `http://localhost:5173` with `GET` and `POST`
and any headers.
### Example: upload + JSON response
```bash
curl -X POST http://localhost:8000/api/parse-837 \
-F "file=@./samples/co_837p.txt" \
-H "Accept: application/json"
```
```json
{
"envelope": { "sender_id": "...", "receiver_id": "...", ... },
"claims": [ { "claim_id": "C1", ... }, { "claim_id": "C2", ... } ],
"summary": { "total_claims": 2, "passed": 2, "failed": 0, ... }
}
```
### Example: stream NDJSON from the browser
```js
const resp = await fetch("http://localhost:8000/api/parse-837", {
method: "POST",
body: formData, // FormData with a "file" entry
});
const reader = resp.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
for (const line of buffer.split("\n")) {
if (!line) continue;
const evt = JSON.parse(line);
if (evt.type === "claim") renderClaim(evt.data);
else if (evt.type === "summary") showSummary(evt.data);
}
buffer = buffer.slice(buffer.lastIndexOf("\n") + 1);
}
```
## Spec & plan
- Design: `docs/superpowers/specs/2026-06-19-cyclone-837p-parser-design.md`