AMORTIS/i — Loan Origination & Servicing

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

AMORTIS/i is a term-loan origination and servicing application: it originates a loan by generating a full constant-principal (straight-line) amortisation schedule, accrues daily interest on a 30/360 basis, bills instalments monthly, applies borrower payments on an interest-first waterfall, ages the book into delinquency buckets, prices early-settlement payoff quotations, charges servicing fees and escrow, posts an interest-income GL distribution, and at year end re-prices, reports and closes matured loans. It is a program-driven estate written in RPG, ILE COBOL, CL, DDS and embedded SQL — the business logic lives in the RPG/COBOL programs themselves, orchestrated by four CL job cycles. This manual is the reference for the operator who runs the online screens and the daily/weekly/monthly/ annual cycles, and for the developer maintaining the application. It is grounded entirely in the committed source (loans-app/src/sources.mjs, src/seed.mjs, and the test/ln_*.mjs drivers).

Not to be confused with LOANSVC/i. A separate SteelFrame X application, LOANSVC/i (Consumer Loan Servicing), is documented elsewhere; it is an SQL-PL-driven consumer-instalment servicer. AMORTIS/i is a different application — its own library (AMORTIS), its own file/program set, and its own design: constant-principal term loans with the logic in RPG/COBOL programs rather than DB2 SQL PL.

A. Overview & Architecture ↑ top

A.1 What it does

AMORTIS/i services the full life of a term loan, in library AMORTIS:

A.2 The amortisation model: constant-principal, exact to the cent

Every loan in AMORTIS is a constant-principal (straight-line) term loan, not a level-annuity loan — a deliberate modelling choice. The principal component of every instalment is fixed (original principal / term), the interest component declines with the balance, and the instalment therefore declines too:

  principal component  = original principal / term          (fixed)
  interest component_n = opening balance_n * periodic rate  (declining)
  instalment_n         = principal + interest_n             (declining)
  closing_n            = opening_n - principal              (declining)

The canonical loan LN000001 is 12,000.00 at 12.0000% over 12 monthly instalments. Periodic rate = 12.0000 / 12 / 100 = 0.010000; principal per instalment = 12000 / 12 = 1000.00.

  n=1  open 12000.00  int 120.00  prin 1000.00  inst 1120.00  close 11000.00
  n=2  open 11000.00  int 110.00  prin 1000.00  inst 1110.00  close 10000.00
  ...
  n=12 open  1000.00  int  10.00  prin 1000.00  inst 1010.00  close     0.00
  total interest = 0.01 * 1000 * (12+11+...+1) = 0.01 * 1000 * 78 = 780.00

When the advance does not divide evenly by the term, the schedule would never reach zero (30,000.00 over 36 left 0.12 outstanding), so the final instalment repays whatever is actually left — a real amortisation plug. The daily accrual uses the same 30/360 convention, so a full 30-day month accrues exactly the schedule's interest: a day on 12,000.00 at 12% is 12000 × 0.01 / 30 = 4.00, and 30 days is 120.00 — the interest the monthly billing bills. That 30/360 identity between accrual and schedule is the design's central invariant.

A.3 Component & flow

  ORIGINATION           ONLINE                    BATCH (the four CL cycles)
  -----------           ------                    -------------------------
  LNREFLD (seed)        LNMENU (5250 menu)        LNDAILY  -> LNACCRU  accrual
  LNORIG   builds         opt 1 -> LNLOANIQ                  LNCASH    payments
    LNSCHD schedule        opt 2 -> LNSCHDIQ                 LNDELQP   re-bucket
    activates loan                (subfile)       LNWEEK   -> LNARRWL  worklist
                                                            LNQUOTE   payoff quote
                        LNPORTF (COBOL report)              LNARRPR   print worklist
                                                  LNMONTH  -> LNBILLP  billing
     LNRUNDT (*DTAARA: accrual / cut-off /                  LNFEEPG   fees+escrow
      as-at run date, set by CHGDTAARA) --------.          LNGLPST   GL distribution
            |                                    |          LNPORTF   portfolio
            v                                    v LNYEAR  -> LNYRSTM  interest stmt
     LNLOAN  <--writes--- every posting program            LNRATRV   rate review
       |  \                                                LNCLOSE   matured closure
       |   +--> LNTXN   (durable ledger: I/B/C/F/S/X, one stable key per event)
       |   +--> LNACRD  (daily accrual detail, keyed LOANNO+ACDT)
       |   +--> LNDELQ  (delinquency movement history)
       +------> LNQUOT (payoff quotations)   LNGLDIST (SQL GL)   LNSCHD (schedule)

A single processing event (say, daily accrual) flows: operator sets the run date on the LNRUNDT data area → the LNDAILY CL cycle CALLs LNACCRU → it reads each active loan, computes the day's interest, writes an LNACRD detail row and an LNTXN ledger row (each guarded by a stable idempotency key), and advances the loan's LNACCR bucket.

