INSCLM/i — Health-Claims Adjudication

SteelFrame X application operation manual  ·  ← back to Operation Manuals  ·  Sign On

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).

Contents

A. Overview & Architecture ↑ top

A.1 What it does

INSCLM/i adjudicates a health-insurance claim from intake to payment:

A.2 SQL-PL-only architecture: the data layer is the application

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:

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.

Several structural choices in the source (an IF/ELSEIF ladder rather than searched 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.

A.3 Component & flow

  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.

A.4 Object inventory

ObjectTypeRole
HCPLANTablePlan master (deductible, coinsurance, network rates, OOP max).
HCPROVTableProvider master (in-network flag, contract rate).
HCMEMTableMember master + benefit-year accumulators (the heart of state).
HCCLMHTableClaim header (the adjudication subject).
HCCLMLTableClaim lines (prorated allocations).
HCPAYTablePayment / remittance (one per paid claim).
HCGLTableAppend-only GL ledger.
HCAUDTableGeneric trigger audit log.
HCCLMRUNTableBatch per-run outcome list (result-set source).
HCCLMTMPTableStaging twin of HCCLMRUN (platform work-around).
HCREMITViewRemittance view over HCPAY (INSTEAD OF UPDATE guard).
FN_* (4)SQL functionsDup hash, allowed amount, deductible-apply, OOP cap.
SP_* (7)SQL proceduresEligibility / prorate / adjudicate / GL post / payment / batch / void.
TR_* (3)TriggersIntake dup pend/veto, accumulator carry-forward, remittance guard.
CALLADJSQLRPGLEReach-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.

B. Online / Access ↑ top

B.1 STRSQL CALL invocation (there is no 5250 screen)

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 thisEnter in STRSQL (or a batch CALL)
Enter a claimINSERT INTO INSCLM/HCCLMH VALUES (…) then its INSCLM/HCCLML line rows
Adjudicate one claimCALL INSCLM/SP_ADJUDICATE_CLAIM('CLM0000001', ?, ?, ?, ?)
Run the whole adjudication cycleCALL INSCLM/SP_ADJUDICATE_BATCH(?, ?, ?)
Check member eligibility in isolationCALL INSCLM/SP_ELIGIBILITY('MB0000001', 20260715, ?, ?)
Void a paymentCALL INSCLM/SP_VOID_PAYMENT('PY0000001')
Evaluate a benefit primitive ad hocSELECT FN_ALLOWED(800.00,'Y',1.0000,1.0000) FROM SYSIBM.SYSDUMMY1
Review resultsSELECT * 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.

SQLRPGLE reach path (proof only)

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.

B.2 Controls & audit workflow (no four-eyes maker–checker)

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:

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.

C. Batch / Cycle Procedures ↑ top

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)
Read the OUT counters with care. The three 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.

