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).
This commit is contained in:
Nora
2026-07-08 03:43:57 -06:00
parent 61e90b64b3
commit 23fc85cad2
4 changed files with 783 additions and 205 deletions
-205
View File
@@ -1,205 +0,0 @@
"""SP41-goal: prove the spot-check driver's validate_edi code path
works against the Edifabric /v2/x12/validate OperationResult shape.
The live Edifabric API is quota-blocked (resets next UTC midnight);
until then we can't make real network calls. This test wires the
shipped ``cyclone.edifabric`` MockTransport seam to return a clean
``Status: success`` OperationResult for each of the 10 spot-check
files and asserts the driver's ``passed()`` classifier returns True.
This pins the end-to-end driver path against the real
``cyclone.edifabric.validate_edi`` client — without using a network
call. The test exercises:
1. validate_edi → OperationResult with Status='success' → passed=True
2. validate_edi → OperationResult with Status='warning' → passed=True
3. validate_edi → OperationResult with Status='error' → passed=False
4. validate_edi → 403 quota → EdifabricError raised (caught by driver)
Each scenario runs against the actual 837P files produced by the
driver in this session (loaded from the scratch dir).
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
import httpx
import pytest
# Make the spot-check driver importable.
SCRATCH = Path("/tmp/grok-goal-4336f8e1af6d/implementer")
sys.path.insert(0, str(SCRATCH))
import cyclone.edifabric as edifabric # noqa: E402
import cyclone.secrets as _secrets # noqa: E402
_REAL_GET_SECRET = _secrets.get_secret
_TEST_KEY = "3ecf6b1c5cf34bd797a5f4c57951a1cf"
_X12_INTERCHANGE = {
"SegmentDelimiter": "~",
"DataElementDelimiter": "*",
"ISA": {"InterchangeControlNumber_13": "000000001"},
"Groups": [],
"IEATrailers": [],
}
def _mock_secrets(patched_key: str):
def _fake(name: str):
if name == "edifabric.api_key":
return patched_key
return _REAL_GET_SECRET(name)
_secrets.get_secret = _fake # type: ignore[assignment]
def _install(handler):
transport = httpx.MockTransport(handler)
edifabric.set_transport_factory(lambda: httpx.Client(transport=transport, timeout=10.0))
@pytest.fixture(autouse=True)
def _restore():
yield
edifabric._reset_transport_factory()
_secrets.get_secret = _REAL_GET_SECRET # type: ignore[assignment]
def _first_spot_file() -> Path:
"""Return the first spot-check 837P file from the scratch dir.
Skip if no driver run has produced files yet (the live driver
writes them; this test reads them).
"""
files = sorted((SCRATCH / "spot-check-837Ps").glob("*.x12"))
if not files:
pytest.skip("no spot-check 837P files in scratch dir — run build_and_validate.py first")
return files[0]
@pytest.fixture
def spot_file() -> Path:
return _first_spot_file()
def _make_handler(operation_result: dict):
def handler(request: httpx.Request) -> httpx.Response:
if request.url.path.endswith("/read"):
return httpx.Response(200, json=[_X12_INTERCHANGE])
if request.url.path.endswith("/validate"):
return httpx.Response(200, json=operation_result)
raise AssertionError(f"unexpected path: {request.url.path}")
return handler
# --- The 4 scenarios --------------------------------------------------- #
def test_driver_passed_classifier_on_success_status(spot_file: Path):
"""Status='success' → driver.passed() returns (True, 'success')."""
from build_and_validate import passed # type: ignore
_mock_secrets(_TEST_KEY)
_install(_make_handler({"Status": "success", "Details": [], "LastIndex": 46}))
body = spot_file.read_bytes()
result = edifabric.validate_edi(body)
ok, msg = passed(result)
assert ok is True
assert msg == "success"
def test_driver_passed_classifier_on_warning_status(spot_file: Path):
"""Status='warning' → driver.passed() returns (True, 'warning').
Edifabric 'warning' is advisory (deprecation / best-practice
notices) and does NOT block a structurally valid envelope per the
SP41 contract.
"""
from build_and_validate import passed # type: ignore
_mock_secrets(_TEST_KEY)
_install(_make_handler({
"Status": "warning",
"Details": [{
"Index": 12,
"SegmentId": "CLM",
"Value": "CLM*R20260205-Q944140-01*333.62***12:B:1*Y*A*Y*Y",
"Message": "Best practice advisory: use place-of-service code 11",
"Status": "warning",
}],
"LastIndex": 46,
}))
body = spot_file.read_bytes()
result = edifabric.validate_edi(body)
ok, msg = passed(result)
assert ok is True
assert msg == "warning"
def test_driver_passed_classifier_on_error_status(spot_file: Path):
"""Status='error' → driver.passed() returns (False, error details)."""
from build_and_validate import passed # type: ignore
_mock_secrets(_TEST_KEY)
_install(_make_handler({
"Status": "error",
"Details": [{
"Index": 5,
"SegmentId": "CLM",
"Value": "CLM*X",
"Message": "Segment CLM is missing required data elements",
"Status": "error",
}],
"LastIndex": 5,
}))
body = spot_file.read_bytes()
result = edifabric.validate_edi(body)
ok, msg = passed(result)
assert ok is False
assert "missing required data elements" in msg
def test_driver_passed_classifier_on_403_quota(spot_file: Path):
"""HTTP 403 quota → EdifabricError raised (driver falls back to pyX12)."""
def handler(request: httpx.Request) -> httpx.Response:
if request.url.path.endswith("/read"):
return httpx.Response(
403,
json={"statusCode": 403,
"message": "Out of call volume quota."},
)
raise AssertionError(f"unexpected path: {request.url.path}")
_mock_secrets(_TEST_KEY)
_install(handler)
body = spot_file.read_bytes()
with pytest.raises(edifabric.EdifabricError) as exc_info:
edifabric.validate_edi(body)
assert exc_info.value.status_code == 403
def test_driver_emits_correct_validate_edi_call(spot_file: Path):
"""The driver calls validate_edi (read+validate) — pin the wire shape.
Validates the read path returns the X12Interchange shape and the
validate path receives that same shape (not the raw bytes).
"""
captured_validates: list[dict] = []
def handler(request: httpx.Request) -> httpx.Response:
if request.url.path.endswith("/read"):
return httpx.Response(200, json=[_X12_INTERCHANGE])
if request.url.path.endswith("/validate"):
captured_validates.append(json.loads(request.content))
return httpx.Response(200, json={"Status": "success", "Details": [], "LastIndex": 46})
raise AssertionError(f"unexpected path: {request.url.path}")
_mock_secrets(_TEST_KEY)
_install(handler)
body = spot_file.read_bytes()
result = edifabric.validate_edi(body)
assert result["Status"] == "success"
assert len(captured_validates) == 1
# The validate body is the X12Interchange, not the raw bytes.
assert captured_validates[0]["ISA"]["InterchangeControlNumber_13"] == "000000001"