A.4 Object inventory

ObjectTypeRole
LNBRWPFBorrower master.
LNLOANPFLoan master — the servicing spine.
LNSCHDPFAmortisation schedule (composite key LOANNO+SCHINS).
LNSCHDLLFSchedule keyed by due date (billing access path).
LNLOANLLFLoans keyed by bucket, omitting bucket 0 (collections path).
LNPAYPFPayment receipts as banked.
LNTXNPFDurable loan transaction ledger.
LNACRDPFDaily interest-accrual detail.
LNDELQPFDelinquency-movement history.
LNQUOTPFPayoff / early-settlement quotations.
LNFEEPFFee & escrow catalogue.
LNGLDISTSQL tableInterest-income GL distribution (+index LNGLDACC).
LNLOAND / LNSCHDD / LNMENUDDSPFInquiry, subfile-schedule, and menu display files.
LNARRPPRTFArrears / collections worklist printer file.
LNREFLD / LNORIGRPGLEReference-data seed / origination.
LNACCRU / LNCASH / LNDELQPRPGLEDaily-cycle programs.
LNBILLP / LNFEEPG / LNGLPSTRPGLE / SQLRPGLEMonthly-cycle programs.
LNQUOTE / LNARRWL / LNARRPRRPGLEWeekly-cycle programs.
LNRATRV / LNCLOSE / LNYRSTMRPGLEAnnual-cycle programs.
LNLOANIQ / LNSCHDIQ / LNMENURPGLEInteractive inquiry programs.
LNPORTFCBLLECOBOL portfolio-position report.
LNSETUPCLPCreate/compile every object.
LNDAILY / LNWEEK / LNMONTH / LNYEARCLPThe four job cycles.
LNRUNDT*DTAARARun-date control (CHAR(10)) read by every dated batch program.

The full catalogue is 11 physical/logical DDS files + 1 SQL table (+index), 4 display/printer files, 17 RPG programs, 1 COBOL program and 5 CL programs, plus the LNRUNDT data area. Sections D and F expand each.

B. Online Transactions & Screens ↑ top

B.1 The command/entry line

AMORTIS/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 via CL/JOBQ for the batch cycles). 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 AMORTIS; every CL cycle does its own ADDLIBLE LIB(AMORTIS) first.

To do thisType on the command line
Build/rebuild the whole application (once)CALL AMORTIS/LNSETUP then CALL AMORTIS/LNREFLD
Originate the pending loansCALL AMORTIS/LNORIG
Open the online menuCALL AMORTIS/LNMENU
Open loan inquiry directlyCALL AMORTIS/LNLOANIQ
Open the amortisation-schedule subfile directlyCALL AMORTIS/LNSCHDIQ
Set the batch run date (before a dated cycle)CHGDTAARA DTAARA(AMORTIS/LNRUNDT) VALUE(' YYYYMMDD')
Run a cycleCALL AMORTIS/LNDAILY (or LNWEEK / LNMONTH / LNYEAR; or SBMJOB it)

The batch cycles take no CALL parameters; the dated programs read their processing date from the LNRUNDT data area (section F.3), so a scheduled submission is a bare CALL. Only LNMENU / LNLOANIQ / LNSCHDIQ are interactive; the batch programs run to completion and DSPLY a one-line result apiece.

B.2 The menu & inquiry screens

Main menu (LNMENU / LNMENUD)

A plain option menu. Key 1 for Loan Inquiry (CALLs LNLOANIQ) or 2 for the Amortisation Schedule (CALLs LNSCHDIQ); any other non-blank option shows Invalid option. F3 exits.

Loan inquiry (LNLOANIQ / LNLOAND)

A plain (non-subfile) screen. Key a loan number and Enter; the program CHAINs LNLOAN, looks the borrower name up in LNBRW, and shows the servicing position plus the derived payoff total — computed on the screen with the same formula LNQUOTE prices with (balance + accrued + billed-unpaid-interest + fees - escrow).

Loan Inquiry - AMORTIS/i Loan number: LN000001 Borrower . . . : ADELAIDE HARGREAVES Principal . . : 12000.00 Rate percent . : 12.0000 Term months . : 12 Balance . . . : 12000.00 Accrued int . : 4.00 Interest due . : 0.00 Principal due : 0.00 Payoff total . : 11975.00 Bucket / DPD . : 0/0 Status . . . . : A Loan found. F3=Exit Enter=Inquire

Amortisation schedule (LNSCHDIQ / LNSCHDD)

The application's natural subfile screen (record SSFL under control record SCTL, SFLPAG(6), SFLSIZ(60)). Key a loan number and Enter: the subfile is cleared and re-loaded with that loan's instalments before it is displayed, so each enquiry shows the schedule just keyed rather than the previous one. A 12-instalment schedule fills two pages, so Roll Up/Roll Down paging is genuinely exercised. The header shows the advance and the total interest over the life, summed from the schedule rows themselves (780.00 for LN000001).

