POLARIS/i — Insurance Policy Administration

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

POLARIS/i is a personal-lines property & casualty (auto + homeowners) policy administration system: policy issuance and underwriting, mid-term endorsement, batch renewal and cancellation, quarterly premium billing with cash application and aged-receivables reporting, a full first-notice-of-loss claims flow (intake / adjudication / payment), agent commission calculation, a balanced GL posting feed, and interactive 5250 inquiry screens. The application is written in the classic IBM i mix — fixed-form and free-form RPG (ILE), one ILE COBOL report, CL build drivers, DDS physical / logical / display / printer files, and DB2 for i via both DDS and SQL DDL — and the transaction/audit file is journaled. This manual is the reference for the operator who runs the online screens and the periodic batch programs, and for the developer maintaining the application. It is grounded entirely in the committed source (insurance-app/src/sources.mjs, src/seed.mjs, the CL driver PLSETUP, and the named test/pl_*.mjs drivers), and it is honest where the application lacks something a reader might expect — including three confirmed emulator platform findings whose effects an operator will actually see.

Contents

A. Overview & Architecture ↑ top

A.1 What it does

POLARIS/i administers the life of a personal-lines P&C policy across nine functional areas:

A.2 Layered architecture: RPG/COBOL business logic over DDS + DB2 for i

Unlike a SQL-PL-centric application, POLARIS/i keeps its business logic in the programs themselves. The layering is:

Everything runs in library POLARIS. The tested jobs run with LIBL = QSYS QGPL POLARIS QTEMP and CURLIB = POLARIS.

A.3 Component & flow

  UNDERWRITING / LIFECYCLE     ONLINE (5250)        BATCH (periodic)
  ------------------------     -------------        ----------------
  UWRPGM  <--CALL-- POLISSUE   POLMENUP             BILLGEN   (quarterly installments)
                    POLENDT      opt 1 --> POLINQ   CASHPOST  (apply cash, oldest-due-first)
                    POLRENEW     opt 2 --> CLMINQ   AGERPT    (aged receivables -> AGEDARP spool)
                    POLCANCEL                        COMMCALC  (commission per bound txn)
   CLAIMS                                            GLPOST    (balanced DR/CR -> GLFEED)
   CLMFNOL --> CLMADJ --> CLMPAY                     POLRPT    (COBOL: inforce prem vs GLFEED)

        every issue/endorse/renew/cancel  ---writes--->  POLTRN (journaled to PLJRN)
        POLISSUE/BILLGEN/CASHPOST/CLM* ---update/write---> POLMST / BILLDUE / CLMMST / CLMPMT
        COMMCALC ---reads POLTRN, writes---> COMMST     GLPOST ---reads POLTRN+COMMST, EXEC SQL---> GLFEED

A single event — say, issuing a policy — flows: an operator (or a batch seed) has a pending POLMST row (PSTAT='Q') with COVMST coverage rows → CALL POLARIS/POLISSUE → POLISSUE sums the coverages, CALLs UWRPGM for the rated premium, UPDATEs POLMST to inforce and WRITEs a POLTRN 'I' row → the journal (PLJRN) captures that WRITE as an R PT entry → downstream, COMMCALC reads that 'I' row and posts a commission, and GLPOST posts the premium to GLFEED.

A.4 Object inventory

ObjectTypeRole
CUSTMSTPFCustomer / insured master.
AGTMSTPFProducer / agent master (commission plan).
POLMSTPFPolicy header master (the heart of the app).
POLTRN / POLLFPF / LFPolicy transaction/audit log (journaled) + per-policy LF.
COVMST / COVLFPF / LFCoverage/risk items + per-policy LF for subfile display.
UWRATEPFUnderwriting rating-factor table.
BILLDUE / AGELFPF / LFBilling installments + aging LF keyed by due date.
CASHRECPFCash receipts (arrival sequence).
CLMMSTPFClaim header (FNOL).
CLMPMT / CLMPLFPF / LFReserve/payment lines + per-claim LF for the subfile.
COMMSTPFCommission line per bound transaction (unkeyed).
GLFEEDSQL tableBalanced DR/CR GL posting feed (DB2 DDL).
POLDSPF / CLMDSPF / POLMENUDSPFPolicy inquiry / claim inquiry (SFL) / operator menu.
POLREGP / AGEDARP / CLMSTATPPRTFPolicy register / aged receivables / open claims printer files.
UWRPGMRPGLECallable underwriting rating subprogram.
POLISSUE / POLENDT / POLRENEW / POLCANCELRPGLEPolicy lifecycle drivers.
BILLGEN / CASHPOST / AGERPTRPGLEBilling / cash / aging.
CLMFNOL / CLMADJ / CLMPAYRPGLEClaims intake / adjudication / payment.
COMMCALC / GLPOSTRPGLECommission calc / GL feed (embedded SQL).
POLINQ / CLMINQ / POLMENUPRPGLEInteractive inquiry + menu programs.
POLRPTCBLLEILE COBOL inforce-premium report + GLFEED cross-check.
PLSETUPCLPBuild-everything + journaling CL driver.
PLJRN / PLJRNRCVJRN / JRNRCVJournal + receiver over POLTRN.

The full catalogue is 17 RPG programs + 1 ILE COBOL program, over 11 PFs, 4 LFs and 1 SQL table, with 3 DSPF (one carrying an SFL/SFLCTL subfile), 3 PRTF, and journaling on POLTRN — all built by the single CL driver PLSETUP. Sections D and F expand each.

B. Online Transactions & Screens ↑ top

B.1 The command/entry line

POLARIS/i has no CICS transaction identifiers and no menu-driven transid switch. On IBM i, each program is reached by name from a 5250 command-entry line (or a JOBQ/scheduler for the batch jobs). The operator equivalent of "type a transid and Enter" is "type a CALL command and Enter". Before invoking anything, the job's library list must include POLARIS — the tested jobs run with LIBL = QSYS QGPL POLARIS QTEMP and CURLIB = POLARIS. The whole library is first built by CALL PGM(POLARIS/PLSETUP).