C.1 The adjudication cycle

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:

  1. Intake edit — the lines' billed amounts must sum to the header billed total; a mismatch denies the claim D/E2 and returns immediately.
  2. Eligibility — nested CALL SP_ELIGIBILITY; ineligible denies D/D2|D3|D4|D1.
  3. Duplicate re-checkFN_DUPHASH against other CSTAT='A' claims; a match pends P/D1.
  4. Allowed amountFN_ALLOWED (provider contract rate in-network, else the plan's out-of-network rate).
  5. DeductibleFN_APPLY_DEDUCTIBLE from the member's remaining deductible.
  6. Coinsurance split — plan share = post-deductible remainder × plan coinsurance; member share = the rest.
  7. OOP capFN_APPLY_OOPCAP caps the member's total responsibility (deductible + coinsurance) at the OOP room remaining; the shaved amount flows back to plan liability.
  8. Persist — UPDATE the header (which fires 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]

C.2 Procedure / trigger table (the full driven set)

RoutineKindPurposeCalls / firesFrequency
SP_ADJUDICATE_BATCHProc (RS)Cursor-loop every E claim; return per-claim outcomes.SP_ADJUDICATE_CLAIM; HCCLMTMP→HCCLMRUN swap.Per adjudication run (e.g. daily).
SP_ADJUDICATE_CLAIMProcThe 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_ELIGIBILITYProcMember exists / active / in coverage window.Reads HCMEM; NOT FOUND handler.Per claim (nested).
SP_PRORATE_LINESProcProrate header allocations to lines; last line absorbs residue.Cursor over HCCLML; UPDATE HCCLML.Per paid claim (nested).
SP_POST_GLProcPost plan-expense (E) + member-contra (M) GL rows, idempotently.INSERT HCGL (guarded by NOT EXISTS on LREF).Per paid claim (nested).
SP_GEN_PAYMENTProcGenerate one PAYNO per claim, idempotently.INSERT HCPAY; UPDATE HCCLMH.PAYNO.Per paid claim (nested).
SP_VOID_PAYMENTProcThe only sanctioned void: flip payment to V, post -LAMT reversal.UPDATE HCPAY; INSERT HCGL (V); SIGNAL 75010/75011.On demand.
TR_HCCLMH_DUPTriggerBEFORE INSERT: stamp DUPHASH, pend a duplicate (D1) or veto (75020).FN_DUPHASH; SIGNAL 75020.Every claim insert.
TR_HCCLMH_ACCUMTriggerAFTER UPDATE OF CSTAT into 'A': carry DEDAPPL/MEMBRESP into HCMEM; audit.UPDATE HCMEM; INSERT HCAUD.Every claim that pays.
TR_HCREMIT_IOUTriggerINSTEAD OF UPDATE on HCREMIT: reject amount edit (75021) / reroute void.SP_VOID_PAYMENT; SIGNAL 75021.Every remittance-view update.

C.3 Ordering & dependencies

D. Data Files (data dictionary) ↑ top

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).

HCPLAN — Plan master (PK PLANNO)

FieldTypeMeaning
PLANNOCHAR(8)Plan number (PK), e.g. PLAN0001.
PNAMEVARCHAR(20)Plan name (GOLD, SILVER).
DEDUCTDECIMAL(9,2)Annual deductible.
COINSDECIMAL(5,4)Plan's coinsurance share after deductible (0.8000 = plan pays 80%).
INNETDECIMAL(5,4)In-network allowed-amount rate applied to billed.
OUTNETDECIMAL(5,4)Out-of-network allowed-amount rate applied to billed.
OOPMAXDECIMAL(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).

HCPROV — Provider master (PK PROVNO)

FieldTypeMeaning
PROVNOCHAR(9)Provider number (PK).
PNAMEVARCHAR(24)Provider name.
INNETWCHAR(1)Y in-network, N out-of-network.
CTRRATEDECIMAL(5,4)Contract rate applied to billed when in-network.

Seed: PRV000001 in-net rate 1.0000 (allowed = billed); PRV000003 out-of-network.

HCMEM — Member master + accumulators (PK MEMBNO)