Amortisation Schedule - AMORTIS/i Loan number: LN000001 Borrower . . . : ADELAIDE HARGREAVES Advanced . . . : 12000.00 Total interest : 780.00 Ins Due Opening Interest Principal Instalment S 1 20260201 12000.00 120.00 1000.00 1120.00 S 2 20260301 11000.00 110.00 1000.00 1110.00 S F3=Exit Roll=Page Enter=Inquire
Rendering accommodations documented in the source: numeric output fields are formatted with %EDITC(...:'L') (not code X, which emits the full unedited 15-digit picture), the due date is composed as yyyymmdd from %char substrings, and the subfile is cleared in the same pass as the load so every WRITE lands on a free RRN rather than a duplicate-record 01021. Operationally the screens behave exactly as shown.

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

Honest statement: AMORTIS/i does not model a true four-eyes maker–checker / separate-authorization workflow. There is no "one user posts, a second user approves" step: a receipt in LNPAY is applied by LNCASH when the daily cycle runs, and origination through LNORIG builds-and-activates in one pass. The control model the application does have:

In sum, the control posture is durable ledger + accrual/delinquency detail + status gating + key-based idempotency, all enforced in the programs, rather than a segregation-of-duties approval workflow.

C. Batch Jobs & the Periodic Cycle ↑ top

AMORTIS/i's servicing runs as four periodic CL cycles rather than one monolithic nightly job: a daily cycle (accrual + cash + re-bucket), a weekly cycle (worklist + payoff quotes), a monthly cycle (billing + fees + GL + portfolio), and an annual cycle (statement + rate review + closure). Each CL program does its own ADDLIBLE LIB(AMORTIS), CALLs its member programs in order, and sends a completion message. The dated programs take no parameters; each reads its processing date from the single LNRUNDT data area — so a scheduled/JOBQ submission sets the date, then simply calls the cycle.

-- set the processing date the next dated cycle runs for (CHAR(10), right-justified)
CHGDTAARA DTAARA(AMORTIS/LNRUNDT) VALUE('  20260116')

-- then submit the (parameterless) cycle
SBMJOB CMD(CALL PGM(AMORTIS/LNDAILY)) JOB(LNDAILY)

-- blank the data area to fall back to each program's built-in default date
CHGDTAARA DTAARA(AMORTIS/LNRUNDT) VALUE('          ')

C.1 Full batch program set

ProgramCyclePurposeReads / writesDSPLY result
LNREFLDsetupSeed borrowers, fee catalogue, pending loans.writes LNBRW/LNFEE/LNLOANLNREFLD BORROWERS=4 FEES=3 LOANS=4
LNORIGsetupBuild each pending loan's schedule, activate it.writes LNSCHD; updates LNLOANLNORIG ORIGINATED=n SKIP=n
LNACCRUdailyAccrue one day's 30/360 interest per active loan.LNRUNDT; LNLOAN/LNACRD/LNTXN(I)LNACCRU ACCRUED=n SKIP=n TOT=x
LNCASHdailyApply unapplied receipts on the waterfall.LNPAY/LNLOAN/LNTXN(C)LNCASH APPLIED=n SKIP=n
LNDELQPdailyAge loans into buckets 0–4; log movement.LNRUNDT; LNSCHD/LNLOAN/LNDELQLNDELQP CHECKED=n MOVED=n
LNARRWLweeklyTotal arrears by bucket for the collections desk.LNLOANL/LNBRWLNARRWL DELQ=n B1..B4=x TOT=x
LNQUOTEweeklyPrice a payoff quotation per active loan.LNRUNDT; LNLOAN/LNQUOTLNQUOTE QUOTED=n SKIP=n
LNARRPRweeklyPrint the arrears worklist (PRTF, page overflow).LNLOANL/LNBRW → LNARRPLNARRPR LINES=n TOT=x
LNBILLPmonthlyBill instalments due (due-date order); relieve accrual.LNRUNDT; LNSCHDL/LNLOAN/LNTXN(B)LNBILLP BILLED=n SKIP=n TOT=x
LNFEEPGmonthlyCharge servicing fees & escrow on billed loans.LNRUNDT; LNFEE/LNLOAN/LNTXN(F)LNFEEPG CHARGED=n SKIP=n SVC=x ESC=x
LNGLPSTmonthlySum ledger by type; write balanced DR/CR pairs.LNTXN → LNGLDIST (SQL)LNGLPST BATCH=202602 ROWS=n
LNYRSTMannualTotal year-end interest / accrual / fees from ledger.LNTXN/LNLOANLNYRSTM BILLED=n INT=x ACCR=x FEE=x
LNRATRVannualRe-price active loans against credit score (anchored).LNLOAN/LNBRW/LNSCHDLNRATRV CHECKED=n REPRICED=n
LNCLOSEannualClose zero-balance loans; refund escrow.LNLOAN/LNTXN(X)LNCLOSE CLOSED=n SKIP=n
LNPORTFmonthly/annualCOBOL portfolio position & delinquent count.reads LNLOANLNPORTF PRINCIPAL/ACCRUED/ARREARS/ESCROW/LOANS/DELINQUENT