To do thisType on the command line
Open the operator menu (routes to the two inquiry screens)CALL POLARIS/POLMENUP
Open policy inquiry directlyCALL POLARIS/POLINQ
Open claim inquiry (subfile) directlyCALL POLARIS/CLMINQ
Issue a pending policyCALL POLARIS/POLISSUE PARM('POL00001' '000')
Endorse a coverage limitCALL POLARIS/POLENDT PARM('POL00006' 'BI ' '75000')
Cancel a policy (elapsed / term days)CALL POLARIS/POLCANCEL PARM('POL00006' '00090' '00365')
Take first notice of lossCALL POLARIS/CLMFNOL PARM('CLM00001' 'POL00001' 20260615 'Collision damage ' 'COL' 2000.00)
Run a batch program (or SBMJOB it)CALL POLARIS/BILLGEN  etc.

The interactive programs (POLMENUP, POLINQ, CLMINQ) take no parameters. The lifecycle and claims programs are parameter-driven via an *ENTRY PLIST (or free-form dcl-pi): a caller/operator supplies the policy or claim key and the amounts. The pure-batch programs (BILLGEN, CASHPOST, AGERPT, COMMCALC, GLPOST, POLRPT) take no parameters and simply pass over whatever rows are on file, so a scheduled submission is a bare CALL. Each program DSPLYs a one-line result summary to the joblog (section F.4).

B.2 The inquiry screens (POLINQ, CLMINQ, POLMENUP)

There are three interactive programs. POLMENUP is a plain menu; POLINQ is a non-subfile policy inquiry; CLMINQ is the application's one real subfile screen.

POLMENUP / POLMENU — operator menu

A one-field menu: key 1 (Policy Inquiry) or 2 (Claim Inquiry) into MOPT and press Enter; POLMENUP does an indicator-conditioned dynamic CALL 'POLINQ' / CALL 'CLMINQ'. F3 exits. An unrecognised option sets the DMSG line to "Invalid option".

POLINQ / POLDSPF — policy inquiry (non-subfile)

POLINQ presents a fixed-layout header screen (DDS record POLINQF). The operator keys a policy number into IPOLNO and presses Enter; POLINQ CHAINs POLMST and, if found, displays the insured, line of business, status, term premium and balance, then loops COVLF (coverages keyed by policy) for up to four coverage lines rendered into DCOV1DCOV4 as "COVCD  LIMIT  DEDUCT  PREMIUM".

Policy Inquiry - POLARIS/i Policy number: PLD00001 Insured . . . : D10001 Line of bus. . : A Status . . . . : I Term premium . : 0.00 Balance . . . : 0.00 Coverage Limit Deduct Premium BI 100000.00 500.00 700.00 COL 25000.00 1000.00 300.00 Policy found. F3=Exit Enter=Inquire
FieldType (DDS)Shows
IPOLNO8A input/outputPolicy number keyed by the operator.
DNAME25A outputInsured (rendered from CUSTNO).
DLOB / DSTAT1A outputLine of business / policy status.
DPREM / DBAL14A outputTerm premium / policy balance.
DCOV1–DCOV430A outputUp to four coverage lines from COVLF.
DMSG50A outputStatus line ("Policy found." / "Policy not found: ...").

CLMINQ / CLMDSPF — claim inquiry (SFL/SFLCTL subfile)

CLMINQ is the one subfile screen. The operator keys a claim number into ICLMNO; CLMINQ CHAINs CLMMST for the header (policy, loss date, cause, status, reserved and paid totals) and loads a subfile (DDS record CLMSFL under control record CLMCTL, SFLPAG(10) per page, SFLSIZ(20)) with one row per reserve/payment line from CLMPLF (the payment LF keyed by claim). Each subfile WRITE advances an explicit RRN counter (SFILE(CLMSFL:CSFLRRN)). The interactive suite loads twelve payment rows on one claim to force paging past a single SFLPAG(10) page.

Claim Inquiry - POLARIS/i Claim number: CLD00001 Policy . . . . : PLD00001 Loss date . . : 20260727 Cause . . . . : Windshield crack, hail damage Status . . . . : O Date Type Amount 20260727 R 900.00 20260727 R 900.00 20260727 P 900.00 reserved 900.00 paid 900.00 Claim found. F3=Exit Enter=Inquire
FieldType (DDS)Shows
ICLMNO8A input/outputClaim number keyed by the operator.
DPOLNO / DLOSSDT / DCAUSE / DCSTAToutputHeader: policy, loss date, cause, status.
DTOTRES / DTOTPAID14A outputReserved and paid totals.
SPMTDT / SPMTTYP / SPMTAMTSFL outputPer-line date / type (R reserve, P payment) / amount.

Subfile indicators mirror the DDS numbering: 31=SFLDSP, 32=SFLDSPCTL, 33=SFLCLR, 34=SFLEND(*MORE). SFLDSPCTL (*IN32) is set on before the first EXFMT so the control format (title and constants) renders even with an empty subfile.

All three screens are display-only. POLINQ and CLMINQ never edit data — there is no option column, no maintenance action, no post-from-screen. Money and status changes happen only through the batch/parameter-driven programs (POLISSUE, POLENDT, CASHPOST, CLMFNOL/CLMADJ/CLMPAY, etc.), never from an inquiry screen.

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

Honest statement: POLARIS/i does not model a four-eyes maker–checker / separate-authorization workflow. There is no "one user posts, a second user approves" step: a policy issued through POLISSUE binds in one flow, a claim payment through CLMPAY posts immediately, an endorsement through POLENDT re-rates and posts in one call. The control model the application does have is:

In sum, the control posture is append-only audit + journaling + status/state gating + coverage-limit and GL-balance enforcement, rather than a segregation-of-duties approval workflow.

C. Batch Jobs & the Periodic Cycle ↑ top

POLARIS/i's back-office work runs as a set of individually-submitted batch programs rather than one monolithic nightly job. A realistic day-end chain is BILLGEN → CASHPOST → COMMCALC → GLPOST, with AGERPT and POLRPT as reports, and the lifecycle/claims programs run on demand as business events arrive.

Honest note on the "day-end" driver. PLAN.md envisaged a packaged PLDAYEND CL that chained the batch programs, but no PLDAYEND member exists in the committed sourcePLSETUP is the only CL driver, and it only builds the library and sets up journaling. The day-end chain is therefore driven by the operator/scheduler, program by program. The pl_daycycle_iter1.mjs driver shows exactly this: it SBMJOBs BILLGEN/CASHPOST/COMMCALC/GLPOST onto a real *JOBQ (POLARIS/NITEQ) with HOLD(*YES), then releases them in dependency order, because the queue itself is FIFO-within-priority and does not model predecessor gating.

