INSCLM/i is a health / insurance claims-adjudication application: claim intake with
duplicate detection, member-eligibility checking, benefit calculation (network-rate allowed amount,
annual deductible, plan/member coinsurance split, out-of-pocket-maximum cap), pro-rata allocation down
to claim lines, member-accumulator carry-forward, GL posting and payment/remittance generation, and a
sanctioned payment-void path. It is a pure SQL PL application: every business rule lives in DB2 for
i compound functions, stored procedures and triggers — there is no 5250 screen and no RPG
business logic. The routines are driven from STRSQL (interactive CALL), from a batch
CALL of SP_ADJUDICATE_BATCH, and — to prove the reach path only —
from a thin SQLRPGLE caller. This manual is the reference for the operator who runs the adjudication cycle
and for the developer maintaining the routines. It is grounded entirely in the committed source
(sqlpl-app-claims/src/schema.sql, src/routines.sql, src/seed.mjs,
and the test/hc_sqlpl_daily.mjs / test/hc_sqlpl_volume_sim.mjs drivers).
INSCLM/i adjudicates a health-insurance claim from intake to payment:
E). A BEFORE-INSERT trigger stamps a duplicate-detection hash
(member+provider+service-date+billed) and, if a matching claim already exists, pends the new one
(P/D1) so it can never be double-paid; an inbound row masquerading as an
already-adjudicated duplicate is vetoed outright.D2 termed, D3 not yet effective, D4 suspended, D1
not found).SP_VOID_PAYMENT, which flips
the payment to void and posts a negative GL reversal; a direct edit of the remittance view is
rejected or rerouted through that procedure.Unlike a mixed RPG/SQL-PL application, INSCLM/i has no orchestration layer and no display file. Every
rule lives in routines.sql as DB2 for i SQL PL:
FN_*) — the pure arithmetic primitives: the duplicate
hash, the network-rate allowed amount, the deductible-apply and the OOP-cap. Kept as functions (not
inlined) so the same logic is reused by procedures, triggers and ad-hoc STRSQL.SP_*) — the adjudication engine
(SP_ADJUDICATE_CLAIM), its nested helpers (SP_ELIGIBILITY,
SP_PRORATE_LINES, SP_POST_GL, SP_GEN_PAYMENT), the batch
driver (SP_ADJUDICATE_BATCH) and the void path (SP_VOID_PAYMENT).TR_*) — genuine trigger-driven state: intake duplicate
pend/veto, the member-accumulator carry-forward (AFTER UPDATE OF CSTAT), and the remittance view's
INSTEAD OF UPDATE guard.The RPG layer is present only as a reach proof: the daily test compiles a tiny SQLRPGLE program
(CALLADJ) whose whole job is one EXEC SQL CALL SP_ADJUDICATE_CLAIM(…) and
one EXEC SQL CALL SP_VOID_PAYMENT(…), DSPLYing the host-variable round trip. No claim
math is coded in RPG. Everything runs in library INSCLM.
CASE…END CASE; GL posting and payment generation split into their own procedures;
the HCCLMTMP staging twin of HCCLMRUN; a caller-assigned ASEQ
instead of GENERATED ALWAYS AS IDENTITY) are documented in the source as deliberate work-arounds for
confirmed platform bugs (SQLPL-PLAT-CLM-01/03/04/05 and SQLPL-PLAT-03 in the engine's
SQLPL-SIM-FINDINGS ledger), not application design preferences. The business logic is identical either
way; the manual notes them where they affect how the operator reads a result. INTAKE ADJUDICATE (per claim) BATCH / VOID
------ --------------------- ------------
INSERT HCCLMH -----> SP_ADJUDICATE_CLAIM SP_ADJUDICATE_BATCH
TR_HCCLMH_DUP 1 line-sum edit (E2) cursor over CSTAT='E'
(pend D1 / veto) 2 SP_ELIGIBILITY (nested CALL) -> SP_ADJUDICATE_CLAIM
INSERT HCCLML 3 dup re-check (FN_DUPHASH) per claim
4 FN_ALLOWED (network rate) WITH RETURN result set
5 FN_APPLY_DEDUCTIBLE (HCCLMRUN via HCCLMTMP)
6 coinsurance split
7 FN_APPLY_OOPCAP SP_VOID_PAYMENT
8 UPDATE HCCLMH --fires--> flip HCPAY -> V
TR_HCCLMH_ACCUM (HCMEM accum) post -LAMT to HCGL
SP_PRORATE_LINES (nested, cursor)
SP_POST_GL (nested) HCREMIT view:
SP_GEN_PAYMENT (nested) TR_HCREMIT_IOU reroutes
a void-style UPDATE
HCCLMH <--writes-- every procedure triggers: TR_HCCLMH_DUP / _ACCUM (HCCLMH)
| \ TR_HCREMIT_IOU (HCREMIT view)
| +--> HCCLML (prorated lines, sum back exactly)
| +--> HCGL (append-only ledger: E plan-expense, M member, V reversal)
| +--> HCPAY (one payment per paid claim) --view--> HCREMIT
| +--> HCAUD (trigger-written accumulator audit)
+------> HCMEM (DEDMET / OOPMET accumulators, carried forward)
A single claim flows: INSERT header (the duplicate trigger stamps/pends it) and lines
→ CALL SP_ADJUDICATE_CLAIM (or let the batch pick it up) → the eight-step engine
computes and persists the header, prorates the lines, and the CSTAT→'A' update fires the
accumulator trigger → GL rows and a payment are generated. Balances and accumulators are only ever
moved by the sanctioned procedures and triggers.
| Object | Type | Role |
|---|---|---|
| HCPLAN | Table | Plan master (deductible, coinsurance, network rates, OOP max). |
| HCPROV | Table | Provider master (in-network flag, contract rate). |
| HCMEM | Table | Member master + benefit-year accumulators (the heart of state). |
| HCCLMH | Table | Claim header (the adjudication subject). |
| HCCLML | Table | Claim lines (prorated allocations). |
| HCPAY | Table | Payment / remittance (one per paid claim). |
| HCGL | Table | Append-only GL ledger. |
| HCAUD | Table | Generic trigger audit log. |
| HCCLMRUN | Table | Batch per-run outcome list (result-set source). |
| HCCLMTMP | Table | Staging twin of HCCLMRUN (platform work-around). |
| HCREMIT | View | Remittance view over HCPAY (INSTEAD OF UPDATE guard). |
| FN_* (4) | SQL functions | Dup hash, allowed amount, deductible-apply, OOP cap. |
| SP_* (7) | SQL procedures | Eligibility / prorate / adjudicate / GL post / payment / batch / void. |
| TR_* (3) | Triggers | Intake dup pend/veto, accumulator carry-forward, remittance guard. |
| CALLADJ | SQLRPGLE | Reach-proof caller (test-built; no business logic). |
The full catalogue is 4 functions + 7 procedures + 3 triggers, over 9 tables and 1 view, driven from STRSQL / a batch CALL and reached (for proof) by one SQLRPGLE program. Sections D and F expand each.
Honest statement: INSCLM/i has no display file and no interactive 5250 program. It is a
pure SQL PL application; the "user interface" is the SQL CALL statement issued from interactive
SQL (STRSQL), from an ad-hoc RUNSQL/JDBC session, or from a batch job. The
operator equivalent of "open a screen" is "enter a CALL and press Enter" in STRSQL; the job's
library list must include INSCLM — the tested jobs run with
LIBL = QSYS QGPL INSCLM QTEMP and CURLIB = INSCLM.
| To do this | Enter in STRSQL (or a batch CALL) |
|---|---|
| Enter a claim | INSERT INTO INSCLM/HCCLMH VALUES (…) then its INSCLM/HCCLML line rows |
| Adjudicate one claim | CALL INSCLM/SP_ADJUDICATE_CLAIM('CLM0000001', ?, ?, ?, ?) |
| Run the whole adjudication cycle | CALL INSCLM/SP_ADJUDICATE_BATCH(?, ?, ?) |
| Check member eligibility in isolation | CALL INSCLM/SP_ELIGIBILITY('MB0000001', 20260715, ?, ?) |
| Void a payment | CALL INSCLM/SP_VOID_PAYMENT('PY0000001') |
| Evaluate a benefit primitive ad hoc | SELECT FN_ALLOWED(800.00,'Y',1.0000,1.0000) FROM SYSIBM.SYSDUMMY1 |
| Review results | SELECT * FROM INSCLM/HCCLMH, …/HCPAY, …/HCGL |
The functions are callable from a bare SELECT … FROM SYSIBM.SYSDUMMY1 — the
daily test exercises each in isolation exactly this way (e.g.
FN_APPLY_OOPCAP(1600.00, 2000.00, 760.00) returns 1240.00). The OUT
parameters of SP_ADJUDICATE_CLAIM (status, hold code, plan liability, member responsibility)
come back on the CALL so a caller gets the full result without re-querying; SP_ADJUDICATE_BATCH
additionally surfaces a WITH RETURN result set (one row per adjudicated claim) that STRSQL displays.
To prove the platform's SQLRPGLE→SQL-PL bridge works, the daily test compiles a minimal SQLRPGLE
program, CALLADJ, that does nothing but the host-variable round trip:
dcl-s clmno char(10); dcl-s cstat char(1); dcl-s holdcd char(2); dcl-s planlia packed(9:2); dcl-s membresp packed(9:2); clmno = 'CLM0000005'; exec sql call SP_ADJUDICATE_CLAIM(:clmno, :cstat, :holdcd, :planlia, :membresp); dsply ('R1=' + cstat + '/' + holdcd + '/' + %char(sqlcode)); -> R1=D/D2/0 exec sql call SP_VOID_PAYMENT('PYNOSUCH02'); dsply ('R2=' + %char(sqlcode)); -> R2=-438 (unhandled SIGNAL)
An in-range business result surfaces as SQLCODE 0; an unhandled SIGNAL raised
by a procedure surfaces to the RPG caller as SQLCODE -438. No claim logic lives in this
program — it is a reach proof, and operationally you drive the app from STRSQL/batch as above.
Honest statement: INSCLM/i does not model a true four-eyes maker–checker / separate-authorization workflow. A claim entered and adjudicated is paid in one flow; there is no "one user adjudicates, a second approves" gate in the code. The control model the application does have is enforced entirely in the data layer:
CSTAT='P',
HOLDCD='D1') so the batch never picks it up, or vetoes (SIGNAL 75020)
an inbound row that claims to be an already-adjudicated duplicate. SP_ADJUDICATE_CLAIM
re-checks duplicates at adjudicate time as a belt-and-braces second guard.SP_ELIGIBILITY denies out-of-window / suspended / unknown
members before any money is computed (hold codes D1–D4).E2 before eligibility is even checked.ACCUM audit row
(TR_HCCLMH_ACCUM) recording the deductible and member responsibility folded into the
member — the after-the-fact accountability trail.LSEQ; nothing is updated in place, so the financial history
is fully reconstructable.TR_HCREMIT_IOU (INSTEAD OF UPDATE on
HCREMIT) rejects a direct edit of a remittance amount (SIGNAL 75021)
and reroutes a void-style update through SP_VOID_PAYMENT — a payment amount can
change only through the sanctioned procedure.In sum, the control posture is duplicate/eligibility/intake gating + trigger-written audit + append-only ledger + channel gating, all in the data layer, rather than a segregation-of-duties approval workflow.
INSCLM/i's "batch cycle" is a single driver, SP_ADJUDICATE_BATCH, run the way a real IBM i
shop runs it: once per business day against whatever claims were entered that day. It cursor-loops every
claim still in status E (entered), calls SP_ADJUDICATE_CLAIM for each, and
surfaces a WITH RETURN result set of the per-claim outcome. There is no control-row date to advance
— the driver simply picks up all CSTAT='E' work, so a bare CALL is the whole
submission.
-- enter the day's claims (header then lines) ... INSERT INTO INSCLM/HCCLMH VALUES ('CLM0000001','MB0000001','PRV000001','PLAN0001', 20260715, 800.00, 0,0,0,0, ' ', 'E', ' ', ''); INSERT INTO INSCLM/HCCLML VALUES ('CLM0000001', 1, 600.00, 0,0,0), ('CLM0000001', 2, 200.00, 0,0,0); -- ... then run the (parameterless-input) cycle CALL INSCLM/SP_ADJUDICATE_BATCH(?, ?, ?); -- OUT paid/denied/pended + a result set -- or as a scheduled batch job: SBMJOB CMD(RUNSQL SQL('CALL INSCLM/SP_ADJUDICATE_BATCH(?,?,?)')) JOB(HCADJ)
OUT counters
(P_PAID/P_DENIED/P_PENDED) are documented in the source as
not trustworthy: confirmed platform bug SQLPL-PLAT-CLM-04 zeroes them merely because the
WITH RETURN result-set cursor is declared alongside the working loop cursor. The loop's
side effects (adjudication, GL, payments, accumulators) all land correctly; verify outcomes by
re-querying HCCLMH (counts by CSTAT) rather than trusting the OUT
parameters. This is why the runbook's post-checks (section E) query the tables, not the counters.SP_ADJUDICATE_BATCH opens a cursor over HCCLMH WHERE CSTAT='E' ORDER BY CLMNO
and, for each claim, calls SP_ADJUDICATE_CLAIM, accumulating the per-claim outcome into the
HCCLMTMP staging table during the loop. After the loop's cursor is closed it swaps the staging
rows into HCCLMRUN (DELETE-then-INSERT) and opens the WITH RETURN cursor over
HCCLMRUN. Each SP_ADJUDICATE_CLAIM runs the eight-step engine:
D/E2 and returns immediately.CALL SP_ELIGIBILITY; ineligible denies
D/D2|D3|D4|D1.FN_DUPHASH against other CSTAT='A' claims;
a match pends P/D1.FN_ALLOWED (provider contract rate in-network, else the
plan's out-of-network rate).FN_APPLY_DEDUCTIBLE from the member's remaining deductible.FN_APPLY_OOPCAP caps the member's total responsibility
(deductible + coinsurance) at the OOP room remaining; the shaved amount flows back to plan liability.TR_HCCLMH_ACCUM to move the member
accumulator), then nested SP_PRORATE_LINES, SP_POST_GL,
SP_GEN_PAYMENT.Worked example (the daily-test seed, plan GOLD, member MB0000001, ded met 0):
CLAIM 1 billed 800.00 allowed 800.00 ded 500.00 plan 240.00 member 560.00 [A]
CLAIM 2 billed 1000.00 allowed 1000.00 ded 0.00 plan 800.00 member 200.00 [A] ded carried fwd
CLAIM 3 billed 8000.00 allowed 8000.00 ded 0.00 plan 6760.00 member 1240.00 [A] OOP cap bites
CLAIM 4 OON billed 500.00 allowed 250.00 (0.5000) ded 250.00 plan 0.00 mbr 250 [A]
CLAIM 5 termed member, svc 20260715 > term 20260630 [D/D2]
CLAIM 6 header 400.00 but lines sum 250.00 [D/E2]
| Routine | Kind | Purpose | Calls / fires | Frequency |
|---|---|---|---|---|
| SP_ADJUDICATE_BATCH | Proc (RS) | Cursor-loop every E claim; return per-claim outcomes. | SP_ADJUDICATE_CLAIM; HCCLMTMP→HCCLMRUN swap. | Per adjudication run (e.g. daily). |
| SP_ADJUDICATE_CLAIM | Proc | The 8-step engine for one claim. | SP_ELIGIBILITY, SP_PRORATE_LINES, SP_POST_GL, SP_GEN_PAYMENT; FN_*; fires TR_HCCLMH_ACCUM. | Per claim (or ad hoc CALL). |
| SP_ELIGIBILITY | Proc | Member exists / active / in coverage window. | Reads HCMEM; NOT FOUND handler. | Per claim (nested). |
| SP_PRORATE_LINES | Proc | Prorate header allocations to lines; last line absorbs residue. | Cursor over HCCLML; UPDATE HCCLML. | Per paid claim (nested). |
| SP_POST_GL | Proc | Post plan-expense (E) + member-contra (M) GL rows, idempotently. | INSERT HCGL (guarded by NOT EXISTS on LREF). | Per paid claim (nested). |
| SP_GEN_PAYMENT | Proc | Generate one PAYNO per claim, idempotently. | INSERT HCPAY; UPDATE HCCLMH.PAYNO. | Per paid claim (nested). |
| SP_VOID_PAYMENT | Proc | The only sanctioned void: flip payment to V, post -LAMT reversal. | UPDATE HCPAY; INSERT HCGL (V); SIGNAL 75010/75011. | On demand. |
| TR_HCCLMH_DUP | Trigger | BEFORE INSERT: stamp DUPHASH, pend a duplicate (D1) or veto (75020). | FN_DUPHASH; SIGNAL 75020. | Every claim insert. |
| TR_HCCLMH_ACCUM | Trigger | AFTER UPDATE OF CSTAT into 'A': carry DEDAPPL/MEMBRESP into HCMEM; audit. | UPDATE HCMEM; INSERT HCAUD. | Every claim that pays. |
| TR_HCREMIT_IOU | Trigger | INSTEAD OF UPDATE on HCREMIT: reject amount edit (75021) / reroute void. | SP_VOID_PAYMENT; SIGNAL 75021. | Every remittance-view update. |
SP_ADJUDICATE_BATCH runs — the intake edit (step 1) reads HCCLML, and
the duplicate trigger fires on the header INSERT.E claims are picked up. The batch cursor selects CSTAT='E', so
pended (P) and already-adjudicated (A/D) claims are skipped
— this is what makes a re-run a no-op.All tables are in library INSCLM, grounded in schema.sql. Dates are stored as
INT in YYYYMMDD form (99999999 = no termination / active); rates and
coinsurance shares are DECIMAL(5,4) fractions (0.8000 = 80%); money is DECIMAL(9,2).
| Field | Type | Meaning |
|---|---|---|
| PLANNO | CHAR(8) | Plan number (PK), e.g. PLAN0001. |
| PNAME | VARCHAR(20) | Plan name (GOLD, SILVER). |
| DEDUCT | DECIMAL(9,2) | Annual deductible. |
| COINS | DECIMAL(5,4) | Plan's coinsurance share after deductible (0.8000 = plan pays 80%). |
| INNET | DECIMAL(5,4) | In-network allowed-amount rate applied to billed. |
| OUTNET | DECIMAL(5,4) | Out-of-network allowed-amount rate applied to billed. |
| OOPMAX | DECIMAL(9,2) | Out-of-pocket maximum (member-share cap). |
Seed: PLAN0001 GOLD (deduct 500.00, coins .8000, OOP max 2000.00); PLAN0002 SILVER (deduct 1000.00, coins .7000, OUTNET .5000, OOP max 4000.00).
| Field | Type | Meaning |
|---|---|---|
| PROVNO | CHAR(9) | Provider number (PK). |
| PNAME | VARCHAR(24) | Provider name. |
| INNETW | CHAR(1) | Y in-network, N out-of-network. |
| CTRRATE | DECIMAL(5,4) | Contract rate applied to billed when in-network. |
Seed: PRV000001 in-net rate 1.0000 (allowed = billed); PRV000003 out-of-network.
| Field | Type | Meaning |
|---|---|---|
| MEMBNO | CHAR(9) | Member number (PK). |
| MNAME | VARCHAR(24) | Member name. |
| PLANNO | CHAR(8) | Enrolled plan. |
| EFFDT | INT | Coverage effective date (YYYYMMDD). |
| TERMDT | INT | Coverage termination date (99999999 = active). |
| DEDMET | DECIMAL(9,2) | Accumulator: deductible met this benefit year. |
| OOPMET | DECIMAL(9,2) | Accumulator: out-of-pocket met this benefit year. |
| MSTAT | CHAR(1) | A active, S suspended. |
DEDMET/OOPMET are the carried-forward state driven by TR_HCCLMH_ACCUM; they
must survive claim-to-claim and across separate batch runs within a benefit year.
| Field | Type | Meaning |
|---|---|---|
| CLMNO | CHAR(10) | Claim number (PK). |
| MEMBNO / PROVNO / PLANNO | CHAR(9)/(9)/(8) | Member, provider, plan. |
| SVCDT | INT | Date of service (YYYYMMDD). |
| BILLED | DECIMAL(9,2) | Total billed (must equal the sum of lines). |
| ALLOWED | DECIMAL(9,2) | Total allowed post network rate. |
| DEDAPPL | DECIMAL(9,2) | Deductible applied on this claim. |
| PLANLIA | DECIMAL(9,2) | Plan liability (paid amount). |
| MEMBRESP | DECIMAL(9,2) | Member responsibility. |
| DUPHASH | CHAR(32) | MEMBNO|PROVNO|SVCDT|BILLED composite (stamped by trigger). |
| CSTAT | CHAR(1) | E entered, A adjudicated/paid, D denied, P pended (dup). |
| HOLDCD | CHAR(2) | ' ' none, D1 dup, D2 termed, D3 not-yet-effective, D4 suspended, E2 line/header mismatch. |
| PAYNO | CHAR(10) | Payment number, set once paid. |
| Field | Type | Meaning |
|---|---|---|
| CLMNO / CLINE | CHAR(10) / INT | Claim + line number (PK). |
| BILLED | DECIMAL(9,2) | Billed amount for this line (input). |
| ALLOWED | DECIMAL(9,2) | Prorated allowed for this line. |
| PLANLIA | DECIMAL(9,2) | Prorated plan liability for this line. |
| MEMBRESP | DECIMAL(9,2) | Prorated member responsibility for this line. |
The prorated ALLOWED/PLANLIA/MEMBRESP over all lines sum exactly back to the header (the last line absorbs the rounding residue).
| Field | Type | Meaning |
|---|---|---|
| PAYNO | CHAR(10) | Payment number (PK), PY + claim suffix. |
| CLMNO | CHAR(10) | Claim paid. |
| PAYDT | INT | Payment date (the claim's service date). |
| PAMT | DECIMAL(9,2) | Paid amount = claim PLANLIA. |
| PSTAT | CHAR(1) | A applied, V void. |
| Field | Type | Meaning |
|---|---|---|
| LSEQ | INT | Caller-assigned ordering/uniqueness key (PK), MAX(LSEQ)+1. |
| LTYPE | CHAR(1) | E plan-expense (+), M member-contra (informational +), V void-reversal (−). |
| LREF | CHAR(10) | Claim or payment number referenced. |
| MEMBNO | CHAR(9) | Member. |
| LAMT | DECIMAL(9,2) | Amount (negative for a V reversal). |
| LDATE | INT | Posting date. |
| Field | Type | Meaning |
|---|---|---|
| ASEQ | INT | Caller-assigned audit sequence (PK). See the note below on why it is not IDENTITY. |
| OP | CHAR(10) | Operation, e.g. ACCUM. |
| TNAME | CHAR(10) | Source table (HCCLMH). |
| KEYVAL | CHAR(12) | Key of the affected row (the claim number). |
| DETAIL | VARCHAR(80) | Human-readable detail (e.g. ded+500.00 oop+560.00). |
ASEQ is caller-assigned (MAX(ASEQ)+1 inside the trigger)
rather than GENERATED ALWAYS AS IDENTITY: the schema documents this as an application-level work-around for
confirmed platform bug SQLPL-PLAT-03 (the engine's identityRewrite() rejects the idiomatic Db2-for-i
IDENTITY + NOT NULL + separate PRIMARY KEY form). Not a design preference.Both carry CLMNO, CSTAT, HOLDCD, PLANLIA, MEMBRESP. SP_ADJUDICATE_BATCH
accumulates each claim's outcome into HCCLMTMP during its cursor loop, then — only after
the loop cursor is closed — swaps the contents into HCCLMRUN and opens the WITH RETURN
cursor over it. The staging twin exists solely to work around confirmed platform bug SQLPL-PLAT-CLM-05 (a
DELETE between a cursor's DECLARE and its OPEN corrupts that OPEN's materialization), documented in the
schema; it is not a modelling concept the operator needs to track.
CREATE VIEW INSCLM/HCREMIT AS SELECT PAYNO, CLMNO, PAYDT, PAMT, PSTAT FROM INSCLM/HCPAY.
Updatable only through TR_HCREMIT_IOU (INSTEAD OF UPDATE): a remit-amount edit is rejected
(75021); a void-style update (PSTAT → 'V') is rerouted through SP_VOID_PAYMENT.
Note: the schema declares primary keys but no explicit FOREIGN KEY constraints — referential integrity is maintained by the procedures/triggers, and the relationships above are the logical (not DDL-declared) keys.
INSCLM (LIBL = QSYS QGPL INSCLM QTEMP,
CURLIB = INSCLM).INSERT each header into INSCLM/HCCLMH (CSTAT
E) and its lines into INSCLM/HCCLML. The BEFORE trigger stamps the dup hash
and pends/vetoes duplicates at this point.CALL INSCLM/SP_ADJUDICATE_BATCH(?, ?, ?) (or SBMJOB a
RUNSQL of it).-- outcome by status SELECT CSTAT, COUNT(*) FROM INSCLM/HCCLMH GROUP BY CSTAT; -- any denials, with reason SELECT CLMNO, HOLDCD FROM INSCLM/HCCLMH WHERE CSTAT IN ('D','P') ORDER BY CLMNO; -- payments generated this run SELECT COUNT(*) FROM INSCLM/HCPAY WHERE PSTAT = 'A';
Post-checks after the cycle:
CSTAT='E' for a member that should have adjudicated (every entered claim
was reached by the loop).PSTAT='A' payment per paid (CSTAT='A') claim — no
double-pay.E and an M row per paid claim (2 rows each).P/D1) claims are duplicates and were never paid; denials carry the
expected hold code.Handle a payment reversal on demand with CALL INSCLM/SP_VOID_PAYMENT('<PAYNO>');
never edit HCPAY.PAMT or the HCREMIT view directly.
These are the same invariants the daily and volume drivers check against an independent hand-derived JS oracle. They are the operator's reconciliation checklist.
PLANLIA + MEMBRESP = ALLOWED on every paid claim
(e.g. 240.00 + 560.00 = 800.00 on claim 1).ALLOWED/PLANLIA/MEMBRESP
equals the header, exactly (claim 1: lines 600+200 allowed, 180+60 plan, 420+140 member).DEDAPPL = 0 once the deductible is met (claim 2: deductible met by claim 1).OOPMET never exceeds the plan OOPMAX; the
capping claim shifts the shaved amount to plan liability (claim 3: member capped at 1240.00, plan
absorbs 6760.00, OOPMET lands at exactly 2000.00).SUM(HCGL LAMT WHERE LTYPE='E') = SUM(HCCLMH.PLANLIA WHERE CSTAT='A')
and the member (M) rows tie to SUM(MEMBRESP). Daily seed: GL E total = 240+800+6760+0 =
7800.00.SUM(E) + SUM(V) = SUM(PLANLIA of claims
still ACTIVELY paid); the reversal row is −PLANLIA of the voided claim.-- reconcile the GL to paid claims SELECT (SELECT SUM(LAMT) FROM INSCLM/HCGL WHERE LTYPE='E') AS GL_PLAN_EXPENSE, (SELECT SUM(PLANLIA) FROM INSCLM/HCCLMH WHERE CSTAT='A') AS CLAIMS_PLANLIA FROM SYSIBM.SYSDUMMY1; -- a member's benefit-year accumulators SELECT MEMBNO, DEDMET, OOPMET FROM INSCLM/HCMEM WHERE MEMBNO = 'MB0000001';
| Situation | Behaviour | Action |
|---|---|---|
| Re-run the batch after it cleared | Cursor selects only CSTAT='E'; nothing to do. | Idempotent no-op. GL, payments and accumulators are all unchanged (proven across day/volume re-runs). Safe to resubmit. |
| Batch fails partway | Claims already flipped off E stay adjudicated; the rest remain E. | Re-submit: only the still-E claims are reprocessed. GL/payment inserts are guarded by NOT EXISTS, so no double-post. |
| OUT counters read as 0 | Confirmed platform bug SQLPL-PLAT-CLM-04 zeroes them when the WITH RETURN cursor is declared. | Ignore the counters; verify by querying HCCLMH by CSTAT. The side effects are correct. |
| Duplicate claim entered | BEFORE trigger pends it P/D1; it is never picked up by the batch. | Expected. The original is paid once; the duplicate stays pended and unpaid. |
| Inbound row claims to be an adjudicated duplicate | SIGNAL 75020 — the INSERT is rejected outright. | The row never lands. Correct the feed; a genuine new claim must arrive as E. |
| Line total ≠ header billed | Denied D/E2 before eligibility. | Fix the lines or header so they sum, re-enter as a new E claim. |
| Void an already-void payment | SIGNAL 75011 (not a silent no-op); no second reversal row. | Intended idempotency guard. The payment is already reversed. |
| Void a nonexistent payment | SIGNAL 75010. | Check the PAYNO; from RPG this surfaces as SQLCODE -438. |
| Direct edit of a remit amount | SIGNAL 75021 via the INSTEAD OF trigger; base table untouched. | Use SP_VOID_PAYMENT and reissue; never UPDATE HCREMIT/HCPAY by hand. |
HCGL row and every paying
adjudication writes an HCAUD accumulator row, any run's effect is fully reconstructable after
the fact for reconciliation and recovery.The complete SQL-PL surface, from routines.sql. All objects are in library
INSCLM. Signatures are given as declared.
MEMBNO || '-' || PROVNO || '-' || CHAR(SVCDT) || '-'
|| CHAR(INT(BILLED*100)). DETERMINISTIC. Kept as a function so the BEFORE trigger,
the adjudicate-time re-check and ad-hoc STRSQL all use identical logic.BILLED × (INNETW='Y' ? CTRRATE : OUTNET) via an IF ladder.
In-network uses the provider contract rate; out-of-network uses the plan's OUTNET rate.
DETERMINISTIC.MIN(ALLOWED, DEDUCT−DEDMET), floored at 0 (never
negative once met). DETERMINISTIC.ROOM = MAX(0, OOPMAX−OOPMET); returns
MIN(MEMBSHARE, ROOM). DETERMINISTIC.D1; MSTAT≠'A' → D4; svc < EFFDT → D3; svc >
TERMDT → D2; else eligible Y. Called nested from
SP_ADJUDICATE_CLAIM.DECIMAL(header × lineBilled / billedTot, 9, 2)); the last line absorbs the
residue so lines sum back to the header exactly. CONTINUE handler on NOT FOUND drives the fetch.TR_HCCLMH_ACCUM for the accumulator carry-forward — the
procedure does not touch DEDMET/OOPMET itself.NOT EXISTS (… LTYPE='E' AND LREF=P_CLMNO)): posts an
E plan-expense row and an M member-contra row, each at
MAX(LSEQ)+1/+2. Split out of the engine per SQLPL-PLAT-CLM-03.NOT EXISTS (… HCPAY WHERE CLMNO=P_CLMNO)): generates
PY+claim-suffix, inserts HCPAY (PSTAT 'A', PAMT = plan liability), stamps HCCLMH.PAYNO.
Also split out per SQLPL-PLAT-CLM-03.CSTAT='E'; nested CALL SP_ADJUDICATE_CLAIM per claim,
accumulating outcomes into HCCLMTMP; after CLOSE, swaps HCCLMTMP→HCCLMRUN and opens
the WITH RETURN cursor over HCCLMRUN. OUT counters are unreliable (SQLPL-PLAT-CLM-04) —
read outcomes from the result set / tables.SIGNAL 75010; already void →
SIGNAL 75011. Otherwise flips HCPAY.PSTAT='V' and posts a V
reversal GL row of −PAMT. Does not touch the member accumulator.N.DUPHASH = FN_DUPHASH(…). If an existing CSTAT='A'
duplicate exists and the inbound row is itself CSTAT='A' → SIGNAL
75020 (veto). Otherwise, if any duplicate in (A,P,E)
exists, pre-mark the inbound row CSTAT='P', HOLDCD='D1'. Always stamps
N.DUPHASH.UPDATE HCMEM SET DEDMET=DEDMET+N.DEDAPPL,
OOPMET=OOPMET+N.MEMBRESP WHERE MEMBNO=N.MEMBNO, then writes an ACCUM HCAUD row at
MAX(ASEQ)+1. Fires only on the transition into paid state, so re-running the batch cannot
double the accumulator.N.PAMT ≠ O.PAMT → SIGNAL 75021 (remit amount immutable). If
N.PSTAT='V' AND O.PSTAT≠'V' → CALL SP_VOID_PAYMENT(O.PAYNO). Ensures
remittance amounts move only through the sanctioned procedure.HCREMIT view: SELECT PAYNO, CLMNO, PAYDT, PAMT, PSTAT FROM INSCLM/HCPAY —
read-through, update-guarded by the trigger above.
FN_ALLOWED and MIN/MAX
clamps in FN_APPLY_DEDUCTIBLE/FN_APPLY_OOPCAP; all
DETERMINISTIC.DECLARE CONTINUE HANDLER FOR NOT FOUND SET V_FOUND=0 to
turn a missing row into a branch (eligibility, void).DECLARE C1 CURSOR… OPEN / FETCH / WHILE V_DONE=0 DO
… END WHILE / CLOSE, driven by a CONTINUE NOT FOUND handler (prorate, batch).SP_ADJUDICATE_CLAIM calls four sub-procedures;
SP_ADJUDICATE_BATCH nests a CALL inside a cursor loop.DECLARE RS CURSOR WITH RETURN FOR …; OPEN RS
under DYNAMIC RESULT SETS 1 to surface batch outcomes to STRSQL/CALL.E-only batch cursor make re-runs no-ops without a control flag.CASE…END CASE (CLM-01); GL/payment split into own procedures (CLM-03);
HCCLMTMP staging twin (CLM-05); caller-assigned ASEQ/LSEQ instead of IDENTITY (PLAT-03); OUT counters
treated as unreliable (CLM-04).| SQLSTATE | Raised by | Meaning |
|---|---|---|
| 75010 | SP_VOID_PAYMENT | Payment not found. |
| 75011 | SP_VOID_PAYMENT | Payment already voided (idempotency guard). |
| 75020 | TR_HCCLMH_DUP | Inbound row is an already-adjudicated duplicate — insert vetoed. |
| 75021 | TR_HCREMIT_IOU | Remit amount is immutable; void and reissue instead. |
The claim-level denials/pends use hold codes on the row
(HCCLMH.HOLDCD: D1–D4, E2) rather than SQLSTATE
— a denied or pended claim is a normal, recorded outcome, not an SQL exception. Only the guard
conditions above raise SQLSTATE. An unhandled SIGNAL surfaces to a SQLRPGLE caller as SQLCODE -438.
SIGNAL SQLSTATE '…'; SQLCODE 0 is
success, and an unhandled SIGNAL reaches an RPG caller as SQLCODE -438.CALL and SELECT statements (there is no 5250 screen).