C.2 Daily / weekly / monthly / annual detail

Daily — LNDAILY (LNACCRU → LNCASH → LNDELQP)

LNACCRU reads the run date from LNRUNDT (default 20260115), and for each active loan with a positive balance accrues balance × rate/1200 / 30 for the day — 4.00 on LN000001 — writing an LNACRD detail row (keyed LOANNO+ACDT) and, guarded by a stable ledger key, an LNTXN type-I row, then adding to LNACCR. LNCASH applies each unapplied receipt fees→interest→principal, surplus as a principal prepayment; a partial receipt clears interest before principal. LNDELQP re-ages each active loan by the days-past-due of its oldest billed-unpaid instalment (30/360 calendar) into buckets 0–4 and logs any movement to LNDELQ.

Expected DSPLY (one active 5000/12000-class loan accruing 4.00/day):
  LNACCRU ACCRUED=  1 SKIP=  3 TOT=      4.00
  LNCASH  APPLIED=  1 SKIP=  0            one receipt applied
  LNDELQP CHECKED=  4 MOVED=  0           book aged, nothing moved yet

Weekly — LNWEEK (LNARRWL → LNQUOTE → LNARRPR)

LNARRWL reads the delinquency logical file LNLOANL (which omits bucket-0 loans), totalling arrears (LNINTDU+LNPRNDU+LNFEE) by bucket. LNQUOTE prices a payoff per active loan: balance + (accrued + billed-unpaid interest) + fees - escrow, netting escrow off as a credit back to the borrower, and writes one LNQUOT row keyed (LOANNO, QTDT). LNARRPR prints the same worklist with borrower names and the payoff figure.

Expected payoff (LN000001 after month-1 billing, per the cycles suite):
  payoff = 12000.00 + (0.00 accrued + 120.00 billed) + 30.00 fees - 25.00 escrow = 12125.00

Monthly — LNMONTH (LNBILLP → LNFEEPG → LNGLPST → LNPORTF)

LNBILLP walks the schedule in due-date order via LNSCHDL, bills every S instalment due on or before the cut-off (default 20260201), moving its interest/principal into LNINTDU/LNPRNDU, relieving LNACCR by the interest billed (floored at 0), flipping the schedule row to B and posting an LNTXN type-B row. LNFEEPG totals the active fee catalogue by type and charges every billed loan the servicing fee (into LNFEE) and escrow (into LNFEE and LNESCRW both — a two-sided movement). LNGLPST sums the ledger and books the double entries. LNPORTF reports the book.

Expected DSPLY (the 4-loan seed book, month 1):
  LNBILLP BILLED=  4 SKIP=... TOT= 4480.00   4 x 1120.00-class instalments
  LNFEEPG CHARGED=  4 SKIP=... SVC=  5.00 ESC= 25.00  per-loan; book: fees 120.00, escrow 100.00
  LNGLPST BATCH=202602 ROWS=  6            DR/CR pairs: 480.00 interest, 120.00 fee+escrow
  LNPORTF LOANS ... 4  DELINQUENT ... 
The GL double entry a lender books: interest billed — DR 1210-INTR / CR 4100-INTI (480.00); cash applied — DR 1010-CASH / CR 1200-LNRC; fees & escrow — DR 1220-FEER / CR 4200-FEEI (120.00). Each pair is balanced; the credit side is what the income accounts recognise.

Annual — LNYEAR (LNYRSTM → LNRATRV → LNCLOSE → LNPORTF)

LNYRSTM totals interest billed (ledger B rows), accrued (I) and fees (F) for the year-end certificate (INT=480.00 across the seed book). LNRATRV re-prices active loans against credit score: score ≥750 → original rate −1.0000 (floor 6.0000); score <600 → original rate +2.0000 (cap 30.0000); otherwise unchanged. The adjustment anchors on the original contracted rate (recovered from schedule instalment 1: interest/opening ×1200), never on the current rate, so the first run moves the rate and every re-run is a no-op. LNCLOSE settles any active zero-balance loan (all buckets zero): refunds the escrow via an LNTXN type-X row and flips status to S.

Expected DSPLY (per the cycles suite):
  LNYRSTM BILLED=  4 INT=    480.00 ACCR= 16.00 FEE=120.00
  LNRATRV CHECKED=  4 REPRICED=  2   780->11.0000 (down), 590->26.0000 (up)
  LNCLOSE CLOSED=  0 SKIP=  4         none fully repaid yet

C.3 Ordering & dependencies

D. Data Files (data dictionary) ↑ top