These programs take no processing-date control row (unlike some batch systems). The batch programs simply pass over whatever rows are on file; the lifecycle and claims programs are driven by their CALL parameters. Dates in this application are plain YYYYMMDD-shaped zoned/packed integers, not a true *DATE type, and several date steps (installment due dates, renewal term extension, cancellation day-count) are deliberately simplified — documented in the source and in section G, and not to be mistaken for calendar-correct arithmetic.

-- a realistic day-end chain, submitted the way a scheduler would
CRTJOBQ JOBQ(POLARIS/NITEQ) TEXT('POLARIS night batch queue')
SBMJOB CMD(CALL POLARIS/BILLGEN)  JOB(PLBILLGN) JOBQ(POLARIS/NITEQ)
SBMJOB CMD(CALL POLARIS/CASHPOST) JOB(PLCASHPO) JOBQ(POLARIS/NITEQ)
SBMJOB CMD(CALL POLARIS/COMMCALC) JOB(PLCOMMCA) JOBQ(POLARIS/NITEQ)
SBMJOB CMD(CALL POLARIS/GLPOST)   JOB(PLGLPOST) JOBQ(POLARIS/NITEQ)
-- reports (own jobs)
CALL POLARIS/AGERPT
CALL POLARIS/POLRPT

C.1 Full batch program set

ProgramPurposeReads / callsInputsOutputsFrequency
POLISSUE Issue a pending (Q) policy: sum coverages, rate, bind or decline. CHAINs POLMST/CUSTMST, SETLL/READE COVMST, CALLs UWRPGM, WRITEs POLTRN 'I'. PARM(POLNO, PRIOR-CLAIMS). DSPLY POLISSUE POSTED=n DECLINE=n SKIP=n; POLMST→I, POLTRN 'I'. On demand.
POLENDT Endorse a coverage limit; re-rate premium delta. CHAIN POLMST + COVMST (POLNO+COVCD KLIST), UPDATE both, WRITE POLTRN 'E'. PARM(POLNO, COVCD, NEWLIMIT). DSPLY POLENDT POSTED=n DELTA=amt REJECT=n; signed PREMCHG. On demand.
POLRENEW Renew inforce policies at/before a cutoff expiry. SETLL *LOVAL / READ POLMST, re-sum COVMST, re-CALL UWRPGM, WRITE POLTRN 'R'. PARM(CUTOFF-YYYYMMDD). DSPLY POLRENEW RENEWED=n SKIPPED=n. Batch (periodic).
POLCANCEL Cancel a policy; pro-rate the unearned refund. CHAIN(E) POLMST, %DECH proration, UPDATE, WRITE POLTRN 'C' (negative PREMCHG). PARM(POLNO, ELAPSED-DAYS, TERM-DAYS). DSPLY POLCANCEL POSTED=n REFUND=amt REJECT=n. On demand.
BILLGEN Generate 4 quarterly installments per inforce policy (idempotent). SETLL/READ POLMST, CHAIN(E) BILLDUE dup-check, WRITE BILLDUE x4. none (scans POLMST for PSTAT='I'). DSPLY BILLGEN GENERATED=n SKIPPED=n; BILLDUE rows (BSTAT='O'). Per term / day-end.
CASHPOST Apply cash receipts oldest-due-first, partial-pay aware. READ CASHREC, bounded 1..4 CHAIN(E) BILLDUE, UPDATE BILLDUE + POLMST.POLBAL. none (scans CASHREC). DSPLY CASHPOST FULL=n PARTIAL=n NOBILL=n. Day-end.
AGERPT Age outstanding installments into buckets; spooled report. SETLL *LOVAL / READ AGELF, write AGEDARP PRTF. none (scans AGELF). DSPLY AGERPT CURRENT=.. D30=.. D60=.. D90PLUS=.. GRAND=..; AGEDARP spool. Periodic.
CLMFNOL First notice of loss: open a claim, initial reserve. CHAIN(E) POLMST (must be inforce), WRITE CLMMST + CLMPMT 'R'. PARM(CLMNO, POLNO, LOSSDT, CAUSE, COVCD, RESAMT). DSPLY CLMFNOL ACCEPTED ... / REJECTED .... On demand.
CLMADJ Adjudicate a proposed reserve vs the coverage limit. CHAIN(E) CLMMST + COVMST (POLNO+COVCD), UPDATE CLMMST, WRITE CLMPMT 'R'. PARM(CLMNO, COVCD, PROPOSED-RESERVE). DSPLY CLMADJ APPROVED ... / DENIED .... On demand.
CLMPAY Post a claim payment against the remaining limit. CHAIN(E) CLMMST + COVMST, UPDATE CLMMST.TOTPAID, WRITE CLMPMT 'P'. PARM(CLMNO, COVCD, PMTAMT). DSPLY CLMPAY POSTED ... / REJECTED: EXCEEDS REMAINING LIMIT .... On demand.
COMMCALC Commission per bound transaction (I/E). READ POLTRN, CHAIN POLMST→AGTMST, WRITE COMMST. none (scans POLTRN). DSPLY COMMCALC POSTED=n SKIP=n TOTCOMM=amt; COMMST rows. Day-end.
GLPOST Consolidate premium + commission into balanced DR/CR GLFEED rows. READ POLTRN (I/E) + COMMST, embedded EXEC SQL INSERT into GLFEED. none (scans POLTRN + COMMST). DSPLY GLPOST BATCH=1 ROWS=n; GLFEED DR/CR rows. Day-end.
POLRPT COBOL: inforce premium by LOB + GLFEED cross-check. READ POLMST (COMP-3 FD), EXEC SQL SELECT SUM over GLFEED. none. DSPLY POLRPT INFORCE-AUTO/HOME ..., GLFEED-PREMINC ..., CROSS-CHECK: IN/OUT OF BALANCE. Periodic.

C.2 Lifecycle / billing / claims / GL detail

Underwriting & issuance — UWRPGM / POLISSUE

