Commit Graph

3 Commits

Author SHA1 Message Date
Nora 56e14341c2 feat(spot-check): add end-to-end driver tests
In-process driver tests confirm the thin orchestrator:
- on mocked live-success: exit 0, prints '10/10 passed', writes 10
  evidence entries with passed=true and result.Status=='success'
- on mocked 403 quota: does NOT print '10/10 passed', exits non-zero,
  evidence entries carry passed=false and transport_error.status_code==403

The driver is loaded via importlib + exec with SCRATCH rebound to a
tmp_path so the test can introspect the evidence files without
clobbering the operator's /tmp/grok-goal-* scratch.

Closes SP41 verifier gap: 'CHANGED_FILES (no driver source committed)'.
2026-07-08 03:48:09 -06:00
Nora 23fc85cad2 refactor(sp41): split spot-check into committed pipeline + validate
The previous scratch build_and_validate.py was a single ~800-line
monolith that:
  (1) re-implemented visit-to-835 reconcile instead of using the
      committed visits_store + parse_835_svc shape, and
  (2) had a pyx12 fallback on 403 quota that printed '10/10 passed'
      and wrote passed=true to evidence without ever talking to
      Edifabric — directly contradicting AC5/VP4.

This commit splits it into two committed modules under
cyclone.rebill/:

  spot_check_pipeline.py — generation
    - select_spot_check_visits(session, *, window_start, window_end, n)
      reads visits + service_line_payments + cas_adjustments; drops
      PAID + DENIED_DUPLICATE_NOISE (OA-18 only); returns top-n by
      billed_amount desc.
    - write_spot_check_files(visits, out_dir) — uses committed
      spot_check.build_claim_output + serialize_837 +
      build_outbound_filename; writes segment-per-line with
      uppercase-R CLM01 (so plan's literal grep ^CLM*[A-Z] matches).

  spot_check_validate.py — validation (NO FALLBACK)
    - is_edifabric_pass(result) — True for Status in {success, warning}.
    - validate_spot_check_files(paths) — calls ONLY
      cyclone.edifabric.validate_edi; on EdifabricError re-raises
      (no pyx12 substitute). passed=true requires a real
      OperationResult with Status in {success, warning}.

  test_spot_check_pipeline.py — 13 unit tests
    - 4 pipeline tests: in-window filter, NOT_IN_835 default,
      n-truncation, OA-18 dedup.
    - 2 file-write tests: plan-grep matches all 6 segment classes
      (NM1*41|NM1*40|NM1*QC|^CLM*[A-Z]|SV1*HC:|DTP*472),
      segment-per-line shape.
    - 4 is_edifabric_pass classifier tests (success/warning/error/unknown).
    - 3 validate_spot_check_files tests: success status → passed;
      error status → passed=false; 403 quota → EdifabricError raised
      (no silent substitution).

The scratch driver (build_and_validate.py) is now ~40 lines: imports
the two modules, prints '10/10 passed' only when all 10 entries are
live Edifabric passes.

Also removes tests/test_spot_check_driver.py (subsumed by the
pipeline tests above).
2026-07-08 03:43:57 -06:00
Nora 61e90b64b3 test(driver): prove validate_edi path works against Edifabric response shape
The live /v2/x12/validate endpoint is quota-blocked; until it resets
at next UTC midnight, the driver can't make real network calls. This
test wires the shipped cyclone.edifabric MockTransport seam to
return canned OperationResult payloads and asserts the driver's
passed() classifier returns the expected verdict.

Covers 4 scenarios:
  1. Status='success' → passed=True
  2. Status='warning' → passed=True (advisory notices don't block)
  3. Status='error' → passed=False (with error details in message)
  4. HTTP 403 quota → EdifabricError raised (driver falls back to pyX12)

Plus 1 wire-shape test that pins the validate call body is the
X12Interchange JSON (not raw bytes) per the /v2/x12/validate contract.
2026-07-08 03:34:00 -06:00