All files are in library AMORTIS, grounded in the DDS/SQL members in sources.mjs. Dates are stored as signed yyyymmdd numerics; money is packed 11P2; the nominal annual rate is packed 7P4 (12.0000 = 12%), wide enough that dividing by 12 keeps the periodic factor exact.

LNBRW — Borrower master (key BRWID, unique)

FieldTypeMeaning
BRWID7ABorrower id (key), e.g. BR00001.
BRNAME30ABorrower name.
BRADDR30AAddress.
BRSCORE4S 0Credit score (read by the annual rate review).
BRSTAT1AA active, C closed.
BROPNDT8S 0Relationship-open date (yyyymmdd).

LNLOAN — Loan master (key LOANNO, unique)

FieldTypeMeaning
LOANNO8ALoan number (key), e.g. LN000001.
BRWID7AOwning borrower.
LNPRIN11P 2Original advanced principal.
LNRATE7P 4Nominal annual rate percent (12.0000 = 12%).
LNTERM3S 0Number of monthly instalments.
LNBAL11P 2Current principal outstanding.
LNACCR11P 2Interest accrued but not yet billed.
LNINTDU11P 2Interest billed and still unpaid.
LNPRNDU11P 2Principal billed and still unpaid.
LNESCRW11P 2Escrow balance held on the loan.
LNFEE11P 2Fees outstanding.
LNBUCK1S 0Delinquency bucket 0–4 (0 current, 4 = 90+).
LNDPD5S 0Days past due.
LNSTAT1AP pending, A active, S settled, W written off.
LNOPNDT / LNMATDT / LNNXTDT8S 0Opened / matures / next-due dates (yyyymmdd).

LNSCHD — Amortisation schedule (key LOANNO+SCHINS, unique)

FieldTypeMeaning
LOANNO / SCHINS8A / 3S 0Loan + instalment number (composite key).
SCHDUEDT8S 0Instalment due date (yyyymmdd).
SCHOPN11P 2Opening balance for the instalment.
SCHINT11P 2Interest component.
SCHPRN11P 2Principal component (constant, plug on the last).
SCHAMT11P 2Instalment = SCHINT + SCHPRN.
SCHCLS11P 2Closing balance = SCHOPN − SCHPRN.
SCHSTAT1AS scheduled, B billed, P paid.