UWRPGM is a callable rating subprogram: given DOB, prior-claim count and a base premium, it hard-declines (status 'D', premium 0) on more than three prior claims; otherwise it derives an age band (Y25 under 25, SR 65+, else STD), CHAINs UWRATE for the factor, and returns base × factor, status 'A'. POLISSUE sums the policy's COVMST coverage premiums as the base, CALLs UWRPGM, and on approval posts TERMPRM and flips PSTAT to inforce; on decline it marks PSTAT='X'. It is idempotent per policy (only issues PSTAT='Q').

Seeded UWRATE factors (from the lifecycle driver):
  Y25 -> 1.500   (under 25)
  STD -> 1.000   (standard)
  SR  -> 0.900   (senior discount)
Expected DSPLY (standard risk, 800.00 base):
  UWRPGM RATECD=STD FACTOR=1.000 PREM=800.00
Known effect (finding PG-002). Under the emulator, POLISSUE's coverage-sum loop (a partial-key SETLL/READE over the unique-keyed COVMST) mis-resolves, so the summed base premium comes out as 0.00 and the issued policy's TERMPRM lands at 0.00 instead of the true total. This cascades into 0.00 installments, 0.00 commission and a 0.00 (still balanced) GL batch. It is a confirmed platform finding (section F.5), left unworked-around in the app source; the inquiry screen honestly shows the same 0.00 that is on disk.

Endorsement / renewal / cancellation — POLENDT / POLRENEW / POLCANCEL

POLENDT re-rates a coverage proportionally (NEWPREM = OLDPREM × NEWLIMIT / OLDLIMIT), updates COVMST and POLMST.TERMPRM by the delta, and writes a signed 'E' transaction (the delta can be negative on a limit decrease). POLRENEW scans inforce policies whose EXPDT is at/before the cutoff parameter, extends the term (EFFDT := old EXPDT, EXPDT += 10000 on the YYYYMMDD integer — a simplified one-year step), re-rates and writes 'R'. POLCANCEL pro-rates the unearned premium via %DECH (half-adjusted packed division) from the caller-supplied elapsed/term day counts, sets PSTAT='C', and writes a negative-refund 'C' transaction; it rejects an already-cancelled policy.

Billing & cash — BILLGEN / CASHPOST / AGERPT

BILLGEN writes four quarterly installments per inforce policy: DUEAMT = TERMPRM/4 for installments 1–3 (packed DIV truncates), installment 4 absorbs the MVR remainder so the four sum exactly to TERMPRM (e.g. 1000.01 → 250, 250, 250, 250.01). Due dates use a synthetic EFFDT + (INSTNO-1)×100 scheme. It is idempotent: if installment 1 already exists for a policy, the whole policy is skipped. CASHPOST applies each CASHREC receipt to the single oldest open/partial installment (bounded 1..4 CHAIN), marking it 'P' (full) or 'D' (partial) and decrementing POLMST.POLBAL by the amount applied. AGERPT ages the outstanding (non-'P') installments over AGELF (keyed by due date) into CURRENT/30/60/90+ buckets against a synthetic as-of date, writing the AGEDARP spooled report and DSPLYing the bucket totals.

Expected DSPLY (billing/cash examples):
  BILLGEN GENERATED=2 SKIPPED=0        2 inforce policies billed
  BILLGEN GENERATED=0 SKIPPED=2        idempotent rerun
  CASHPOST FULL=1 PARTIAL=1 NOBILL=0   one full, one partial pay
Honest note on CASHPOST idempotency. Only BILLGEN is dup-guarded. CASHREC carries no "posted" flag, so re-running CASHPOST re-applies every receipt again from scratch. Re-run it only after clearing/replacing the receipts you have already posted (the billing driver does exactly this with CLRPFM).

Claims — CLMFNOL / CLMADJ / CLMPAY

CLMFNOL validates the policy is inforce, opens CLMMST (CSTAT='O', TOTRES=reserve) and writes an initial reserve CLMPMT 'R' row. CLMADJ CHAINs the claim then the coverage (POLNO+COVCD); if the proposed reserve fits the coverage limit it is APPROVED (TOTRES updated, CSTAT stays 'O'), else DENIED (CSTAT='D', TOTRES unchanged) — either way a CLMPMT 'R' row is written to keep the history complete. CLMPAY posts against remaining limit = COVLIMIT - TOTPAID; if the payment fits it writes CLMPMT 'P' and updates TOTPAID, else it is rejected with no row written.

Expected DSPLY (claims examples):
  CLMFNOL ACCEPTED CLMNO=CLM00001 POLNO=POL00001 RESERVE=2000.00 COVCD=COL
  CLMADJ APPROVED CLMNO=CLM00001 RESERVE=2000.00 LIMIT=5000.00
  CLMADJ DENIED   CLMNO=CLM00003 PROPOSED=9000.00 LIMIT=5000.00
  CLMPAY POSTED   CLMNO=CLM00001 AMT=1500.00 TOTPAID=1500.00 REMAIN=3500.00
  CLMPAY REJECTED: EXCEEDS REMAINING LIMIT CLMNO=CLM00001 REQ=4000.00 REMAIN=3500.00

Commissions, GL & the COBOL cross-check — COMMCALC / GLPOST / POLRPT

COMMCALC walks POLTRN in arrival order, posts a commission for every bound ('I' or 'E') transaction (COMMAMT = PREMCHG × COMMPCT/100 via a two-hop CHAIN POLTRN→POLMST→AGTMST), and skips other transaction types. GLPOST posts a balanced DR/CR pair per premium transaction (1200-AR/4000-PREM) and per commission (6100-CEXP/2100-CPAY) into GLFEED via embedded EXEC SQL INSERT. The ILE COBOL POLRPT totals inforce premium by LOB and cross-checks it against a GLFEED SUM, printing "IN BALANCE" or "OUT OF BALANCE".

Expected DSPLY (commission/GL examples):
  COMMCALC POSTED=3 SKIP=1 TOTCOMM=235.00   3 bound txns, 1 'C' skipped
  GLPOST BATCH=1 ROWS=6
  POLRPT INFORCE-AUTO     1 PREM    1200.00
  POLRPT GLFEED-PREMINC   3 AMT     2150.00
  POLRPT CROSS-CHECK: IN BALANCE            when TERMPRM matches GLFEED

