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.
POLARIS/i administers the life of a personal-lines P&C policy across nine functional areas:
UWRPGM) reads an age-band rating
factor from UWRATE, applies it to a base premium, and hard-declines on more than three
prior at-fault claims.POLISSUE takes a pending (quoted) policy header plus its
coverage rows, sums the coverage premiums as the base, calls underwriting, and either binds the
policy (PSTAT='I', term premium posted, a POLTRN 'I' row written) or marks
it declined.POLENDT endorses a coverage limit, re-rates the premium
proportionally, and writes a signed POLTRN 'E' delta (positive or negative).POLRENEW batch-scans inforce policies at/before a cutoff
expiry date, extends the term, re-rates, and writes a 'R' transaction.POLCANCEL pro-rates the unearned premium and writes a
negative-refund 'C' transaction.BILLGEN generates four quarterly installments
per inforce policy (idempotently), CASHPOST applies cash receipts oldest-due-first with
partial-pay awareness, and AGERPT ages the outstanding installments into CURRENT/30/60/90+
buckets on a spooled report.CLMFNOL takes first notice of loss and opens the claim with an
initial reserve, CLMADJ adjudicates a proposed reserve against the coverage limit
(approve/deny), and CLMPAY posts a payment against the remaining limit.COMMCALC reads bound transactions from POLTRN,
looks up the servicing agent's rate, and writes one COMMST row per bound transaction.GLPOST consolidates premium and commission
activity into balanced DR/CR rows in the GLFEED DB2 table; the ILE COBOL
POLRPT totals inforce premium by line of business and cross-checks it against GLFEED.Unlike a SQL-PL-centric application, POLARIS/i keeps its business logic in the programs themselves. The layering is:
QDDSSRC (customer, agent,
policy, transaction log, coverage, rating, billing, cash, claims, commission) plus one SQL-DDL DB2
table, GLFEED, created via RUNSQLSTM — deliberately exercising both the
DDS-PF path and the SQL-DDL path. The audit-critical POLTRN file is journaled to
PLJRN.CHAIN/SETLL/READE file access, packed-decimal arithmetic,
program-to-program CALL (POLISSUE→UWRPGM), embedded
EXEC SQL (GLPOST inserts, POLRPT selects), and 5250
EXFMT interaction. There is no separate stored-procedure tier; the RPG is the
logic.PLSETUP builds every object (CRTPF/CRTLF/
CRTDSPF/CRTPRTF/CRTBNDRPG/CRTBNDCBL/CRTCLPGM/RUNSQLSTM) and sets up journaling in one run. There is
no single packaged "day-end" CL in the committed source (see the honest note in section C);
operators/schedulers drive the batch programs individually.Everything runs in library POLARIS. The tested jobs run with
LIBL = QSYS QGPL POLARIS QTEMP and CURLIB = POLARIS.
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.
| Object | Type | Role |
|---|---|---|
| CUSTMST | PF | Customer / insured master. |
| AGTMST | PF | Producer / agent master (commission plan). |
| POLMST | PF | Policy header master (the heart of the app). |
| POLTRN / POLLF | PF / LF | Policy transaction/audit log (journaled) + per-policy LF. |
| COVMST / COVLF | PF / LF | Coverage/risk items + per-policy LF for subfile display. |
| UWRATE | PF | Underwriting rating-factor table. |
| BILLDUE / AGELF | PF / LF | Billing installments + aging LF keyed by due date. |
| CASHREC | PF | Cash receipts (arrival sequence). |
| CLMMST | PF | Claim header (FNOL). |
| CLMPMT / CLMPLF | PF / LF | Reserve/payment lines + per-claim LF for the subfile. |
| COMMST | PF | Commission line per bound transaction (unkeyed). |
| GLFEED | SQL table | Balanced DR/CR GL posting feed (DB2 DDL). |
| POLDSPF / CLMDSPF / POLMENU | DSPF | Policy inquiry / claim inquiry (SFL) / operator menu. |
| POLREGP / AGEDARP / CLMSTATP | PRTF | Policy register / aged receivables / open claims printer files. |
| UWRPGM | RPGLE | Callable underwriting rating subprogram. |
| POLISSUE / POLENDT / POLRENEW / POLCANCEL | RPGLE | Policy lifecycle drivers. |
| BILLGEN / CASHPOST / AGERPT | RPGLE | Billing / cash / aging. |
| CLMFNOL / CLMADJ / CLMPAY | RPGLE | Claims intake / adjudication / payment. |
| COMMCALC / GLPOST | RPGLE | Commission calc / GL feed (embedded SQL). |
| POLINQ / CLMINQ / POLMENUP | RPGLE | Interactive inquiry + menu programs. |
| POLRPT | CBLLE | ILE COBOL inforce-premium report + GLFEED cross-check. |
| PLSETUP | CLP | Build-everything + journaling CL driver. |
| PLJRN / PLJRNRCV | JRN / JRNRCV | Journal + 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.
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 this | Type on the command line |
|---|---|
| Open the operator menu (routes to the two inquiry screens) | CALL POLARIS/POLMENUP |
| Open policy inquiry directly | CALL POLARIS/POLINQ |
| Open claim inquiry (subfile) directly | CALL POLARIS/CLMINQ |
| Issue a pending policy | CALL POLARIS/POLISSUE PARM('POL00001' '000') |
| Endorse a coverage limit | CALL POLARIS/POLENDT PARM('POL00006' 'BI ' '75000') |
| Cancel a policy (elapsed / term days) | CALL POLARIS/POLCANCEL PARM('POL00006' '00090' '00365') |
| Take first notice of loss | CALL 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).
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.
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 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
DCOV1–DCOV4 as "COVCD LIMIT DEDUCT PREMIUM".
| Field | Type (DDS) | Shows |
|---|---|---|
| IPOLNO | 8A input/output | Policy number keyed by the operator. |
| DNAME | 25A output | Insured (rendered from CUSTNO). |
| DLOB / DSTAT | 1A output | Line of business / policy status. |
| DPREM / DBAL | 14A output | Term premium / policy balance. |
| DCOV1–DCOV4 | 30A output | Up to four coverage lines from COVLF. |
| DMSG | 50A output | Status line ("Policy found." / "Policy not found: ..."). |
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.
| Field | Type (DDS) | Shows |
|---|---|---|
| ICLMNO | 8A input/output | Claim number keyed by the operator. |
| DPOLNO / DLOSSDT / DCAUSE / DCSTAT | output | Header: policy, loss date, cause, status. |
| DTOTRES / DTOTPAID | 14A output | Reserved and paid totals. |
| SPMTDT / SPMTTYP / SPMTAMT | SFL output | Per-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.
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:
POLTRN with its premium
change (PREMCHG, signed: negative on a cancellation refund or a limit decrease). This is
the append-only history a core system relies on instead of only mutating the header.POLTRN is journaled, so every WRITE against it
(an R PT entry) and any UPDATE (UB before-image + UP after-image) is captured with the caller job
stamp — a genuine, replayable audit trail proven by the pl_journal.mjs driver.POLISSUE only issues policies still PSTAT='Q' (idempotent guard);
POLENDT requires PSTAT='I'; POLCANCEL rejects an
already-cancelled policy; CLMFNOL rejects a policy that is not inforce; CLMADJ
denies a reserve over the coverage limit; CLMPAY rejects a payment over the remaining
limit.COVMST coverage limit (via a POLNO+COVCD CHAIN), not merely accepted.GLPOST posts one DR and one matching CR per
transaction under the same BATCHID, so SUM(DR)=SUM(CR) for the batch is true
by construction and independently cross-checked by SQL and by the COBOL POLRPT.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.
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.
PLDAYEND CL that chained the batch programs, but no PLDAYEND member exists in
the committed source — PLSETUP 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
| Program | Purpose | Reads / calls | Inputs | Outputs | Frequency |
|---|---|---|---|---|---|
| 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. |
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
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.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.
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
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).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
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
POLTRN bound transactions, so the issue/endorse/renew/cancel events must be posted
first.NOBILL for a receipt with no open installment.COMMST, which COMMCALC
writes; run COMMCALC first so the commission half of the GL batch is present.AGERPT after billing/cash so aging reflects the day, and
POLRPT after GLPOST so the cross-check has the premium feed.*JOBQ is FIFO-within-priority and does
not gate on predecessors; the scheduler/operator enforces the order (release-and-wait), as the
day-cycle driver does.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.
| Field | Type (DDS) | Meaning |
|---|---|---|
| CUSTNO | 6A | Customer number (unique key). |
| CNAME | 25A | Customer name. |
| ADDR / CITY / ST / ZIP | 25A / 15A / 2A / 5A | Mailing address. |
| DOB | 8S 0 | Date of birth (YYYYMMDD) — drives the age band in UWRPGM. |
| Field | Type | Meaning |
|---|---|---|
| AGTNO | 4A | Agent number (unique key). |
| AGTNAME | 20A | Agency name. |
| COMMPCT | 5P 2 | Commission rate percent (e.g. 11.00). |
| ASTAT | 1A | Agent status (A active). |
| Field | Type | Meaning |
|---|---|---|
| POLNO | 8A | Policy number (unique key), e.g. POL00001. |
| CUSTNO | 6A | Owning insured. |
| AGTNO | 4A | Servicing agent (drives commission). |
| LOB | 1A | Line of business: A auto, H homeowners. |
| EFFDT / EXPDT | 8S 0 | Term effective / expiry date (YYYYMMDD). |
| PSTAT | 1A | Status: Q quoted/pending, I inforce, C cancelled, X declined/expired, R renewed. |
| TERMPRM | 9P 2 | Term premium (posted at issue, adjusted by endorsement/renewal/cancel). |
| POLBAL | 9P 2 | Policy balance (decremented by cash applied in CASHPOST). |
| Field | Type | Meaning |
|---|---|---|
| TRANID | 8S 0 | Arrival-sequence transaction id (unique key). |
| POLNO | 8A | Policy the transaction belongs to. |
| TRANTYPE | 1A | I issue, E endorsement, R renewal, C cancellation. |
| TRANDT | 8S 0 | Transaction date (YYYYMMDD). |
| PREMCHG | 9P 2 | Signed premium change (negative on a cancel refund or limit decrease). |
POLLF — logical over POLTRN keyed POLNO+TRANID for per-policy
transaction history.
| Field | Type | Meaning |
|---|---|---|
| POLNO / COVCD | 8A / 3A | Policy + coverage code (composite unique key), e.g. BI/COL. |
| COVLIMIT | 9P 2 | Coverage limit (adjudication ceiling in claims). |
| DEDUCT | 7P 2 | Deductible. |
| COVPREM | 9P 2 | Coverage premium (summed as the policy base premium at issue). |
COVLF — logical over COVMST keyed POLNO for the POLINQ
coverage list.
| Field | Type | Meaning |
|---|---|---|
| RATECD | 3A | Rate code (Y25 / STD / SR) (unique key). |
| FACTOR | 5P 3 | Rating multiplier (e.g. 1.500 / 1.000 / 0.900). |
| RDESC | 20A | Description. |
| Field | Type | Meaning |
|---|---|---|
| POLNO / INSTNO | 8A / 2S 0 | Policy + installment number 1–4 (composite unique key). |
| DUEDT | 8S 0 | Synthetic due date (EFFDT + (INSTNO-1)×100). |
| DUEAMT | 9P 2 | Installment due (quarterly split; inst 4 absorbs the remainder). |
| PAIDAMT | 9P 2 | Amount paid so far on the installment. |
| BSTAT | 1A | O open (paid 0), D partially paid, P fully paid. |
AGELF — logical over BILLDUE keyed DUEDT+POLNO so the aging
pass reads oldest-due-first.
| Field | Type | Meaning |
|---|---|---|
| RCPTNO | 8S 0 | Receipt number (unique key). |
| POLNO | 8A | Policy the receipt is for. |
| RCPTDT | 8S 0 | Receipt date. |
| CAMT | 9P 2 | Cash amount received. |
| Field | Type | Meaning |
|---|---|---|
| CLMNO | 8A | Claim number (caller-assigned unique key). |
| POLNO | 8A | Policy the claim is against. |
| LOSSDT | 8S 0 | Loss date. |
| CAUSE | 20A | Cause of loss. |
| CSTAT | 1A | O open, C closed, D denied. |
| TOTRES / TOTPAID | 9P 2 | Total reserved / total paid. |
| Field | Type | Meaning |
|---|---|---|
| PMTID | 8S 0 | Arrival-sequence line id (unique key). |
| CLMNO | 8A | Owning claim. |
| PMTDT | 8S 0 | Line date. |
| PMTAMT | 9P 2 | Line amount. |
| PMTTYPE | 1A | R reserve, P payment. |
CLMPLF — logical over CLMPMT keyed CLMNO+PMTID, the source
of the CLMINQ subfile.
| Field | Type | Meaning |
|---|---|---|
| AGTNO | 4A | Agent earning the commission. |
| POLNO | 8A | Policy the commission is on. |
| TRANID | 8S 0 | The bound POLTRN transaction id it derives from. |
| COMMAMT | 9P 2 | Commission 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).
| Column | Type | Meaning |
|---|---|---|
| BATCHID | INTEGER | Posting batch id. |
| ACCT | CHAR(10) | Account: 1200-AR, 4000-PREM, 6100-CEXP, 2100-CPAY. |
| DRCR | CHAR(1) | D debit, C credit. |
| AMT | DECIMAL(9,2) | Posting amount. |
| SRCPGM | CHAR(10) | Source program (GLPOST). |
Created via RUNSQLSTM from the GLFEED SQL member — the one
table on the SQL-DDL path rather than DDS.
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.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.)POLENDT for endorsements,
POLCANCEL for cancellations, each with the appropriate parameters.CLMFNOL → CLMADJ →
CLMPAY in order; check each DSPLY line for ACCEPTED/APPROVED/POSTED.CALL POLARIS/POLMENUP (or POLINQ/CLMINQ
directly) to inspect a policy header + coverages, or a claim header + its reserve/payment subfile.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:
BILLGEN GENERATED=n SKIPPED=m — n equals the inforce policies not yet
billed this term; a rerun should show GENERATED=0 (idempotent).CASHPOST FULL=n PARTIAL=n NOBILL=n — NOBILL flags receipts with no
open installment; POLMST.POLBAL decremented only by what was actually applied.COMMCALC POSTED=n SKIP=m TOTCOMM=amt — one commission per bound (I/E) transaction;
non-bound types skipped.GLPOST BATCH=1 ROWS=n then verify SUM(DR)=SUM(CR) in GLFEED (below).BILLGEN, CASHPOST,
COMMCALC, GLPOST.AGERPT (aged receivables spool) and POLRPT (COBOL inforce
premium + GLFEED cross-check).-- 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):
SUM(DR) = SUM(CR) over the whole batch, true by construction
(one DR + one CR per transaction). Even when PG-002 drives premiums to 0.00, the batch is still
balanced (both sides 0.00).SUM(AMT) WHERE ACCT='4000-PREM' AND DRCR='C' equals
the sum of bound premium changes fed by GLPOST (e.g. 1200 + 800 + 150 = 2150.00).SUM(AMT) WHERE ACCT='2100-CPAY' AND DRCR='C'
equals total commission (e.g. 120 + 100 + 15 = 235.00).INFORCE-AUTO/INFORCE-HOME
totals equal SUM(TERMPRM) over PSTAT='I' policies of each LOB; Q/C/X policies excluded.PREMCHG is in the GL feed but POLMST.TERMPRM was not updated in the same seed —
the cross-check is proving the COBOL EXEC SQL bridge really computes, not that the books are wrong.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).
| Situation | Behaviour | Action |
|---|---|---|
| BILLGEN re-run | Idempotent — policies already billed are skipped, no duplicate installments. | Safe to re-run; expect GENERATED=0. |
| CASHPOST re-run | Not idempotent — no posted-flag on CASHREC, so every receipt re-applies. | Clear/replace already-posted receipts (CLRPFM CASHREC) before re-running. |
| Rejected claim payment | CLMPAY over remaining limit writes no CLMPMT row; TOTPAID unchanged. | Correct the amount and retry; DSPLY explains the rejection. |
| Denied reserve | CLMADJ 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.00 | POLISSUE'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 policy | Calling 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 rerun | Re-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. |
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.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.
base × FACTOR. Called
by POLISSUE and POLRENEW via dcl-pr ... extpgm('UWRPGM') / CALLP.NEWPREM = %dech(OLDPREM × NEWLIMIT /
OLDLIMIT); UPDATE COVMST + POLMST.TERMPRM by delta; WRITE POLTRN 'E' with signed delta.UNEARNED = %dech(TERMPRM × (TERMDAYS-ELAPSED) / TERMDAYS);
PSTAT='C', TERMPRM:=earned; WRITE POLTRN 'C' with PREMCHG = -UNEARNED.COMMAMT = PREMCHG ×
COMMPCT / 100, WRITE COMMST. (Shared POLNO/AGTNO/TRANID globals carry across the CHAINs.)EXEC SQL INSERT INTO POLARIS.GLFEED under BATCHID 1.EXEC SQL SELECT COUNT/SUM over GLFEED premium-income
rows; prints IN/OUT OF BALANCE.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).
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.
Each program's joblog summary line is the operational signal to check. The confirmed formats are:
| Program | DSPLY signature |
|---|---|
| UWRPGM | UWRPGM RATECD=<cd> FACTOR=<f> PREM=<p> / UWRPGM DECLINE CLAIMS=<n> |
| POLISSUE | POLISSUE POSTED=<n> DECLINE=<n> SKIP=<n> |
| POLENDT | POLENDT POSTED=<n> DELTA=<amt> REJECT=<n> |
| POLRENEW | POLRENEW RENEWED=<n> SKIPPED=<n> |
| POLCANCEL | POLCANCEL POSTED=<n> REFUND=<amt> REJECT=<n> |
| BILLGEN | BILLGEN GENERATED=<n> SKIPPED=<n> |
| CASHPOST | CASHPOST FULL=<n> PARTIAL=<n> NOBILL=<n> |
| AGERPT | AGERPT CURRENT=.. D30=.. D60=.. D90PLUS=.. GRAND=.. |
| CLMFNOL | CLMFNOL ACCEPTED CLMNO=.. POLNO=.. RESERVE=.. COVCD=.. / REJECTED: ... |
| CLMADJ | CLMADJ APPROVED CLMNO=.. RESERVE=.. LIMIT=.. / CLMADJ DENIED ... |
| CLMPAY | CLMPAY POSTED CLMNO=.. AMT=.. TOTPAID=.. REMAIN=.. / REJECTED: EXCEEDS REMAINING LIMIT ... |
| COMMCALC | COMMCALC POSTED=<n> SKIP=<n> TOTCOMM=<amt> |
| GLPOST | GLPOST BATCH=<n> ROWS=<n> |
| POLRPT | POLRPT INFORCE-AUTO/HOME .., GLFEED-PREMINC .., CROSS-CHECK: IN/OUT OF BALANCE |
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:
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.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.
UNEARNED = TERMPRM × (TERMDAYS-ELAPSED) / TERMDAYS.POLARIS/NITEQ). The queue is FIFO-within-priority; ordering across dependent jobs is the
scheduler's job.