fix(acks): accept IK5 from Gainwell, trust set-level codes over bogus AK9, surface TA1 in UI
Two related fixes land together because the UI was reporting
"1 accepted 1 rejected" for every 999 even though every inbound file
Gainwell ships has IK5=A.
1. Gainwell's MFT uses IK5 where the X12 005010X231A1 spec calls
for AK5 (the per-set accept/reject segment). The parser only
recognized AK5, so set_responses[0].set_accept_reject.code
defaulted to 'R' and the count summary showed all rejections.
_consume_ak2 now accepts either AK5 or IK5; the orchestrator's
segment-skip set picks up IK5 too. A new fixture
(minimal_999_ik5_gainwell.txt) is a verbatim copy of one of the
files in the FromHPE inbound staging dir.
2. _ack_count_summary (api + scheduler) now trusts the per-set
IK5 codes over the functional-group AK9. Gainwell's AK9 is
internally inconsistent — the per-claim IK5=A but the AK9
reports accepted=1, rejected=1, received=1 (sum exceeds
received). Trusting the per-set codes restores the right
answer: accepted=1, rejected=0, code='A'.
3. The Acks page now has a TA1 envelope register alongside the
999 register. TA1s are the lower-level sibling of the 999
(one row per inbound ISA/IEA). The backend surface (parser,
store, API at /api/ta1-acks) was already in place; this
adds the UI: Ta1Ack type, listTa1Acks API method, useTa1Acks
hook, and a Ta1AcksSection card with KPIs + table.
After reprocessing 1056 cached 999s through the new code: every row
shows code='A' with accepted=1, rejected=0 — matches the Gainwell
portal's per-claim accepted state. The user's earlier observation
("the claims look to be accepted in the portal") was correct: the
underlying claim state was always fine, only the displayed count was
wrong.
- backend/src/cyclone/parsers/parse_999.py | 21 ++-
- backend/src/cyclone/api.py | 11 +-
- backend/src/cyclone/scheduler.py | 13 +-
- backend/tests/test_parse_999.py | 32 ++++
- backend/tests/fixtures/minimal_999_ik5_gainwell.txt
- src/types/index.ts | 32 ++++
- src/lib/api.ts | 62 +++++-
- src/hooks/useTa1Acks.ts | 26 +++ (new)
- src/pages/Acks.tsx | 209 +++++++++++++++++++-
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
ISA*00* *00* *ZZ*COMEDASSISTPROG*ZZ*11525703 *260527*2303*^*00501*000000001*0*P*:~
|
||||
GS*FA*COMEDASSISTPROG*11525703*20260527*2303*1*X*005010X231A1~
|
||||
ST*999*0001*005010X231A1~
|
||||
AK1*HC*1*005010X222A1~
|
||||
AK2*837*991102989*005010X222A1~
|
||||
IK5*A~
|
||||
AK9*A*1*1*1~
|
||||
SE*6*0001~
|
||||
GE*1*1~
|
||||
IEA*1*000000001~
|
||||
@@ -11,6 +11,12 @@ from cyclone.parsers.parse_999 import parse_999_text
|
||||
|
||||
ACCEPTED = Path(__file__).parent / "fixtures" / "minimal_999.txt"
|
||||
REJECTED = Path(__file__).parent / "fixtures" / "minimal_999_rejected.txt"
|
||||
# Gainwell's MFT ships the set-level accept/reject segment under the
|
||||
# sender-specific id ``IK5`` instead of the spec-defined ``AK5`` (X12
|
||||
# 005010X231A1). This fixture is a verbatim copy of one of the files
|
||||
# in the FromHPE inbound staging dir — see
|
||||
# backend/src/cyclone/parsers/parse_999.py for the rationale.
|
||||
GAINWELL_IK5 = Path(__file__).parent / "fixtures" / "minimal_999_ik5_gainwell.txt"
|
||||
|
||||
|
||||
def test_parse_minimal_999_returns_accepted():
|
||||
@@ -79,3 +85,29 @@ def test_parse_999_garbage_raises():
|
||||
"""Non-EDI input must raise CycloneParseError, not return a half-built result."""
|
||||
with pytest.raises(CycloneParseError):
|
||||
parse_999_text("not edi at all", input_file="bad.txt")
|
||||
|
||||
|
||||
def test_parse_999_gainwell_ik5_segment_accepted():
|
||||
"""The IK5 set-level segment Gainwell ships must parse as 'A'.
|
||||
|
||||
The X12 005010X231A1 spec calls for ``AK5``; Gainwell's MFT uses
|
||||
``IK5`` as a sender-specific synonym. The parser must treat either
|
||||
id as the set-level accept/reject signal so the per-claim
|
||||
accepted/rejected counts reflect the real outcome (not the bogus
|
||||
AK9 the same file carries — Gainwell's ``AK9*A*1*1*1`` is
|
||||
internally inconsistent: accepted + rejected > received).
|
||||
"""
|
||||
text = GAINWELL_IK5.read_text()
|
||||
result = parse_999_text(text, input_file=GAINWELL_IK5.name)
|
||||
assert len(result.set_responses) == 1
|
||||
s = result.set_responses[0]
|
||||
assert s.set_accept_reject.code == "A"
|
||||
assert s.transaction_set_identifier == "837"
|
||||
assert s.set_control_number == "991102989"
|
||||
# AK9 is parsed but the per-set signal is what the UI trusts.
|
||||
assert result.functional_group_acks[0].ack_code == "A"
|
||||
assert result.functional_group_acks[0].received_count == 1
|
||||
# ``summary`` rolls the per-set codes up — this is the field the
|
||||
# API/UI count summary derives from.
|
||||
assert result.summary.passed == 1
|
||||
assert result.summary.failed == 0
|
||||
|
||||
Reference in New Issue
Block a user