C.3 Ordering & dependencies

D. Data Files (data dictionary) ↑ top

All files are in library POLARIS, grounded in the DDS/SQL source in src/sources.mjs. Dates are stored as zoned 8S 0 (or the SQL INT/DECIMAL for GLFEED) in YYYYMMDD form — plain integers, not a *DATE type. Money is packed decimal (9P 2 etc.); commission percent is 5P 2; rating factor is 5P 3.

CUSTMST — Customer / insured master (PK CUSTNO)

FieldType (DDS)Meaning
CUSTNO6ACustomer number (unique key).
CNAME25ACustomer name.
ADDR / CITY / ST / ZIP25A / 15A / 2A / 5AMailing address.
DOB8S 0Date of birth (YYYYMMDD) — drives the age band in UWRPGM.

AGTMST — Producer / agent master (PK AGTNO)

FieldTypeMeaning
AGTNO4AAgent number (unique key).
AGTNAME20AAgency name.
COMMPCT5P 2Commission rate percent (e.g. 11.00).
ASTAT1AAgent status (A active).

POLMST — Policy header master (PK POLNO; FK CUSTNO, AGTNO)

FieldTypeMeaning
POLNO8APolicy number (unique key), e.g. POL00001.
CUSTNO6AOwning insured.
AGTNO4AServicing agent (drives commission).
LOB1ALine of business: A auto, H homeowners.
EFFDT / EXPDT8S 0Term effective / expiry date (YYYYMMDD).
PSTAT1AStatus: Q quoted/pending, I inforce, C cancelled, X declined/expired, R renewed.
TERMPRM9P 2Term premium (posted at issue, adjusted by endorsement/renewal/cancel).
POLBAL9P 2Policy balance (decremented by cash applied in CASHPOST).

POLTRN — Policy transaction / audit log (PK TRANID; journaled)

FieldTypeMeaning
TRANID8S 0Arrival-sequence transaction id (unique key).
POLNO8APolicy the transaction belongs to.
TRANTYPE1AI issue, E endorsement, R renewal, C cancellation.
TRANDT8S 0Transaction date (YYYYMMDD).
PREMCHG9P 2Signed premium change (negative on a cancel refund or limit decrease).

POLLF — logical over POLTRN keyed POLNO+TRANID for per-policy transaction history.

COVMST — Coverage / risk items (PK POLNO+COVCD)

FieldTypeMeaning
POLNO / COVCD8A / 3APolicy + coverage code (composite unique key), e.g. BI/COL.
COVLIMIT9P 2Coverage limit (adjudication ceiling in claims).
DEDUCT7P 2Deductible.
COVPREM9P 2Coverage premium (summed as the policy base premium at issue).

COVLF — logical over COVMST keyed POLNO for the POLINQ coverage list.

UWRATE — Underwriting rating factors (PK RATECD)

FieldTypeMeaning
RATECD3ARate code (Y25 / STD / SR) (unique key).
FACTOR5P 3Rating multiplier (e.g. 1.500 / 1.000 / 0.900).
RDESC20ADescription.

BILLDUE — Billing installments (PK POLNO+INSTNO)

FieldTypeMeaning
POLNO / INSTNO8A / 2S 0Policy + installment number 1–4 (composite unique key).
DUEDT8S 0Synthetic due date (EFFDT + (INSTNO-1)×100).
DUEAMT9P 2Installment due (quarterly split; inst 4 absorbs the remainder).
PAIDAMT9P 2Amount paid so far on the installment.
BSTAT1AO open (paid 0), D partially paid, P fully paid.

AGELF — logical over BILLDUE keyed DUEDT+POLNO so the aging pass reads oldest-due-first.

CASHREC — Cash receipts (PK RCPTNO)

FieldTypeMeaning
RCPTNO8S 0Receipt number (unique key).
POLNO8APolicy the receipt is for.
RCPTDT8S 0Receipt date.
CAMT9P 2Cash amount received.

CLMMST — Claim header / FNOL (PK CLMNO)

FieldTypeMeaning
CLMNO8AClaim number (caller-assigned unique key).
POLNO8APolicy the claim is against.
LOSSDT8S 0Loss date.
CAUSE20ACause of loss.
CSTAT1AO open, C closed, D denied.
TOTRES / TOTPAID9P 2Total reserved / total paid.

CLMPMT — Reserve / payment lines (PK PMTID)

FieldTypeMeaning
PMTID8S 0Arrival-sequence line id (unique key).
CLMNO8AOwning claim.
PMTDT8S 0Line date.
PMTAMT9P 2Line amount.
PMTTYPE1AR reserve, P payment.

CLMPLF — logical over CLMPMT keyed CLMNO+PMTID, the source of the CLMINQ subfile.

COMMST — Commission lines (unkeyed, arrival sequence)

FieldTypeMeaning
AGTNO4AAgent earning the commission.
POLNO8APolicy the commission is on.
TRANID8S 0The bound POLTRN transaction id it derives from.
COMMAMT9P 2Commission amount (PREMCHG × COMMPCT/100).

Unkeyed by design (PLAN.md section 2). This arrival-sequence file is where platform finding polaris-c-1 shows up on rerun (section F.5).

GLFEED — GL posting feed (SQL DDL table)

ColumnTypeMeaning
BATCHIDINTEGERPosting batch id.
ACCTCHAR(10)Account: 1200-AR, 4000-PREM, 6100-CEXP, 2100-CPAY.
DRCRCHAR(1)D debit, C credit.
AMTDECIMAL(9,2)Posting amount.
SRCPGMCHAR(10)Source program (GLPOST).

Created via RUNSQLSTM from the GLFEED SQL member — the one table on the SQL-DDL path rather than DDS.

Relationships

E. Operations Runbook ↑ top

