Files
cyclone/backend/README.md
T

4.5 KiB

Cyclone Backend — X12 837P Parser

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 via a FastAPI service (see REST API below).

Install

cd backend
python -m venv .venv
. .venv/bin/activate
pip install -e ".[dev]"

CLI

python -m cyclone.cli parse-837 path/to/837p.txt --output-dir ./claims

Options:

  • --payer {co_medicaid,generic_837p} — default co_medicaid
  • --strict — promote warnings to errors
  • --max-retries N — stub in v1 (no auto-fix yet)
  • --include-raw-segments / --no-raw-segments — default: include
  • --log-level {DEBUG,INFO,WARNING,ERROR}

Exit codes:

  • 0 — every claim parsed
  • 2 — file-level failure (no envelope, no claims)
  • 1 — unexpected exception

Output

claims/
├── claim-991102977-20260611-CLM001.json   # one per claim
├── claim-991102977-20260611-CLM002.json
└── summary.json                            # batch summary

Programmatic use

from pathlib import Path
from cyclone.parsers.parse_837 import parse
from cyclone.parsers.payer import PayerConfig
from cyclone.parsers.writer import write_outputs

result = parse(Path("input.txt").read_text(), PayerConfig.co_medicaid())
write_outputs(result, Path("./claims"))
print(f"passed={result.summary.passed} failed={result.summary.failed}")

Tests

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

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:

  • payerco_medicaid (default) or generic_837p
  • include_raw_segmentstrue (default) or false
  • strictfalse (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

curl -X POST http://localhost:8000/api/parse-837 \
  -F "file=@./samples/co_837p.txt" \
  -H "Accept: application/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

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
  • Plan: docs/superpowers/plans/2026-06-19-cyclone-837p-parser.md