FieldTypeMeaning
MEMBNOCHAR(9)Member number (PK).
MNAMEVARCHAR(24)Member name.
PLANNOCHAR(8)Enrolled plan.
EFFDTINTCoverage effective date (YYYYMMDD).
TERMDTINTCoverage termination date (99999999 = active).
DEDMETDECIMAL(9,2)Accumulator: deductible met this benefit year.
OOPMETDECIMAL(9,2)Accumulator: out-of-pocket met this benefit year.
MSTATCHAR(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.

HCCLMH — Claim header (PK CLMNO)

FieldTypeMeaning
CLMNOCHAR(10)Claim number (PK).
MEMBNO / PROVNO / PLANNOCHAR(9)/(9)/(8)Member, provider, plan.
SVCDTINTDate of service (YYYYMMDD).
BILLEDDECIMAL(9,2)Total billed (must equal the sum of lines).
ALLOWEDDECIMAL(9,2)Total allowed post network rate.
DEDAPPLDECIMAL(9,2)Deductible applied on this claim.
PLANLIADECIMAL(9,2)Plan liability (paid amount).
MEMBRESPDECIMAL(9,2)Member responsibility.
DUPHASHCHAR(32)MEMBNO|PROVNO|SVCDT|BILLED composite (stamped by trigger).
CSTATCHAR(1)E entered, A adjudicated/paid, D denied, P pended (dup).
HOLDCDCHAR(2)' ' none, D1 dup, D2 termed, D3 not-yet-effective, D4 suspended, E2 line/header mismatch.
PAYNOCHAR(10)Payment number, set once paid.

HCCLML — Claim lines (PK CLMNO, CLINE)

FieldTypeMeaning
CLMNO / CLINECHAR(10) / INTClaim + line number (PK).
BILLEDDECIMAL(9,2)Billed amount for this line (input).
ALLOWEDDECIMAL(9,2)Prorated allowed for this line.
PLANLIADECIMAL(9,2)Prorated plan liability for this line.
MEMBRESPDECIMAL(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).

HCPAY — Payment / remittance (PK PAYNO)

FieldTypeMeaning
PAYNOCHAR(10)Payment number (PK), PY + claim suffix.
CLMNOCHAR(10)Claim paid.
PAYDTINTPayment date (the claim's service date).
PAMTDECIMAL(9,2)Paid amount = claim PLANLIA.
PSTATCHAR(1)A applied, V void.

HCGL — GL ledger (append-only, PK LSEQ)

FieldTypeMeaning
LSEQINTCaller-assigned ordering/uniqueness key (PK), MAX(LSEQ)+1.
LTYPECHAR(1)E plan-expense (+), M member-contra (informational +), V void-reversal (−).
LREFCHAR(10)Claim or payment number referenced.
MEMBNOCHAR(9)Member.
LAMTDECIMAL(9,2)Amount (negative for a V reversal).
LDATEINTPosting date.

HCAUD — Trigger audit log (PK ASEQ)

FieldTypeMeaning
ASEQINTCaller-assigned audit sequence (PK). See the note below on why it is not IDENTITY.
OPCHAR(10)Operation, e.g. ACCUM.
TNAMECHAR(10)Source table (HCCLMH).
KEYVALCHAR(12)Key of the affected row (the claim number).
DETAILVARCHAR(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.

HCCLMRUN / HCCLMTMP — Batch outcome + staging (PK CLMNO each)

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.

HCREMIT — Remittance view

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.

Relationships

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.

E. Operations Runbook ↑ top

E.1 Day-in-the-life

  1. Confirm the job's library list includes INSCLM (LIBL = QSYS QGPL INSCLM QTEMP, CURLIB = INSCLM).
  2. Enter the day's claims: 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.
  3. Run the cycle: CALL INSCLM/SP_ADJUDICATE_BATCH(?, ?, ?) (or SBMJOB a RUNSQL of it).
  4. Post-check by re-querying the tables (not the OUT counters — see the CLM-04 note in C):
-- 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:

Handle a payment reversal on demand with CALL INSCLM/SP_VOID_PAYMENT('<PAYNO>'); never edit HCPAY.PAMT or the HCREMIT view directly.

E.2 Reconciling figures

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.

-- 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';

E.3 Failure & re-run rules

SituationBehaviourAction
Re-run the batch after it clearedCursor 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 partwayClaims 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 0Confirmed 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 enteredBEFORE 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 duplicateSIGNAL 75020 — the INSERT is rejected outright.The row never lands. Correct the feed; a genuine new claim must arrive as E.
Line total ≠ header billedDenied D/E2 before eligibility.Fix the lines or header so they sum, re-enter as a new E claim.
Void an already-void paymentSIGNAL 75011 (not a silent no-op); no second reversal row.Intended idempotency guard. The payment is already reversed.
Void a nonexistent paymentSIGNAL 75010.Check the PAYNO; from RPG this surfaces as SQLCODE -438.
Direct edit of a remit amountSIGNAL 75021 via the INSTEAD OF trigger; base table untouched.Use SP_VOID_PAYMENT and reissue; never UPDATE HCREMIT/HCPAY by hand.
Because every money movement posts an append-only 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.

F. Developer Reference ↑ top

The complete SQL-PL surface, from routines.sql. All objects are in library INSCLM. Signatures are given as declared.

F.1 Functions (4)

FN_DUPHASH (MEMBNO CHAR(9), PROVNO CHAR(9), SVCDT INT, BILLED DECIMAL(9,2)) RETURNS CHAR(32)
The duplicate-detection composite key: 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.
FN_ALLOWED (BILLED DECIMAL(9,2), INNETW CHAR(1), CTRRATE DECIMAL(5,4), OUTNET DECIMAL(5,4)) RETURNS DECIMAL(9,2)
Allowed amount = 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.
FN_APPLY_DEDUCTIBLE (ALLOWED DECIMAL(9,2), DEDUCT DECIMAL(9,2), DEDMET DECIMAL(9,2)) RETURNS DECIMAL(9,2)
Deductible taken on this claim = MIN(ALLOWED, DEDUCT−DEDMET), floored at 0 (never negative once met). DETERMINISTIC.
FN_APPLY_OOPCAP (MEMBSHARE DECIMAL(9,2), OOPMAX DECIMAL(9,2), OOPMET DECIMAL(9,2)) RETURNS DECIMAL(9,2)
Caps a member share at the OOP room remaining: ROOM = MAX(0, OOPMAX−OOPMET); returns MIN(MEMBSHARE, ROOM). DETERMINISTIC.

F.2 Procedures (7)

SP_ELIGIBILITY (IN P_MEMBNO CHAR(9), IN P_SVCDT INT; OUT P_ELIG CHAR(1), OUT P_REASON CHAR(2))
Reads HCMEM (CONTINUE HANDLER FOR NOT FOUND sets a found flag). IF/ELSEIF ladder: not found → D1; MSTAT≠'A' → D4; svc < EFFDT → D3; svc > TERMDT → D2; else eligible Y. Called nested from SP_ADJUDICATE_CLAIM.
SP_PRORATE_LINES (IN P_CLMNO CHAR(10), IN P_ALLOWED, P_PLANLIA, P_MEMBRESP, P_BILLEDTOT DECIMAL(9,2))
Cursor loop over the claim's lines ordered by CLINE, prorating each of allowed/plan/member by billed ratio (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.
SP_ADJUDICATE_CLAIM (IN P_CLMNO CHAR(10); OUT P_CSTAT CHAR(1), P_HOLDCD CHAR(2), P_PLANLIA, P_MEMBRESP DECIMAL(9,2))
The 8-step engine (see C.1): intake edit (E2), nested eligibility, dup re-check, allowed, deductible, coinsurance split, OOP cap on the member total, persist header + nested prorate/GL/payment. The header UPDATE fires TR_HCCLMH_ACCUM for the accumulator carry-forward — the procedure does not touch DEDMET/OOPMET itself.
SP_POST_GL (IN P_CLMNO CHAR(10), P_MEMBNO CHAR(9), P_PLANLIA, P_MEMBRESP DECIMAL(9,2), P_SVCDT INT)
Idempotent (guarded by 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.
SP_GEN_PAYMENT (IN P_CLMNO CHAR(10), P_SVCDT INT, P_PLANLIA DECIMAL(9,2))
Idempotent (guarded by 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.
SP_ADJUDICATE_BATCH (OUT P_PAID INT, OUT P_DENIED INT, OUT P_PENDED INT) — DYNAMIC RESULT SETS 1
Cursor over 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.
SP_VOID_PAYMENT (IN P_PAYNO CHAR(10))
The only sanctioned void. NOT FOUND → 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.

F.3 Triggers (3) & the view

TR_HCCLMH_DUP — BEFORE INSERT ON HCCLMH, FOR EACH ROW
Computes 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.
TR_HCCLMH_ACCUM — AFTER UPDATE OF CSTAT ON HCCLMH, WHEN (N.CSTAT='A' AND O.CSTAT≠'A')
The accumulator carry-forward: 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.
TR_HCREMIT_IOU — INSTEAD OF UPDATE ON HCREMIT, FOR EACH ROW
If N.PAMT ≠ O.PAMTSIGNAL 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.

F.4 SQL-PL patterns used here

F.5 Application SQLSTATE table

SQLSTATERaised byMeaning
75010SP_VOID_PAYMENTPayment not found.
75011SP_VOID_PAYMENTPayment already voided (idempotency guard).
75020TR_HCCLMH_DUPInbound row is an already-adjudicated duplicate — insert vetoed.
75021TR_HCREMIT_IOURemit amount is immutable; void and reissue instead.

The claim-level denials/pends use hold codes on the row (HCCLMH.HOLDCD: D1D4, 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.

G. Glossary ↑ top

Adjudication
The end-to-end processing of a claim: intake edit, eligibility, duplicate check, benefit calculation, line pro-rata, accumulator carry-forward, GL post and payment generation (SP_ADJUDICATE_CLAIM).
Allowed amount
Billed × the applicable network rate (in-network provider contract rate, else the plan's out-of-network rate). The base on which deductible and coinsurance are computed (FN_ALLOWED).
Accumulator (DEDMET / OOPMET)
The member's running deductible-met and out-of-pocket-met totals for the benefit year, carried forward claim to claim by TR_HCCLMH_ACCUM.
Coinsurance
The share split of the post-deductible remainder between plan and member (plan share = remainder × plan COINS; member share = the rest).
Deductible
The amount a member pays before the plan begins sharing cost; taken from the member's remaining deductible on each claim (FN_APPLY_DEDUCTIBLE).
Duplicate hash (DUPHASH)
A composite of member+provider+service-date+billed used to detect a re-billed claim (FN_DUPHASH); a match pends the new claim so it is never double-paid.
Hold code (HOLDCD)
The reason a claim was denied or pended: D1 duplicate/not-found, D2 termed, D3 not-yet-effective, D4 suspended, E2 line/header mismatch.
Idempotent
Safe to run again with the same result. The batch (E-only cursor), GL post and payment generation (NOT EXISTS guards) are idempotent; re-running never doubles the accumulator, GL or payments.
INSTEAD OF trigger
A trigger on a view that replaces the attempted DML with its own logic. TR_HCREMIT_IOU rejects a remit-amount edit and reroutes a void through SP_VOID_PAYMENT.
Out-of-pocket maximum (OOP max)
The cap on a member's total cost share for the benefit year; once met, further claims cost the member nothing and the plan absorbs the full allowed amount (FN_APPLY_OOPCAP).
Pro-rata (line allocation)
Distributing the header allowed/plan/member amounts down to claim lines by billed ratio, with the last line absorbing the rounding residue so lines sum back to the header exactly (SP_PRORATE_LINES).
SIGNAL / SQLSTATE / SQLCODE
SQL PL raises a business-rule exception with SIGNAL SQLSTATE '…'; SQLCODE 0 is success, and an unhandled SIGNAL reaches an RPG caller as SQLCODE -438.
SQL PL
SQL Procedural Language — DB2 for i's compound-statement language for functions, procedures and triggers. This entire application is written in it.
STRSQL
Start Interactive SQL — the IBM i session from which an operator drives this app by entering CALL and SELECT statements (there is no 5250 screen).
Void
Reversing a payment: flipping HCPAY to V and posting a negative GL reversal (SP_VOID_PAYMENT). A financial reversal only — it does not un-adjudicate the claim or move the accumulator.
WITH RETURN result set
A cursor opened under DYNAMIC RESULT SETS so a procedure hands rows back to its caller; SP_ADJUDICATE_BATCH returns one row per adjudicated claim this way.