E.1 Day-in-the-life

  1. Confirm the build. On a fresh system, CALL PGM(POLARIS/PLSETUP) builds the whole library and journals POLTRN; confirm the joblog shows POLARIS data model ready and that POLTRN is journaled to PLJRN.
  2. Issue new business on demand. For each pending (Q) policy with its coverages on file: CALL POLARIS/POLISSUE PARM('<POLNO>' '<prior-claims>'); confirm POSTED=1 and that POLMST is now PSTAT='I' with a POLTRN 'I' row. (Be aware of finding PG-002 & PG-003 below.)
  3. Handle mid-term changes on demand. POLENDT for endorsements, POLCANCEL for cancellations, each with the appropriate parameters.
  4. Handle claims as they arrive. CLMFNOLCLMADJCLMPAY in order; check each DSPLY line for ACCEPTED/APPROVED/POSTED.
  5. Look policies and claims up online. CALL POLARIS/POLMENUP (or POLINQ/CLMINQ directly) to inspect a policy header + coverages, or a claim header + its reserve/payment subfile.
  6. Run the day-end batch chain. BILLGEN → CASHPOST → COMMCALC → GLPOST (submit to a JOBQ, releasing in that order), then the AGERPT and POLRPT reports.

Pre-checks: confirm the job's library list includes POLARIS; confirm the master data an operator relies on (customers, agents, UWRATE factors) is on file before issuing.

Post-checks after the batch chain:

E.2 Day-end / period close

  1. Confirm all the day's lifecycle and claims events have posted (POLTRN and CLMMST reflect them).
  2. Run the batch chain in dependency order (section C.3): BILLGEN, CASHPOST, COMMCALC, GLPOST.
  3. Run the reports: AGERPT (aged receivables spool) and POLRPT (COBOL inforce premium + GLFEED cross-check).
  4. Reconcile the figures below.
-- GL balance check (must be equal)
SELECT SUM(AMT) FROM POLARIS.GLFEED WHERE DRCR = 'D';
SELECT SUM(AMT) FROM POLARIS.GLFEED WHERE DRCR = 'C';
-- premium-income feed vs inforce term premium (POLRPT does this cross-check)
SELECT SUM(AMT) FROM POLARIS.GLFEED WHERE ACCT = '4000-PREM' AND DRCR = 'C';

Reconciling figures (the same ones the drivers verify against real SQL / decoded PF bytes):

E.3 Failure, re-run & known findings

Each program DSPLYs a one-line result to the joblog; a healthy run shows the expected counts. The table below covers ordinary re-run behavior and the three confirmed emulator findings an operator will actually encounter (full write-up in section F.5).

SituationBehaviourAction
BILLGEN re-runIdempotent — policies already billed are skipped, no duplicate installments.Safe to re-run; expect GENERATED=0.
CASHPOST re-runNot idempotent — no posted-flag on CASHREC, so every receipt re-applies.Clear/replace already-posted receipts (CLRPFM CASHREC) before re-running.
Rejected claim paymentCLMPAY over remaining limit writes no CLMPMT row; TOTPAID unchanged.Correct the amount and retry; DSPLY explains the rejection.
Denied reserveCLMADJ over the coverage limit sets CSTAT='D', TOTRES unchanged, still logs a reserve row.Re-adjudicate within limit if appropriate.
PG-002: issued premium is 0.00POLISSUE's partial-key coverage-sum mis-resolves; TERMPRM lands at 0.00, cascading to 0.00 bills/commission/GL.Known platform finding; app source left unworked-around. Values are internally consistent (screen matches disk). Do not treat as data loss.
PG-003: second POLISSUE loses a prior policyCalling POLISSUE a second time against a different policy in the same job/root corrupts POLMST and can silently drop the earlier policy's row (shared-field key stomp).Issue one policy per job/root, or power-cycle between issues. Confirmed platform finding; do not chain POLISSUE calls in one job.
polaris-c-1: COMMST truncates on rerunRe-running COMMCALC truncates COMMST (unkeyed O-usage file OPENs OUTPUT) instead of appending, so it holds only the latest run's rows.Known platform finding; COMMCALC re-computes correctly each run, but do not rely on cross-run accumulation in COMMST.
Because every bound event is appended to POLTRN and journaled to PLJRN with before/after images, the policy transaction history is fully reconstructable after the fact for reconciliation and recovery — even where a downstream figure is affected by a platform finding, the source event is preserved.

F. Developer Reference ↑ top

The complete program surface, from src/sources.mjs. All objects are in library POLARIS. RPG source is the classic column-exact fixed-form C/D-spec style with embedded /free blocks; COBOL is ILE with COPY SQLCA and EXEC SQL.

F.1 Programs (17 RPG + 1 COBOL)

