AQUILA/i is a combined electric / water / gas utilities billing core: customer and
service-point/meter masters, monthly meter reads, tiered/stepped-rate billing with a printed bill,
cash receipts, delinquency & disconnect processing with a matching reconnect arc, late-fee and
interest accrual, and a balanced GL posting feed. It is a native AS/400 estate — DDS physical
and logical files, RPG (fixed-form and free-form) and ILE COBOL programs, one DB2 SQL table, CL for
build and night batch, 5250 display files (including a subfile), a printer file, journaling and an
object-authority restriction. This manual is the reference for the operator who runs the online
screens and the night-batch chain, and for the developer maintaining the application. It is grounded
entirely in the committed source (utilities-app/src/sources.mjs in library
AQUILA, src/seed.mjs, and the test/aquila_*.mjs drivers).
AQUILA/i runs the monthly cycle of a combined-service (electric / water / gas) utility:
UTCUST) and physical meters
(UTMETER) through one callable create/change/inquire program (UTMNT),
against a service-point master (UTSVCPT) that links a customer to a utility type,
rate class, meter and running balance.UTREAD) applies the stepped rate plan (UTRATE): each read is billed
tier-by-tier (a fixed base charge plus per-tier unit prices), writing a bill header
(UTBILL), one detail line per tier (UTBLIN), and an audit row
(UTLEDG).UTBILLP) spool of each bill: header,
tier detail lines, total, due date.UTCASH) are applied to the oldest open
bill for a service point, partial or full, updating the bill and service-point balances and
journaling a payment.UTGLF) under commitment control, committed only if
debits equal credits, with an ILE COBOL cross-check report (UTGLRPT).Unlike a two-layer SQL-PL application, AQUILA/i is a classic native IBM i estate: the
business logic lives in the RPG and COBOL programs themselves, operating directly on DDS record-level
files through keyed and arrival I/O (CHAIN, SETLL/READ,
WRITE/UPDATE). Only two paths use embedded SQL — the GL posting program
UTGLPST (DB2 INSERT/SUM under commitment control) and the COBOL
report UTGLRPT (a cross-check SUM). Everything else is record-level RPG/COBOL.
7P4 unit prices to 15P4 interest
accumulators) is exercised throughout the billing and fee math.UTSETUP builds every object, wires journaling and an
authority restriction, and seeds the rate plan; UTNIGHT documents the night chain.
In practice each night-batch step is submitted as a separate SBMJOB so each
program closes its output files fully before the next opens them.Everything runs in library AQUILA (data root
utilities-app/ibmidata-aquila*). The tested jobs run with
LIBL = QSYS QGPL AQUILA QTEMP and CURLIB = AQUILA as QSECOFR.
MAINTENANCE ONLINE (5250) BATCH (night chain, SBMJOB *JOBQ)
----------- ------------- --------------------------------
UTMNT (CC/MC/CI/MI) UTMENU UTBLGEN --> UTBILL + UTBLIN + UTLEDG'B'
UTRATLD (rate plan) 1 -> UTACCTIQ UTCSHPST --> pay oldest bill, UTLEDG'P'
2 -> UTBILLIQ (SFL) UTDELINQ --> flag D, disconnect, UTLEDG'A'
UTFEECAL --> late fee+interest, UTLEDG'F'
UTGLPST --> balanced DR/CR into UTGLF (DB2)
UTBILLPR --> bill spool (UTBILLP)
reference: UTRATE (RATECD+TIERNO) UTRECON --> reconnect paid-up SP, UTLEDG'R'
| UTAGE --> aged-AR buckets (report)
v UTGLRPT --> COBOL cross-check (IN/OUT OF BALANCE)
UTREAD (meter reads, journaled) --UTBLGEN--> UTBILL --UTAGELF(by DUEDT)--> UTDELINQ/UTFEECAL/UTAGE
| \
+--> every posting program APPENDS a keyed row to UTLEDG (journaled A/R audit trail)
A single billing event flows: meter reads land in UTREAD (journaled) →
UTBLGEN walks each read against the UTRATE tiers, computes the whole bill in
working storage, then writes UTBLIN detail lines, the UTBILL header, rolls up
UTSVCPT.SPBAL, and appends a UTLEDG 'B' audit row. Downstream programs read
bills through the aging logical file UTAGELF (keyed by DUEDT).
| Object | Type | Role |
|---|---|---|
| UTCUST | PF | Customer master. |
| UTSVCPT | PF | Service-point master (customer + utility type + rate + meter + balance). |
| UTMETER | PF | Physical meter master. |
| UTRATE | PF | Tiered/stepped rate plan (keyed RATECD+TIERNO). |
| UTREAD | PF (journaled) | Meter reads. |
| UTBILL | PF | Bill header. |
| UTBLIN | PF | Bill tier-detail lines (keyed BILLNO+LINENO). |
| UTCASH | PF | Cash receipts. |
| UTLEDG | PF (journaled) | A/R ledger / audit trail (keyed by LEDGSEQ). |
| UTBLINL | LF | UTBLIN keyed by BILLNO (subfile source). |
| UTAGELF | LF | UTBILL keyed by DUEDT (aging pass). |
| UTGLF | DB2 table | GL posting feed (SQL DDL via RUNSQLSTM). |
| UTACCTD / UTBILLD / UTMENUD | DSPF | Account inquiry / bill-inquiry subfile / operator menu. |
| UTBILLP | PRTF | The printed bill. |
| UTRATLD | RPG | Seed the tiered rate plan. |
| UTMNT | RPG | Customer/meter master maintenance (callable, dcl-pi). |
| UTBLGEN | RPG | Tiered billing engine. |
| UTCSHPST | RPG | Cash posting (partial-pay aware). |
| UTDELINQ | RPG | Delinquency / disconnect. |
| UTFEECAL | RPG | Late-fee + interest accrual. |
| UTRECON | RPG | Reconnect a paid-up disconnected service point. |
| UTGLPST | SQLRPGLE | GL posting under commitment control (**FREE + embedded SQL). |
| UTBILLPR | RPG | Bill print (PRTF spool). |
| UTAGE | RPG | Aged-AR report. |
| UTACCTIQ / UTBILLIQ / UTMENU | RPG (WORKSTN) | Account inquiry / bill-inquiry subfile / menu. |
| UTGLRPT | ILE COBOL | GL cross-check report (COMP-3 FD + embedded SQL). |
| UTSETUP / UTNIGHT | CL | Build/journal/authority/seed · night-batch chain. |
| UTJRN / UTRCV | *JRN / *JRNRCV | Journal + receiver over UTREAD and UTLEDG. |
The catalogue is 8 PFs + 2 LFs + 1 DB2 table, 3 DSPFs + 1 PRTF, 13 RPG programs + 1 ILE COBOL + 2 CL, plus a journal/receiver pair. Sections D and F expand each.
AQUILA/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 AQUILA — the
tested jobs run with LIBL = QSYS QGPL AQUILA QTEMP and CURLIB = AQUILA.
| To do this | Type on the command line |
|---|---|
| Open the operator menu (routes to the two inquiries) | CALL AQUILA/UTMENU |
| Account inquiry directly | CALL AQUILA/UTACCTIQ |
| Bill inquiry (subfile) directly | CALL AQUILA/UTBILLIQ |
| Maintain a customer or meter | CALL AQUILA/UTMNT (with parameters — see below) |
| Build the whole app + seed the rate plan | CALL AQUILA/UTSETUP |
| Run one night-batch step | CALL AQUILA/UTBLGEN (or SBMJOB it) |
UTMNT is the one online program that is parameter-driven rather than screen-driven:
it is called with a dcl-pi entry list so a driver or the day-cycle can maintain masters
without a green screen. Its first parameter pAction selects the operation:
| pAction | Does | Key parameters used |
|---|---|---|
| CC | Create/change a customer. | pKey (first 6 = CUSTNO), pName, pCity, pState. |
| MC | Create/change a meter. | pKey (METERNO), pMSpno, pMUtype, pMMult. |
| CI | Inquire a customer. | pKey; returns pName/pCity/pState, pOutFound. |
| MI | Inquire a meter. | pKey; returns pMSpno/pMUtype/pMMult, pOutFound. |
UTMNT reports its result in pOutFound ('Y'/'N') and a 30-char
pOutMsg (e.g. CUSTOMER CREATED, METER CHANGED,
CUSTOMER NOT FOUND, BAD ACTION), and also DSPLYs one line so a batch driver
can assert on it without a screen. For a change it re-CHAINs the full key immediately
before the UPDATE so it never double-updates a stale read.
The interactive front end is three WORKSTN programs over three display files. UTMENU
(format MENUFMT) offers two options and dynamically CALLs the matching inquiry program;
an invalid option shows Invalid option and neither program is called;
F3 ends the menu.
A plain (non-subfile) screen. Key a service point in ISPNO, Enter:
UTACCTIQ CHAINs UTSVCPT, then UTCUST for the name, and
shows the customer, utility type, rate class, meter number, status and balance. A blank key prompts
Enter a service point.; an unknown one shows Service point not found: <spno>.
The application's one subfile screen. Key a bill number in IBILLNO,
Enter: UTBILLIQ CHAINs UTBILL for the
header (service point, cycle, usage, status, total charge), then loads the bill's tier lines from
UTBLINL (the LF over UTBLIN keyed by BILLNO) into the
BSFL subfile via SETLL/READE, and displays them under control
record BCTL (SFLPAG(10), SFLSIZ(20)). Indicators drive the
subfile: 41=SFLDSP, 42=SFLDSPCTL, 43=SFLCLR,
44=SFLEND(*MORE). A blank key prompts Enter a bill number.; an unknown one
shows Bill not found: <billno> and the subfile is cleared.
| Field | Type (DDS) | Shows |
|---|---|---|
| SLINENO | 2Y 0 O | Bill line number. |
| STIER | 2Y 0 O | Tier number (LTIERNO). |
| SUSE | 9Y 0 O | Usage billed in this tier (TIERUSE). |
| SPRC | 14A O | Per-tier unit price (TIERPRC, rendered as char). |
| SAMT | 14A O | Tier line amount (LINEAMT, rendered as char). |
SLINENO/STIER/SUSE
carry no EDTCDE/EDTWRD keyword, so 5250 renders them as raw
zero-padded digits (e.g. usage 500 shows as 000000500, a zero-usage tier as
000000000) with no zero-suppression — the correct DDS rule for an unedited numeric
output field. Price and amount are pre-formatted with %char() into character fields.Honest statement: AQUILA/i does not model a four-eyes maker–checker /
separate-authorization workflow. There is no "one clerk posts, a second approves" step; a cash
receipt is applied immediately by UTCSHPST, and delinquency/disconnect/reconnect happen
automatically in the night batch. The control model the application does have is:
B bill (UTBLGEN), P payment (UTCSHPST), A
delinquency/disconnect adjustment (UTDELINQ), F late fee/interest (UTFEECAL),
R reconnect (UTRECON). Each posting program draws LEDGSEQ from a
disjoint numeric band (see F.3) so keys never collide and the ledger accumulates across
the whole night into one durable trail. A full lifecycle reads B → P → A → R.UTREAD and UTLEDG are journaled
(STRJRNPF … IMAGES(*BOTH) to UTJRN/UTRCV), so meter
reads and every ledger write/update produce before/after journal images inspectable with
DSPJRN/WRKJRNA.UTSETUP revokes
*PUBLIC *ALL on UTLEDG and grants only *CHANGE — a
deliberate authority restriction scoped to exactly what the batch posting path needs (read + add
+ update), exercising RVKOBJAUT/GRTOBJAUT.BSTAT IN ('O','D') with a positive balance; delinquency flags only open bills with a
positive balance; reconnect refuses a service point that still owes (SPBAL > 0);
fee assessment runs only for BSTAT='D' bills.UTGLPST commits its DB2 posting only if
debits equal credits, otherwise ROLLBACK; UTGLRPT (COBOL) independently
re-sums and reports IN BALANCE / OUT OF BALANCE.In sum, the control posture is immutable audit + journaling + object authority + state gating + a balanced control total, all in the data/logic layer, rather than a segregation-of-duties approval workflow.
AQUILA/i's billing cycle runs as a night-batch chain: bill generation, cash posting,
delinquency/disconnect, fee assessment, GL posting and bill print, with reconnect and the aged-AR
report as adjuncts. None of the batch programs take CALL parameters — each opens the DDS files it
needs, makes one pass, and closes them. Because rows written to an output-opened file may not be visible
to a called subprogram until the writer closes it, the chain is not one CL calling
several programs in-process; each step is a separate SBMJOB on a *JOBQ, so
each program fully closes its output before the next opens it.
-- create the night-batch job queue, then submit the steps in order
CRTJOBQ JOBQ(AQUILA/NITEQ) TEXT('AQUILA night batch queue')
SBMJOB CMD(CALL AQUILA/UTBLGEN) JOB(AQBLGEN) JOBQ(AQUILA/NITEQ) HOLD(*NO)
SBMJOB CMD(CALL AQUILA/UTCSHPST) JOB(AQCASH) JOBQ(AQUILA/NITEQ) HOLD(*NO)
SBMJOB CMD(CALL AQUILA/UTDELINQ) JOB(AQDLQ) JOBQ(AQUILA/NITEQ) HOLD(*NO)
SBMJOB CMD(CALL AQUILA/UTFEECAL) JOB(AQFEE) JOBQ(AQUILA/NITEQ) HOLD(*NO)
SBMJOB CMD(CALL AQUILA/UTGLPST) JOB(AQGLP) JOBQ(AQUILA/NITEQ) HOLD(*NO)
SBMJOB CMD(CALL AQUILA/UTBILLPR) JOB(AQPRT) JOBQ(AQUILA/NITEQ) HOLD(*NO)
The CL member UTNIGHT documents the same sequence as a single entry point
(BILLGEN → CASH POST → DELINQUENCY → FEE → GL POST → bill print); the tested
harness drives them as the separate SBMJOBs above.
| Program | Purpose | Files read/written | Ledger | Outputs (DSPLY) |
|---|---|---|---|---|
| UTBLGEN | Tiered billing engine — one pass over UTREAD, apply UTRATE tiers. | Reads UTREAD, UTSVCPT, UTRATE; writes UTBILL, UTBLIN, updates UTSVCPT.SPBAL, appends UTLEDG. | 'B' | UTBLGEN GENERATED=n SKIPPED=n REJECTED=n |
| UTCSHPST | Cash posting — one pass over UTCASH, apply each receipt to the oldest open bill. | Reads UTCASH; forward-reads UTBILL; updates UTBILL + UTSVCPT; appends UTLEDG. | 'P' | UTCSHPST FULL=n PART=n NOBILL=n |
| UTDELINQ | Delinquency / disconnect — flag overdue open bills, disconnect SP, flag customer. | Reads UTAGELF; updates UTBILL, UTSVCPT, UTCUST; appends UTLEDG. | 'A' | UTDELINQ FLAGGED=n |
| UTFEECAL | Late fee + interest on delinquent bills (packed-decimal calc). | Reads UTAGELF; updates UTBILL + UTSVCPT balances; appends UTLEDG. | 'F' | UTFEECAL ASSESSED=n TOTFEE=n |
| UTGLPST | GL posting under commitment control (embedded SQL); COMMIT only if balanced. | SQL SUM over UTBILL/UTLEDG; INSERT into UTGLF (DB2). | — | UTGLPST IN BALANCE DR=… CR=… COMMITTED (or OUT OF BALANCE … ROLLED BACK) |
| UTBILLPR | Bill print — one PRTF page-set per bill (header, tier detail, total, due). | Reads UTBILL + UTBLINL; writes UTBILLP spool. | — | UTBILLPR PRINTED=n |
| UTRECON | Reconnect — a paid-up disconnected SP is reconnected; customer restored if no other SP is still delinquent. | Two passes over UTSVCPT; updates UTSVCPT, UTCUST; appends UTLEDG. | 'R' | UTRECON RECONNECTED=n |
| UTAGE | Aged-AR report — bucket open vs delinquent balances. | Reads UTAGELF only. | — | UTAGE BILLS=n TOTBAL=n OPEN=n DLQ=n |
| UTGLRPT | ILE COBOL cross-check — sum UTBILL charges (COMP-3 FD) + fees vs UTGLF A/R debit. | Reads UTBILL; SQL SUM over UTLEDG + UTGLF. | — | UTGLRPT … CROSS-CHECK: IN BALANCE / OUT OF BALANCE |
One pass over UTREAD (arrival). For each read it derives a deterministic
BILLNO = 'B' + last 7 digits of (READID + 10000000) (so READID 1001 →
B0001001, READID 701 → B0000701); a CHAIN(EN) on the header
skips an already-billed read (idempotent). It computes the whole bill in wide (11P2) working
storage first — walking UTRATE tiers in TIERNO order, each tier
billing MIN(remaining, band) × UNITPRC with the sentinel-topped last tier absorbing
the rest, plus BASECHG on tier 1 — then, only if the computed charge fits the
persistable 9P2 capacity, re-walks the tiers to write the UTBLIN lines, the
UTBILL header (BSTAT='O', DUEDT = CYCLE×100 + 20),
rolls up SPBAL, and appends the ledger 'B' row. An oversized read is a clean data-quality
reject with nothing written (no torn write).
Tiered-billing worked example (from the day-cycle test):
ELE 1200 kWh: 9.50 base + 500*.12 + 500*.15 + 200*.20 = 9.50 + 60 + 75 + 40 = 184.50
WAT 400 CCF: 6.00 base + 300*.05 + 100*.08 = 6.00 + 15 + 8 = 29.00
GAS 250 thm: 7.25 base + 200*.40 + 50*.55 = 7.25 + 80 + 27.50 = 114.75
DSPLY: UTBLGEN GENERATED=3 SKIPPED=0 REJECTED=0
One pass over UTCASH (arrival). Each receipt is applied to the single oldest open
bill for its service point — a forward READ over UTBILL selecting the
first BSTAT IN ('O','D') row for that SPNO with a positive balance. A payment
short of the balance is a partial (BSTAT='D', balance reduced); a full/over payment closes
it (BSTAT='P', balance 0). SPBAL is reduced by the amount applied and a ledger
'P' row written. A receipt with no matching open bill increments NOBILL.
Example: ACME ELE 184.50, pay 100.00 (partial); RIVERSIDE GAS 114.75, pay 114.75 (full)
ELE -> BSTAT=D, BILLBAL=84.50, SPBAL 184.50 -> 84.50
GAS -> BSTAT=P, BILLBAL=0.00, SPBAL 114.75 -> 0.00
DSPLY: UTCSHPST FULL=1 PART=1 NOBILL=0
One forward pass over the aging LF UTAGELF (keyed by DUEDT). Any bill
BSTAT IN ('O','D') with a positive balance is delinquent: the bill is set 'D',
its service point SPSTAT='D' (disconnected) and its customer CSTAT='D', and a
ledger 'A' row is written.
BILLBAL > 0, even one cent, is flagged the first time UTDELINQ runs after
billing, and it operates strictly per bill: a service point with even one unpaid weekly/monthly
bill is disconnected and its customer flagged, regardless of other bills paid in full. This is a
stale comment vs. a simpler/harsher implementation, not a functional defect; operators should pay a
bill in full to keep a service point connected.One pass over UTAGELF. For every BSTAT='D' bill still carrying a positive
balance it assesses a flat $5.00 late fee plus monthly interest at 1.50% of the balance,
computed in wide packed storage (15P4) then rounded half-up to cents. The fee is added to
BILLBAL and SPBAL, and a ledger 'F' row is written. A per-bill idempotency
guard scans UTLEDG for an existing 'F' row tagged with this bill's memo, so a bill is
fee-assessed at most once per pass.
Example: bill 184.50 delinquent
fee = 5.00 flat + round(184.50 * 0.0150 = 2.7675 -> 2.77) = 7.77
BILLBAL 184.50 -> 192.27, SPBAL grows by the same 7.77
DSPLY: UTFEECAL ASSESSED=1 TOTFEE=7.77
Embedded SQL under commitment control (ctl-opt commit(*yes)). It sums total billed
charges (SUM(CHARGE) over UTBILL), cash applied (ledger 'P') and fees (ledger
'F'), then inserts three balanced DR/CR pairs into UTGLF: billing DR A/R (120000) / CR
Revenue (400000); cash DR Cash (100000) / CR A/R (120000); fee DR A/R (120000) / CR Fee Revenue
(410000). It re-sums the batch just posted and COMMITs only if SUM(DR)=SUM(CR),
else ROLLBACK.
UTGLPST posts the full cumulative
all-time totals under a static BATCHID='GLB00001', with no delta/watermark tracking.
Each individual run is internally balanced, but calling it more than once in a period re-posts the whole
cumulative total again as new rows, inflating the running UTGLF A/R total. Run it
exactly once per close. When it has been run more than once, UTGLRPT correctly
reports OUT OF BALANCE — that is the cross-check working, not a defect in the report
or the SQL engine.One pass over UTBILL; for each bill it writes the PRTF (UTBILLP) header,
then the tier detail lines from UTBLINL (READE by BILLNO), the
total charge and the due line, producing a UTBILLP spooled file. The spool shows the
bill title, the total (e.g. 184.50) and per-tier prices (e.g. 0.1200).
UTRECON closes the disconnect loop: pass 1 reconnects every disconnected service point whose
balance is cleared (SPSTAT 'D' → 'A', ledger 'R'); pass 2 restores a customer to
CSTAT='A' only if it has no other still-disconnected service point. It refuses to reconnect
a service point that still owes. UTAGE buckets open vs delinquent balances for a report.
UTGLRPT (ILE COBOL) totals UTBILL.CHARGE through a COMP-3 FD, adds ledger fees, and
compares against the UTGLF A/R debit, printing IN BALANCE / OUT OF
BALANCE.
UTBLGEN has produced them.UTFEECAL only fees BSTAT='D' bills, which
only UTDELINQ creates.UTGLPST after billing, cash and fees are final, and
exactly once per close (see the idempotency note above).UTBLGEN (deterministic BILLNO + CHAIN(EN) skip)
and UTFEECAL (per-bill 'F'-row guard) are safe to re-run within a pass; each posting
program's disjoint LEDGSEQ band with a high-water-mark scan (F.3) prevents duplicate-key
collisions on a second run in the same business day. UTGLPST is not re-run
safe.All files are in library AQUILA, grounded in the DDS/SQL in src/sources.mjs.
Type codes: nA = character, nS d = zoned decimal (n digits, d after point),
nP d = packed decimal. Dates are stored as zoned integers in YYYYMMDD
(or YYYYMM for a billing cycle). Unless noted, files are UNIQUE-keyed PFs.
| Field | Type | Meaning |
|---|---|---|
| CUSTNO | 6A | Customer number (key). |
| CNAME | 25A | Customer name. |
| ADDR / CITY / ST / ZIP | 25A / 15A / 2A / 5A | Mailing address. |
| CSTAT | 1A | Status: A active, D delinquent. |
| Field | Type | Meaning |
|---|---|---|
| SPNO | 8A | Service-point number (key), e.g. SP000101. |
| CUSTNO | 6A | Owning customer. |
| UTYPE | 1A | Utility type: E electric, W water, G gas. |
| RATECD | 3A | Rate class (ELE/WAT/GAS) → UTRATE. |
| METERNO | 8A | Meter serving this point → UTMETER. |
| SPSTAT | 1A | Status: A active, D disconnected. |
| SPBAL | 9P 2 | Running service-point balance. |
| Field | Type | Meaning |
|---|---|---|
| METERNO | 8A | Meter serial (key). |
| MSPNO | 8A | Service point the meter serves. |
| MUTYPE | 1A | Utility type (E/W/G). |
| MINSTDT | 8S 0 | Install date (YYYYMMDD). |
| MMULT | 5P 2 | Dial multiplier (scaled-unit meters: consumption = raw-delta × multiplier). |
| MPRVRD | 9P 0 | Previous read value. |
| MSTAT | 1A | Status: A active, R removed. |
| Field | Type | Meaning |
|---|---|---|
| RATECD | 3A | Rate class (key part 1). |
| TIERNO | 2S 0 | Tier number within the class (key part 2). |
| TIERUB | 9P 0 | Tier upper bound (cumulative); the top tier carries a sentinel 999999999. |
| UNITPRC | 7P 4 | Per-unit price in this tier. |
| BASECHG | 7P 2 | Fixed monthly service charge (carried on tier 1 only). |
Seeded plan (UTRATLD): ELE 1→ub500/0.1200/base9.50, 2→ub1000/0.1500, 3→sentinel/0.2000; WAT 1→ub300/0.0500/base6.00, 2→sentinel/0.0800; GAS 1→ub200/0.4000/base7.25, 2→sentinel/0.5500. (7 tiers: ELE=3, WAT=2, GAS=2.)
| Field | Type | Meaning |
|---|---|---|
| READID | 8S 0 | Read id (key); drives the derived BILLNO. |
| SPNO | 8A | Service point read. |
| CYCLE | 6S 0 | Billing cycle (YYYYMM). |
| PRIORRD / CURRRD | 9P 0 | Prior and current dial readings. |
| RUSAGE | 9P 0 | Consumption for the cycle (renamed from USAGE — see F.4). |
| Field | Type | Meaning |
|---|---|---|
| BILLNO | 8A | Bill number (key), 'B'+7 digits, e.g. B0001001. |
| SPNO | 8A | Service point billed. |
| CYCLE | 6S 0 | Billing cycle (YYYYMM). |
| BUSAGE | 9P 0 | Usage billed (renamed from USAGE — see F.4). |
| CHARGE | 9P 2 | Total computed charge. |
| DUEDT | 8S 0 | Due date = CYCLE×100 + 20 (day 20 of the cycle month). |
| BSTAT | 1A | O open, P paid, D delinquent. |
| BILLBAL | 9P 2 | Remaining bill balance. |
| Field | Type | Meaning |
|---|---|---|
| BILLNO / LINENO | 8A / 2S 0 | Bill + line number (key). |
| LTIERNO | 2S 0 | Rate tier this line represents. |
| TIERUSE | 9P 0 | Usage billed in this tier. |
| TIERPRC | 7P 4 | Unit price applied. |
| LINEAMT | 9P 2 | Line amount = TIERUSE × TIERPRC. |
| Field | Type | Meaning |
|---|---|---|
| RCPTNO | 8S 0 | Receipt number (key). |
| SPNO | 8A | Service point paid. |
| RCPTDT | 8S 0 | Receipt date (YYYYMMDD). |
| PAYAMT | 9P 2 | Payment amount. |
| Field | Type | Meaning |
|---|---|---|
| LEDGSEQ | 8S 0 | Ledger sequence (key); assigned from a per-program disjoint band (F.3). |
| SPNO | 8A | Service point. |
| LTYPE | 1A | B bill, P payment, A adjustment (delinquency), F fee, R reconnect. |
| LDT | 8S 0 | Ledger date (YYYYMMDD). |
| LAMT | 9P 2 | Amount of the entry. |
| LMEMO | 20A | Free-text memo (e.g. BILL B0001001, CASH RCPT, FEE …). |
Authority: *PUBLIC is restricted to *CHANGE on UTLEDG
(RVKOBJAUT *ALL then GRTOBJAUT *CHANGE) so the batch can add/update but not delete.
UTBLINL is an LF over UTBLIN keyed by BILLNO — the subfile
source for UTBILLIQ and the detail source for UTBILLPR. UTAGELF
is an LF over UTBILL keyed by DUEDT (record UTAGER, exposing
BILLNO/SPNO/DUEDT/CHARGE/BSTAT/BILLBAL) — the aging pass read by UTDELINQ,
UTFEECAL and UTAGE via SETLL *LOVAL / forward READ.
| Field | Type | Meaning |
|---|---|---|
| BATCHID | CHAR(8) | Posting batch id (currently the static GLB00001). |
| ACCT | CHAR(6) | GL account: 100000 Cash, 120000 A/R, 400000 Revenue, 410000 Fee Revenue. |
| DRCR | CHAR(1) | D debit / C credit. |
| AMT | DECIMAL(11,2) | Posted amount. |
| SRCPGM | CHAR(8) | Source program (UTGLPST). |
CALL AQUILA/UTSETUP creates every object, wires journaling and the ledger authority,
and seeds the rate plan (DSPLY AQUILA setup complete. Rate plan loaded.).UTMNT (customers CC/CI,
meters MC/MI).UTREAD (journaled) — in the test harness a small
RPG loader writes them; in production this is the meter-read import.SBMJOBs on AQUILA/NITEQ:
BILLGEN → CASH POST → DELINQUENCY → FEE → GL POST → bill print.CALL AQUILA/UTMENU as they arrive.UTRECON to reconnect them.Pre-checks: confirm the library list includes AQUILA; confirm the rate plan is
present (UTRATE has 7 tiers); confirm each SBMJOB reached
status = OUTQ before starting the next.
Post-checks per step (each program DSPLYs a one-line result):
UTBLGEN GENERATED=n SKIPPED=n REJECTED=n — generated matches the new reads;
SKIPPED counts already-billed/missing-SP reads; REJECTED counts oversized-charge data-quality
rejects (should be 0 for clean data).UTCSHPST FULL=n PART=n NOBILL=n — NOBILL counts receipts with no matching open
bill; investigate any non-zero NOBILL.UTDELINQ FLAGGED=n and UTFEECAL ASSESSED=n TOTFEE=n reconcile to the set
of unpaid open bills.UTGLPST IN BALANCE … COMMITTED — a ROLLBACK line means the
batch did not balance and nothing posted.UTBILLPR PRINTED=n equals the number of bills; the UTBILLP spool shows
each bill title, total and tier prices.OUTQ.UTGLPST exactly once for the close. Confirm the joblog shows
IN BALANCE … COMMITTED.CALL AQUILA/UTGLRPT. Confirm
CROSS-CHECK: IN BALANCE.-- GL control total (should balance and be non-zero) SELECT COALESCE(SUM(CASE WHEN DRCR='D' THEN AMT ELSE 0 END),0), COALESCE(SUM(CASE WHEN DRCR='C' THEN AMT ELSE 0 END),0) FROM AQUILA.UTGLF; -- A/R debit = billed charges + fees SELECT COALESCE(SUM(AMT),0) FROM AQUILA.UTGLF WHERE ACCT='120000' AND DRCR='D';
Reconciling figures (the same ones the focused suites check):
UTGLRPT cross-check target.SUM(DR) = SUM(CR) across the batch, or UTGLPST
rolls back.Aged-AR: CALL AQUILA/UTAGE DSPLYs
UTAGE BILLS=n TOTBAL=n OPEN=n DLQ=n for a quick open-vs-delinquent split.
Each program DSPLYs its result; the night chain sequences steps as separate SBMJOBs so a
failed step can be re-submitted alone.
| Situation | Behaviour | Action |
|---|---|---|
| BILLGEN re-run | Deterministic BILLNO + CHAIN(EN) skips already-billed reads. | Safe — no duplicate bills. Idempotent. |
| Oversized meter read | Charge exceeds 9P2 capacity → clean reject, nothing written. | Correct the read; REJECTED count flags it. No torn write. |
| CASH re-run | Applies to the oldest open bill; a paid bill is no longer eligible. | Re-run does not double-apply a settled bill; NOBILL rises if all bills are settled. |
| DELINQUENCY / FEE re-run same day | LEDGSEQ high-water-mark scan avoids a duplicate-key (SNX1021) on the second run; FEE's per-bill 'F'-row guard prevents fee-stacking within a pass. | Safe to re-submit; fees are not double-assessed in the same pass. |
| GL POST run more than once | Not idempotent — re-posts the full cumulative total under the static BATCHID, inflating UTGLF. | Run exactly once per close. If double-posted, the inflated rows must be cleared before re-closing; UTGLRPT flags it OUT OF BALANCE. |
| Reconnect while still owing | UTRECON refuses (SPBAL > 0). | Post the payment first, then re-run UTRECON. |
| Ledger SFF9802 authority error | Batch cannot write UTLEDG. | Confirm *PUBLIC holds *CHANGE on UTLEDG (UTSETUP grants it); re-grant if a stricter authority was applied. |
UTLEDG
(B/P/A/F/R) and meter reads are journaled to UTREAD, any night's effect is fully
reconstructable after the fact with DSPJRN for reconciliation and recovery. Use the
single-paren DSPJRN … FILE(AQUILA/UTLEDG) form when filtering by file.The complete program surface, from src/sources.mjs. All objects are in library
AQUILA. RPG source is authored as column-exact fixed-form C/D-specs (helpers
C()/D()/Ccond()) mixed with /free blocks; some
programs are totally-free-form (**FREE).
UTRATE (ELE×3, WAT×2, GAS×2), each top
tier carrying the sentinel 999999999 upper bound and BASECHG on tier 1.CC/MC/CI/MI).
Full-key CHAIN, validate-before-write, re-CHAIN before each
UPDATE; reports via pOutFound/pOutMsg.READ over UTBILL to find the oldest open bill per
SPNO, partial (D) vs full (P), roll SPBAL down,
ledger 'P'.UTAGELF; flags bill/SP/customer delinquent, ledger 'A'. (Honest note:
no grace period; per-bill — see C.2.)UTGLF; three balanced DR/CR pairs;
COMMIT/ROLLBACK on the balance check.UTBILL + UTBLINL to the UTBILLP spool.UTAGELF.UTBILL (zoned fields PIC S9(n), packed COMP-3);
totals charges, adds ledger fees via embedded SQL, cross-checks the UTGLF A/R debit,
prints IN BALANCE/OUT OF BALANCE.For a read's usage U under rate class R, walk UTRATE tiers in
TIERNO order (full composite-key CHAIN(E) on RATECD:TIERNO):
t = TIERUB - prior TIERUB.MIN(remaining, band); line amount = usage ×
UNITPRC.BASECHG; the sentinel-topped last tier absorbs any remaining usage.CHARGE = BASECHG + Σ(tier amounts).Two-phase, torn-write-safe. Phase 1 computes the whole bill in wide 11P2
accumulators, writing nothing; if the computed charge (or any single tier amount) exceeds what the
persistable 9P2 fields can hold, the read is rejected cleanly. Phase 2 runs only for a
validated bill, re-walking the tiers deterministically to write the UTBLIN lines, then the
UTBILL header, the SPBAL roll-up and the ledger 'B' row — so a detail line
is never written for a read whose header would later fail. Packed math spans
9P0/7P4/9P2/11P2 widths.
UTLEDG is a UNIQUE-keyed PF on LEDGSEQ (not a bare arrival file):
a keyed WRITE appends, so the ledger accumulates across the whole night into a durable trail.
Every posting program assigns LEDGSEQ from a disjoint numeric band so keys never
collide:
| Program | LTYPE | LEDGSEQ band | Assignment |
|---|---|---|---|
| UTBLGEN | B | 10000000– | 10000000 + READID |
| UTCSHPST | P | 20000000– | 20000000 + RCPTNO |
| UTRECON | R | 60000000–60999999 | high-water-mark + seq |
| UTFEECAL | F | 70000000–70999999 | high-water-mark + seq |
| UTDELINQ | A | 90000000–90999999 | high-water-mark + seq |
The three high-water-mark programs (UTRECON/UTFEECAL/UTDELINQ) forward-scan their own band with a
full-key SETLL to find the current max key before assigning, so a second run in the
same business day that adds ≥1 new row does not restart at 0 and collide (which would raise
SNX1021 duplicate-key). This is what makes those steps safely re-runnable.
Documented in the source and PLAN.md; operationally the app behaves as described, but these are the real reasons certain names and idioms look the way they do:
USAGE field renamed. A DDS field literally named USAGE breaks the
RPG→COBOL compile bridge (USAGE is COBOL-reserved), so meter consumption is
RUSAGE and bill usage is BUSAGE.RATECD:TIERNO) via the CHAIN retry loop, avoiding a partial-key access quirk on
unique files.LEAVESR; no free-form elseif. The subroutine exit is a
fixed-form C … LEAVESR and option routing uses nested if/else/endif,
both to sidestep known free-form lowering quirks.PIC S9(n), packed =
COMP-3. The bill PRTF and the COBOL report obey these layout rules.FILE(). Filter journal entries with
FILE(AQUILA/UTLEDG), not the double-paren list form.*CHANGE. *PUBLIC gets *CHANGE
(read+add+update), the authority the batch posting path actually needs, as a deliberate restriction.SETLL *START / forward
READ). Here UTREAD and UTCASH are read in arrival order for
their one-pass batch programs (they are UNIQUE-keyed for the write path but read sequentially).'B' + last 7 digits of (READID + 10000000), making it deterministic per
read (READID 1001 → B0001001) so re-running the billing engine never duplicates a
bill.UTGLPST posts to UTGLF; it
COMMITs only if the batch balances, else ROLLBACK. Honored on the SQL
path, so GL posting is done entirely in embedded SQL.UTAGELF keys UTBILL by
DUEDT for the aging pass; UTAGE splits balances into open vs delinquent.SPSTAT='D' when its bill is delinquent (UTDELINQ)
and back to 'A' once paid up (UTRECON) — the signature utility
service-lifecycle arc.UTBLGEN and UTFEECAL are
idempotent within a pass; the high-water-mark posting steps are re-run safe on the ledger key;
UTGLPST is not (it re-posts cumulative totals).UTREAD and
UTLEDG are journaled with IMAGES(*BOTH) to UTJRN/UTRCV,
inspectable with DSPJRN/WRKJRNA.LTYPE tags the event: B bill, P payment, A adjustment
(delinquency), F fee, R reconnect.nP d)15P4) so multiplies never overflow before rounding to
cents.UTBILLP is the bill printer file;
UTBILLPR writes the spool.RATECD + TIERNO; each tier
has an upper bound and a unit price, with the top tier sentinel-topped to absorb the rest.UTBILLIQ's BSFL lists a
bill's tier lines under control record BCTL.