Logical views: LNSCHDL keys the same records by SCHDUEDT, LOANNO (the monthly billing's due-date access path). LNLOANL keys LNLOAN by LNBUCK, LOANNO and omits bucket 0 (COMP(GT 0)) — the collections worklist is the file.

LNPAY — Payment receipts (key PYREF, unique)

FieldTypeMeaning
PYREF8AReceipt reference (key), e.g. PY000001.
LOANNO8ALoan the receipt is for.
PYDT / PYAMT8S 0 / 11P 2Receipt date / amount.
PYMETH1APayment method.
PYSTAT1AU unapplied, A applied (applied once).

LNTXN — Transaction ledger (key TXSEQ, unique)

FieldTypeMeaning
TXSEQ8S 0Stable business-derived sequence (key; see F.2).
LOANNO8ALoan.
TXTYPE1AI accrual, B billing, C cash, F fee/escrow, S payoff, X closure.
TXDT / TXAMT8S 0 / 11P 2Transaction date / amount.
TXINT / TXPRN11P 2Interest / principal split of the amount.
TXBAL11P 2Balance snapshot after the txn (where applicable).
TXREF / TXMEMO8A / 25ASource reference / free-text memo.

LNACRD — Daily accrual detail (key LOANNO+ACDT, unique)

FieldTypeMeaning
LOANNO / ACDT8A / 8S 0Loan + accrual date (composite key — the accrual idempotency guard).
ACBAL11P 2Balance the accrual was computed on.
ACRATE9P 6Monthly factor applied (rate/1200).
ACAMT11P 2Interest accrued that day.

LNDELQ — Delinquency history (key DQSEQ, unique)

FieldTypeMeaning
DQSEQ8S 0Stable per (loan, resulting bucket) (key; range 70000000).
LOANNO / DQDT8A / 8S 0Loan / re-bucketing date.
DQOLD / DQNEW1S 0Bucket before / after.
DQDPD / DQAMT5S 0 / 11P 2Days past due / arrears at the move.

LNQUOT — Payoff quotations (key LOANNO+QTDT, unique)

FieldTypeMeaning
LOANNO / QTDT8A / 8S 0Loan + quote date (composite key; one quote per day).
QTPRIN / QTINT / QTFEE / QTESCR11P 2Principal / interest / fees / escrow components.
QTTOT11P 2Payoff total = prin + int + fee − escrow.
QTSTAT1AO open quotation.

LNFEE — Fee & escrow catalogue (key FECODE, unique)

FieldTypeMeaning
FECODE4AFee code (key): SVC1, ESC1, LAT1.
FEDESC25ADescription.
FETYPE1AS servicing fee (per instalment), E escrow contribution.
FEAMT11P 2Amount (SVC1 5.00, ESC1 25.00, LAT1 15.00).
FESTAT1AA active, I inactive (LAT1 is seeded inactive).

LNGLDIST — Interest-income GL distribution (SQL; PK GLSEQ)

FieldTypeMeaning
GLSEQDECIMAL(8,0)Distribution row sequence (PK).
GLBATCHDECIMAL(6,0)Posting batch (yyyymm).
GLACCT / GLDRCRCHAR(9) / CHAR(1)Account (e.g. 4100-INTI) / D or C.
GLAMTDECIMAL(11,2)Posted amount.
GLLOAN / GLDTCHAR(8) / DECIMAL(8,0)Loan (or *ALL) / GL date.

Indexed by (GLACCT, GLDRCR) (LNGLDACC) so income by account is a keyed read.

Relationships

E. Operations Runbook ↑ top

E.1 Day-in-the-life

  1. One-time build (or after a rebuild): CALL AMORTIS/LNSETUP, then CALL AMORTIS/LNREFLD (seeds 4 borrowers, 3 fees, 4 pending loans), then CALL AMORTIS/LNORIG to build schedules and activate the loans.
  2. Set today's accrual date: CHGDTAARA DTAARA(AMORTIS/LNRUNDT) VALUE(' <YYYYMMDD>') (the value is CHAR(10), right-justified).
  3. Submit the daily cycle: SBMJOB CMD(CALL PGM(AMORTIS/LNDAILY)).
  4. Post-check the daily run (below).
  5. Handle interactive lookups through CALL AMORTIS/LNMENU (option 1 loan inquiry, option 2 the amortisation subfile) as needed.

Pre-checks: confirm the job's library list includes AMORTIS; confirm LNRUNDT holds the intended date (or is blank to accept the program default).

Post-checks after the daily cycle:

E.2 Month-end & year-end close

  1. Confirm the month's daily cycles have all run.
  2. Set the billing cut-off: CHGDTAARA DTAARA(AMORTIS/LNRUNDT) VALUE(' <month-end>').
  3. Submit LNMONTH. Confirm LNBILLP BILLED=, LNFEEPG CHARGED=, LNGLPST ROWS=, and the LNPORTF lines.
  4. At year end, submit LNYEAR and confirm the statement, rate review and closure lines.
  5. Reconcile the ledger and GL (below).

Reconciling figures (the same ones the volume suites check against a hand-derived oracle, for the 4-loan seed book after month-1 billing):

Ad-hoc portfolio & GL review: CALL AMORTIS/LNPORTF DSPLYs the book totals; STRSQL → SELECT GLACCT,GLDRCR,SUM(GLAMT) FROM AMORTIS.LNGLDIST GROUP BY GLACCT,GLDRCR proves the DR/CR pairs balance.

E.3 Failure & re-run rules

Each program DSPLYs a one-line result. Re-run safety is built on the stable idempotency keys (F.2): a re-run regenerates the same key, the CHAIN(EN) guard finds the existing row, and the program refuses to double-post.

SituationBehaviourAction
Re-run the daily accrual, same dateEach loan already has its LNACRD (LOANNO+ACDT) row and its ledger row.Safe no-op — nothing double-accrues. Idempotent per accrual date. Advance LNRUNDT to accrue a new day.
Re-run cash applicationThe receipt is flipped to A; its ledger key (range 30000000) already exists.Safe no-op — a receipt is applied exactly once.
Re-run monthly billing, same cut-offBilled instalments are B; their ledger key (20000000) already exists.Safe no-op — BILLED=0, the due bucket stays 120.00 (not 240.00).
Re-run feesThe fee ledger key (40000000) per loop already exists.Safe no-op — CHARGED=0.
Re-run delinquency re-bucketHistory key (70000000 + loan + resulting bucket) is stable per move.No second history row for the same bucket; the loan is simply recomputed to the same value.
Re-run the annual rate reviewThe target is anchored to the original rate.First run reprices, every re-run is REPRICED=0 — never walks the rate down repeatedly.
Re-run closureAn S loan is skipped; its closure ledger key (60000000) exists.Safe no-op; a loan is never closed twice, and its escrow refunds exactly once.
Run a dated cycle without advancing LNRUNDTThe run date is unchanged, so the idempotency key is unchanged.The cycle processes the same period again as a no-op. Always CHGDTAARA to a new date to process a new period (see F.3).
Because every money movement is journaled to LNTXN (with its interest/ principal split and, where relevant, a post-txn balance), every accrual to LNACRD, and every bucket move to LNDELQ, any cycle's effect is fully reconstructable after the fact for reconciliation and recovery.

F. Developer Reference ↑ top

The complete program surface, from sources.mjs. All objects are in library AMORTIS. The RPG is written in the fixed-form C-spec / free-form hybrid the emulator's ibmi/samples.js convention uses; helpers C() and D() in sources.mjs emit column-exact C-spec and D-spec source.

F.1 Programs

LNREFLD (RPGLE) — reference-data seed
Writes 4 borrowers, 3 fee/escrow catalogue rows (SVC1/ESC1 active, LAT1 inactive) and 4 pending loans. DSPLYs LNREFLD BORROWERS=4 FEES=3 LOANS=4.
LNORIG (RPGLE) — origination
For each P loan, generates the constant-principal schedule (periodic factor rate/1200, principal advance/term, last-instalment plug), then re-CHAINs the loan and activates it (LNBAL=LNPRIN, LNSTAT='A'). Idempotent via the composite (LOANNO, SCHINS) KLIST guard.
LNACCRU (RPGLE) — daily accrual
Per active positive-balance loan: LNACCR += balance × rate/1200 / 30, one LNACRD (LOANNO+ACDT) row and one LNTXN type-I row (key range 10000000 + loan digits ×100 + day slot).
LNCASH (RPGLE) — payment application
Waterfall fees → interest → billed principal (also reduces LNBAL) → surplus principal prepayment. Ledger key range 30000000 + receipt digits; receipt flipped to A. Drives balance to zero but does not settle.
LNDELQP (RPGLE) — delinquency re-bucketing
Finds the oldest Billed-unpaid instalment, computes 30/360 days-past-due, maps to bucket 0–4, updates LNBUCK/LNDPD, and (on a move) writes LNDELQ.
LNBILLP (RPGLE) — monthly billing
Reads schedule in due-date order via LNSCHDL; bills each S instalment due ≤ cut-off, moves interest/principal to the due buckets, relieves LNACCR (floored 0), flips the row to B, posts LNTXN type-B (key 20000000).
LNFEEPG (RPGLE) — fees & escrow
Totals the active catalogue by type, charges every billed active loan the servicing fee (into LNFEE) and escrow (into LNFEE and LNESCRW), posts type-F (key 40000000).
LNGLPST (SQLRPGLE) — GL distribution
Sums LNTXN by type and inserts balanced DR/CR pairs into LNGLDIST with embedded EXEC SQL INSERT: interest (1210-INTR/4100-INTI), cash (1010-CASH/1200-LNRC), fee+escrow (1220-FEER/4200-FEEI).
LNQUOTE (RPGLE) — payoff quotation
Per active loan: balance + (accrued + billed-unpaid interest) + fees - escrow, one LNQUOT row keyed (LOANNO, QTDT).
LNARRWL / LNARRPR (RPGLE) — arrears worklist / print
Read LNLOANL (bucket-0 omitted), total arrears by bucket / print with borrower names and payoff. LNARRPR uses printer overflow (OFLIND(*IN90)) to re-print the header per page.
LNRATRV (RPGLE) — annual rate review
Recovers the original rate from schedule instalment 1 (SCHINT×1200/SCHOPN) and applies the score policy to that anchor. Idempotent by anchoring, not delta.
LNCLOSE (RPGLE) — matured closure
Settles an active zero-everything loan: refunds LNESCRW via type-X (key 60000000), sets LNSTAT='S'. Skips already-S loans.
LNYRSTM (RPGLE) — year-end statement
Totals ledger interest (B), accrual (I) and fees (F) for the tax certificate.
LNLOANIQ / LNSCHDIQ / LNMENU (RPGLE) — interactive
Loan-position screen with derived payoff; amortisation-schedule subfile (clear-then-load per enquiry); option menu dispatching to the two inquiries.
LNPORTF (ILE COBOL) — portfolio report
Sequential read of the keyed LNLOAN, DSPLYing PRINCIPAL / ACCRUED / ARREARS / ESCROW / LOANS / DELINQUENT totals.
LNSETUP / LNDAILY / LNWEEK / LNMONTH / LNYEAR (CL)
Build-and-compile; and the four cycles, each ADDLIBLE LIB(AMORTIS) then CALLing its members in the fixed order (section C).

F.2 The idempotency-key scheme

AMORTIS/i has no DB trigger layer; re-run safety is engineered into the ledger/detail keys. Each posting program derives a stable key from a business key plus a disjoint numeric range, so a re-run regenerates the SAME key, the CHAIN(EN) guard finds it, and the program refuses rather than double-posting.

ProgramTXTYPEKey formula
LNACCRUI10000000 + loan digits × 100 + accrual day-of-month slot
LNBILLPB20000000 + loan digits × 100 + instalment number
LNCASHC30000000 + receipt-reference digits
LNFEEPGF40000000 + loan digits × 100 + instalment number
LNCLOSEX60000000 + loan digits
LNDELQP70000000 + loan digits × 10 + resulting bucket (into LNDELQ)

LNSCHD (composite LOANNO+SCHINS) and LNQUOT / LNACRD (composite LOANNO+date) are guarded through KLISTs rather than a single range — guarding on LOANNO alone would match instalment 1 and skip the whole loan.

F.3 The run-date data area (LNRUNDT)

Every dated batch program (LNACCRU, LNDELQP, LNBILLP, LNFEEPG, LNQUOTE) reads its processing date from a *CHAR(10) data area AMORTIS/LNRUNDT. If it holds a non-blank all-digit value, that is the date; otherwise the program falls back to a built-in default literal. The operator sets it with CHGDTAARA DTAARA(AMORTIS/LNRUNDT) VALUE(' YYYYMMDD') and blanks it to restore defaults.

Why this matters (a real bug the source records). Because each idempotency key derives FROM the run date, when the date was a compile-time literal and nothing else, every run after the first was a permanent no-op — the cycle could only ever process ONE calendar period, forever (the same shape as ESTATE/i FAGLPST+FADEPPER). Routing the date through LNRUNDT is what lets the cycle advance day by day. A related fix: the accrual ledger's day slot (WDAY) must track the real accrual date, not a fixed literal, or the loan-level totals look right while the LNTXN audit trail silently stops growing after day one.

F.4 Program-transaction (DSPLY) reference

Every batch program ends with a single DSPLY line the operator (and the test harness) reads as its result — the AMORTIS/i equivalent of a job-completion message. The full set:

ProgramDSPLY line
LNREFLDLNREFLD BORROWERS=n FEES=n LOANS=n
LNORIGLNORIG ORIGINATED=n SKIP=n
LNACCRULNACCRU ACCRUED=n SKIP=n TOT=x
LNCASHLNCASH APPLIED=n SKIP=n
LNDELQPLNDELQP CHECKED=n MOVED=n
LNBILLPLNBILLP BILLED=n SKIP=n TOT=x
LNFEEPGLNFEEPG CHARGED=n SKIP=n SVC=x ESC=x
LNGLPSTLNGLPST BATCH=202602 ROWS=n
LNQUOTELNQUOTE QUOTED=n SKIP=n
LNARRWLLNARRWL DELQ=n B1=x B2=x B3=x B4=x TOT=x
LNARRPRLNARRPR LINES=n TOT=x
LNRATRVLNRATRV CHECKED=n REPRICED=n
LNCLOSELNCLOSE CLOSED=n SKIP=n
LNYRSTMLNYRSTM BILLED=n INT=x ACCR=x FEE=x
LNPORTFLNPORTF PRINCIPAL/ACCRUED/ARREARS/ESCROW/LOANS/DELINQUENT <value> (six lines)

G. Glossary ↑ top

Amortisation schedule (LNSCHD)
The per-instalment plan generated at origination: opening balance, interest, principal, instalment and closing balance for each of the loan's instalments.
Constant-principal (straight-line)
An amortisation where the principal component of every instalment is fixed (advance/term) and the interest declines with the balance; the instalment declines. AMORTIS/i's chosen model, exact to the cent with a final-instalment plug when the advance does not divide evenly.
30/360
The day-count convention: every month is 30 days, every year 360. A day of interest is balance × rate/1200 / 30; a 30-day month accrues exactly the schedule's interest.
Accrual (LNACCR / LNACRD)
Interest recognised each day on the outstanding balance, added to the loan's unbilled accrual bucket and audited row-by-row in LNACRD; relieved when the instalment is billed.
Payment waterfall
The order a receipt is applied by LNCASH: fees, then interest, then billed principal, then any surplus as a principal prepayment.
Delinquency bucket (LNBUCK)
0 current, 1 = 1–29 days past due, 2 = 30–59, 3 = 60–89, 4 = 90+, off the oldest billed-unpaid instalment.
Payoff quotation (LNQUOT)
The price to settle a loan today: principal + accrued + billed-unpaid interest + fees − escrow (escrow returned to the borrower).
Escrow (LNESCRW)
Money collected from the borrower and held on the loan; a two-sided movement at billing and refunded at closure.
Idempotent
Safe to run again with the same result. Here achieved by deriving a stable ledger/detail key from a business key and refusing to re-post via CHAIN(EN); and, for the rate review, by anchoring on the original rate.
Anchoring (rate review)
Applying an adjustment to the loan's ORIGINAL contracted rate (recovered from schedule instalment 1), not the current rate — so a re-run does not walk the rate away.
Run-date data area (LNRUNDT)
The *CHAR(10) data area holding the processing date the dated batch programs read, set by CHGDTAARA, so a parameterless cycle can be driven for any period.
SBMJOB / CHGDTAARA / DSPLY
Submit Job; Change Data Area (set the run date); and the RPG operation each program uses to emit its one-line result.
Ledger (LNTXN)
The durable transaction trail every posting program contributes to: types I accrual, B billing, C cash, F fee/escrow, S payoff, X closure.
GL distribution (LNGLDIST)
The SQL table LNGLPST writes balanced double entries into: interest, cash and fee/escrow DR/CR pairs by account.