UWRPGM — RPGLE, callable rating subprogram (dcl-pi)
IN: DOB(8,0), prior-claims(3,0), base-prem(9,2). OUT: rated prem(9,2), status(1) A/D. Hard-declines on claims>3; else age-band (Y25/STD/SR) → CHAIN UWRATE → base × FACTOR. Called by POLISSUE and POLRENEW via dcl-pr ... extpgm('UWRPGM') / CALLP.
POLISSUE (IN POLNO(8), PRIOR-CLAIMS(3))
Idempotent guard (only PSTAT='Q'); real DOB via CHAIN CUSTMST; SETLL/READE COVMST to sum coverage premiums; SETGT *HIVAL/READP POLTRN for the next TRANID; CALLP UWRPGM; on approve UPDATE POLMST (TERMPRM, PSTAT='I') + WRITE POLTRN 'I'; on decline PSTAT='X' + 'I' row with 0 PREMCHG. Restores the shared POLNO after the READP before UPDATE.
POLENDT (IN POLNO(8), COVCD(3), NEWLIMIT(9,2))
Guards PSTAT='I'; KLIST CHAIN COVMST (POLNO+COVCD); NEWPREM = %dech(OLDPREM × NEWLIMIT / OLDLIMIT); UPDATE COVMST + POLMST.TERMPRM by delta; WRITE POLTRN 'E' with signed delta.
POLRENEW (IN CUTOFF(8,0))
SETLL *LOVAL / READ POLMST forward pass; for PSTAT='I' with EXPDT≤CUTOFF: re-sum COVMST, re-CALL UWRPGM, extend term (EFFDT:=EXPDT, EXPDT+=10000), UPDATE POLMST, WRITE POLTRN 'R'. Saves/restores the shared POLNO around the arrival-seq READP.
POLCANCEL (IN POLNO(8), ELAPSED(5,0), TERMDAYS(5,0))
Guards not-already-'C'; UNEARNED = %dech(TERMPRM × (TERMDAYS-ELAPSED) / TERMDAYS); PSTAT='C', TERMPRM:=earned; WRITE POLTRN 'C' with PREMCHG = -UNEARNED.
BILLGEN — RPGLE, no parms
SETLL/READ POLMST; for PSTAT='I', CHAIN(E) BILLDUE inst 1 dup-check, then WRITE 4 installments (TERMPRM/4 via DIV, inst 4 += MVR remainder), BSTAT='O'.
CASHPOST — RPGLE, no parms
READ CASHREC arrival order; per receipt, bounded 1..4 CHAIN(E) BILLDUE to the oldest open/partial installment; full pay → BSTAT='P', partial → BSTAT='D'; UPDATE BILLDUE + CHAIN/UPDATE POLMST.POLBAL by amount applied.
AGERPT — RPGLE, no parms
SETLL *LOVAL / READ AGELF (by due date); for BSTAT≠'P', bucket via WASOF-DUEDT (CURRENT/30/60/90+), accumulate, write AGEDARP DETAIL; TOTLINE grand total; DSPLY bucket totals. Uses nested if/else (not elseif) — see polaris-c-2.
CLMFNOL (IN CLMNO(8), POLNO(8), LOSSDT(8,0), CAUSE(20), COVCD(3), RESAMT(9,2))
CHAIN(E) POLMST, reject if not found / not inforce; SETGT *HIVAL/READP CLMPMT for next PMTID; WRITE CLMMST (CSTAT='O', TOTRES=RESAMT) + CLMPMT 'R'.
CLMADJ (IN CLMNO(8), COVCD(3), PROPOSED(9,2))
CHAIN(E) CLMMST then COVMST (POLNO+COVCD); proposed≤COVLIMIT → APPROVED (TOTRES set, CSTAT='O'), else DENIED (CSTAT='D'); restore CLMNO (shared with CLMPMT, clobbered by the READP) before UPDATE; WRITE CLMPMT 'R' either way.
CLMPAY (IN CLMNO(8), COVCD(3), PMTAMT(9,2))
CHAIN(E) CLMMST then COVMST; remaining = COVLIMIT-TOTPAID; if PMTAMT fits, restore CLMNO, UPDATE TOTPAID, WRITE CLMPMT 'P'; else reject with no row.
COMMCALC — RPGLE, no parms
READ POLTRN; for TRANTYPE I/E, two-hop CHAIN POLMST→AGTMST, COMMAMT = PREMCHG × COMMPCT / 100, WRITE COMMST. (Shared POLNO/AGTNO/TRANID globals carry across the CHAINs.)
GLPOST — RPGLE, no parms, embedded EXEC SQL
Pass 1 POLTRN I/E with PREMCHG≠0 → DR 1200-AR / CR 4000-PREM; pass 2 COMMST → DR 6100-CEXP / CR 2100-CPAY; each pair one EXEC SQL INSERT INTO POLARIS.GLFEED under BATCHID 1.
POLINQ / CLMINQ / POLMENUP — RPGLE WORKSTN
POLINQ EXFMT POLINQF, CHAIN POLMST + loop COVLF (4 lines). CLMINQ EXFMT CLMCTL, CHAIN CLMMST + load CLMSFL subfile from CLMPLF (SFILE RRN counter). POLMENUP EXFMT MENUFMT, indicator-conditioned dynamic CALL to POLINQ/CLMINQ.
POLRPT — ILE COBOL
Indexed FD over POLMST with COMP-3 fields matching the packed DDS byte-for-byte; totals inforce premium by LOB (EVALUATE POL-LOB); EXEC SQL SELECT COUNT/SUM over GLFEED premium-income rows; prints IN/OUT OF BALANCE.

F.2 Files, keys & the shared-field model

A defining characteristic of this application (and the root of two of its platform findings) is RPG's flat externally-described-field namespace: fields with the same name across two open files resolve to a single shared program global. POLNO is declared in POLMST, POLTRN, COVMST, BILLDUE, CASHREC, CLMMST and COMMST; CLMNO in CLMMST and CLMPMT; AGTNO/TRANID across several. Programs exploit this deliberately (after a CHAIN, the shared field already holds the right value for the next file — COMMCALC's two-hop lookup relies on it), but it also means that any positioning op (SETGT *HIVAL/READP over an arrival-sequence PF) clobbers the shared key, which is why POLISSUE/POLENDT/POLRENEW/CLMADJ/CLMPAY explicitly MOVEL the saved key back before their UPDATE/WRITE. These restores are real, correct RPG discipline in the source; where the emulator still diverges despite them, it is logged as a finding (F.5).

F.3 Journaling

POLTRN is journaled to PLJRN (receiver PLJRNRCV) with IMAGES(*BOTH), set up by PLSETUP via CRTJRNRCV/CRTJRN/STRJRNPF. Every RPG WRITE against POLTRN produces an R PT journal entry stamped with the caller job; an UPDATE produces a UB before-image and a UP after-image. DSPJRN JRN(POLARIS/PLJRN) FILE(POLARIS/POLTRN) renders seq/code/type/object/job; CHGJRN rolls to a new receiver and entries keep accumulating across the boundary; ENDJRNPF writes an F JE entry and (only then) DLTJRN can delete the journal (DLTJRN is refused with SFF7021 while the file is still journaled). The whole setup is cleanly re-runnable on every fresh build. This is proven independently in pl_journal.mjs.

F.4 Conventions & DSPLY signatures

Each program's joblog summary line is the operational signal to check. The confirmed formats are:

