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:
Nora
2026-06-25 00:26:13 -06:00
parent 1381a7652d
commit 6507a8c874
9 changed files with 399 additions and 17 deletions
+26
View File
@@ -0,0 +1,26 @@
import { useQuery } from "@tanstack/react-query";
import { api, type PaginatedResponse } from "@/lib/api";
import type { Ta1Ack } from "@/types";
/**
* Lists persisted TA1 (Interchange Acknowledgment) rows, newest
* first. Mirrors `useAcks` but for the lower-level envelope ack.
*
* A TA1 is one row per inbound ISA/IEA interchange — distinct from
* a 999, which is per-batch. Colorado Medicaid's Gainwell MFT
* currently only ships 999s in the FromHPE path, but historically
* they've sent TA1s, so the hook stays in the surface for when
* they reappear.
*
* No in-memory fallback: there is no zustand sample-data path for
* TA1s in v1. The hook is `enabled: api.isConfigured` so the page
* treats an empty list as "no TA1s on file" rather than a
* configuration error.
*/
export function useTa1Acks(params: { limit?: number } = {}) {
return useQuery<PaginatedResponse<Ta1Ack>>({
queryKey: ["ta1-acks", params],
queryFn: () => api.listTa1Acks(params),
enabled: api.isConfigured,
});
}