ProgramDSPLY signature
UWRPGMUWRPGM RATECD=<cd> FACTOR=<f> PREM=<p> / UWRPGM DECLINE CLAIMS=<n>
POLISSUEPOLISSUE POSTED=<n> DECLINE=<n> SKIP=<n>
POLENDTPOLENDT POSTED=<n> DELTA=<amt> REJECT=<n>
POLRENEWPOLRENEW RENEWED=<n> SKIPPED=<n>
POLCANCELPOLCANCEL POSTED=<n> REFUND=<amt> REJECT=<n>
BILLGENBILLGEN GENERATED=<n> SKIPPED=<n>
CASHPOSTCASHPOST FULL=<n> PARTIAL=<n> NOBILL=<n>
AGERPTAGERPT CURRENT=.. D30=.. D60=.. D90PLUS=.. GRAND=..
CLMFNOLCLMFNOL ACCEPTED CLMNO=.. POLNO=.. RESERVE=.. COVCD=.. / REJECTED: ...
CLMADJCLMADJ APPROVED CLMNO=.. RESERVE=.. LIMIT=.. / CLMADJ DENIED ...
CLMPAYCLMPAY POSTED CLMNO=.. AMT=.. TOTPAID=.. REMAIN=.. / REJECTED: EXCEEDS REMAINING LIMIT ...
COMMCALCCOMMCALC POSTED=<n> SKIP=<n> TOTCOMM=<amt>
GLPOSTGLPOST BATCH=<n> ROWS=<n>
POLRPTPOLRPT INFORCE-AUTO/HOME .., GLFEED-PREMINC .., CROSS-CHECK: IN/OUT OF BALANCE

F.5 Confirmed platform findings

POLARIS/i was built to stress the SteelFrame X emulator; its findings log records emulator divergences from real IBM i behavior, left unworked-around in the app source. Three affect what an operator sees:

PG-002 (a.k.a. RPG partial-key SETLL/READE on unique files) — HIGH
A partial-key SETLL/READE over a unique-keyed file (only the leading key supplied) mis-resolves on the emulator, comparing against the full composite key. POLISSUE's coverage-sum loop over COVMST hits this, so an issued policy's TERMPRM lands at 0.00, cascading into 0.00 bills, commission and GL. CASHPOST works around the same behavior with an explicit bounded 1..4 full-key CHAIN; AGERPT exercises the genuine partial-key path over AGELF unworked-around.
PG-003 (RPG-SHAREDFIELD-001, second-POLISSUE corruption) — CRITICAL
Calling POLISSUE a second time in the same job/root against a different policy corrupts POLMST and can silently drop a previously-issued policy's row, root-caused to the shared-field model (POLNO across POLMST/COVMST/POLTRN collapsing onto one storage item, so a later file's positioning stomps an earlier key). Operationally: issue one policy per job, or power-cycle between issues. The lifecycle driver splits phases across fresh roots specifically to avoid re-triggering it.
polaris-c-1 (unkeyed O-usage DISK file truncates on OPEN) — and polaris-c-2 (free-form if/elseif emits one too few END-IF) — MEDIUM
polaris-c-1: an unkeyed arrival-sequence PF opened with RPG usage O compiles to COBOL OPEN OUTPUT and truncates on every CALL instead of appending; COMMST is unkeyed, so re-running COMMCALC leaves only the latest run's rows (the RPG logic still re-computes correctly each run). polaris-c-2: the free-form if/elseif/endif translator emits one fewer END-IF than opened when a chain has any elseif, silently orphaning scope; the app avoids it by using nested if/else throughout (AGERPT's bucketing, POLMENUP's option routing) — not to dodge the bug, but because nested if/else is an equally idiomatic construct proven correct in isolation.

All are documented in the app's own BUGS-AND-GAPS.md with minimal standalone repros; the emulator source (ibmi/*.js) is left unmodified.

G. Glossary ↑ top

Adjudication
Deciding a claim reserve against the coverage limit (CLMADJ): approve within limit (CSTAT stays open), deny over limit (CSTAT='D').
Aging / aged receivables
Classifying outstanding installments by how overdue they are — CURRENT / 30 / 60 / 90+ — against an as-of date (AGERPT, over the AGELF logical keyed by due date).
Bound transaction
A POLTRN transaction that creates or changes premium under a policy: issue ('I') or endorsement ('E'). Commissions and the GL premium feed are computed from bound transactions.
Coverage / coverage code (COVMST, COVCD)
One insured peril line on a policy (e.g. BI, PD, COL for auto; DWELL/CONTENTS/LIAB for home), each with a limit, deductible and premium. A policy is a header plus N coverage rows.
Endorsement
A mid-term change to a policy (POLENDT) — here a coverage-limit change that re-rates the premium and posts a signed 'E' transaction.
FNOL — First Notice Of Loss
The intake event that opens a claim (CLMFNOL): validates the policy is inforce, records cause and loss date, sets an initial reserve.
Idempotent
Safe to run again with the same result. BILLGEN is idempotent (dup-check on installment 1); CASHPOST is not (no posted-flag on receipts).
Inforce (PSTAT='I')
A bound, active policy. Only inforce policies are billed, renewed, endorsed, and can have claims opened against them.
Installment (BILLDUE)
One of the four quarterly premium instalments BILLGEN generates per inforce policy; instalments 1–3 are TERMPRM/4 and instalment 4 absorbs the truncation remainder.
Journaling (PLJRN, IMAGES(*BOTH))
Capturing every WRITE (R PT) and UPDATE (UB before-image + UP after-image) against POLTRN, with the caller job stamp — the audit/recovery trail for the transaction log.
LOB — Line Of Business
The product line of a policy: A (auto) or H (homeowners). POLRPT totals inforce premium by LOB.
Pro-rated / unearned premium
On cancellation (POLCANCEL), the portion of the term premium not yet earned, refunded as a negative PREMCHG: UNEARNED = TERMPRM × (TERMDAYS-ELAPSED) / TERMDAYS.
Rate tier / rating factor (UWRATE)
An age-band multiplier (Y25=1.500 under 25, STD=1.000, SR=0.900 senior) applied to the base premium in underwriting.
SBMJOB / *JOBQ
Submit Job / a job queue — the IBM i idiom for queuing a batch program (e.g. the day-end chain onto POLARIS/NITEQ). The queue is FIFO-within-priority; ordering across dependent jobs is the scheduler's job.
Shared field (externally-described)
An RPG field of the same name across two open files resolves to one shared program global. Exploited for cross-file lookups, and the reason positioning ops must restore a saved key before UPDATE/WRITE.
Subfile (SFL/SFLCTL)
A 5250 display construct listing many rows on one screen. CLMINQ's claim-payment list is the app's one subfile (CLMSFL under CLMCTL, SFLPAG(10)).
Waterfall (cash application)
CASHPOST applies a receipt to the single oldest open/partial installment, marking it fully paid or partially paid; excess is not carried forward (a deliberately simple, documented convention).