PHARMOS/i — Retail Pharmacy Dispensing, Inventory & Third-Party Billing

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

PHARMOS/i is a retail-pharmacy dispensing, inventory and third-party-billing application: a patient's prescription is filled at the counter, priced against the drug's Average Wholesale Price (AWP), adjudicated against the patient's third-party plan (the plan pays a percentage, the patient owes a copay), the dispensed quantity is drawn from inventory lots FIFO by expiry, the plan later remits payment (weekly), the patient copay becomes an AR receivable, and month-end posts the balanced GL distribution, ages the AR, and values the remaining inventory. The logic is coded in RPG IV and fixed-form RPG (the dispensing and billing engine), SQLRPGLE (the GL and inventory-valuation posts), COBOL (the AR-aging report) and CL (setup + the three job cycles); every rule lives in the programs themselves. This manual is the reference for the operator who runs the counter screens and the periodic batch cycles, and for the developer maintaining the application. It is grounded entirely in the committed source (pharmacy-app/src/sources.mjs, src/seed.mjs, and the test/rx_*.mjs drivers). Everything runs in library PHARMOS.

Contents

A. Overview & Architecture ↑ top

A.1 What it does

PHARMOS/i runs the full life of a retail pharmacy dispense:

A.2 The pricing & adjudication maths

Every program obeys one pricing chain, stated in the source header and proven by the test oracles:

AWP (DAWP) is a public billing benchmark and is different by design from the pharmacy's own cost (DCOST); the contract discount belongs to the plan (PLCONTR), not the pharmacy, because a pharmacy's AWP pricing is public and it is the payer who negotiates the discount off it. Prices are held at fractional precision (DAWP/DCOST are 9P4) so a per-unit price such as 0.4215/tablet survives an extended-price multiply exactly.

A.3 Component & flow

  ONLINE                     DAILY (RXDAILY)          WEEKLY (RXWEEK)        MONTHLY (RXMONTH)
  ------                     ---------------          ---------------        -----------------
  RXMENU (5250 menu)         RXINTAKE  (edit)         RXPEND   (worklist)    RXGLDST (GL post)
    1 -> RXFILLIQ            RXELIG    (eligibility)  RXREMIT  (remit+EOB)   RXAGE   (AR aging)
    2 -> RXPROFIQ           RXADJUD   (adjudicate)                          RXINVAL (inv value)
                                 |
                                 v
   RXFILL (fill event) <--writes-- every step        printer: RXREMP (remittance advice / EOB)
     |  \
     |   +--> RXLOT   (FIFO decrement, earliest LEXPDT first, all-or-nothing)
     |   +--> RXAR    (one open copay receivable per adjudicated fill)
     |   +--> RXLEDG  (durable re-runnability guard: J/R/G/V, disjoint ranges)
     +------> RXREM   (weekly plan remittance header)   RXWORK (weekly pended worklist)
              RXGLDIST (monthly GL rows)                RXINVVAL (monthly lot valuation)

A single dispense flows: a fill row is entered (FSTAT='E') → RXINTAKE edits it to 'V'RXELIG re-tests it and (on pass) consumes a refill slot → RXADJUD prices it, splits plan-pay/copay, sweeps RXLOT FIFO by expiry to satisfy the quantity, writes the copay to RXAR, and stamps the fill 'A' (adjudicated) with a durable RXLEDG guard row. Weekly, RXREMIT pays the plan and stamps the fill 'X' (remitted); monthly, the three close programs post GL, age AR and value inventory.

A.4 Object inventory

ObjectTypeRole
RXPATPFPatient master.
RXPRVDRPFPrescriber master.
RXDRUGPFDrug / NDC master (AWP, cost, schedule).
RXPLANPFThird-party plan master (discount, copay rule).
RXRXPFPrescription header (refills, expiry).
RXFILLPFFill / dispense event (the pricing spine).
RXFILLP / RXFILLDLFFills keyed by patient / by drug (access paths).
RXLOTPFInventory lots, composite key (NDC, LOTNO), FIFO by expiry.
RXARPFPatient copay AR ledger.
RXLEDGPFDurable posting / re-runnability ledger.
RXREMPFWeekly plan remittance headers.
RXWORKPFWeekly pended-fill worklist.
RXGLDISTSQL tableMonthly GL distribution (balanced DR/CR).
RXINVVALSQL tableMonthly inventory valuation snapshot.
RXFILLD1 / RXPROFD / RXMENUDDSPFFill inquiry / patient profile / menu.
RXREMPPRTFRemittance-advice / EOB printer file.
RXREFLDRPGSeed reference data (plans, drugs, patients, lots).
RXINTAKE / RXELIG / RXADJUDRPGDaily engine: edit / eligibility / adjudicate.
RXPEND / RXREMITRPGWeekly worklist / plan remittance + EOB.
RXGLDST / RXINVALSQLRPGLEMonthly GL post / inventory valuation.
RXAGECOBOLMonthly AR aging report.
RXFILLIQ / RXPROFIQ / RXMENURPGInteractive fill inquiry / patient profile / menu.
RXSETUPCLCreate every object, compile every program.
RXDAILY / RXWEEK / RXMONTHCLThe three job cycles.

The catalogue is 11 PFs + 2 LFs over DDS, 2 SQL tables (each with an index), 3 DSPFs and 1 PRTF, driven by 11 RPG/SQLRPGLE programs, 1 COBOL program and 4 CL programs. Sections D and F expand each.

B. Online Transactions & Screens ↑ top

B.1 The command/entry line

PHARMOS/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 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 PHARMOS — the tested jobs run with LIBL = QSYS QGPL PHARMOS QTEMP and CURLIB = PHARMOS.

To do thisType on the command line
Open the operator menu (routes to both inquiry screens)CALL PHARMOS/RXMENU
Open the single-fill pricing inquiry directlyCALL PHARMOS/RXFILLIQ
Open the patient-profile subfile inquiry directlyCALL PHARMOS/RXPROFIQ
Run the daily fill/adjudication cycleCALL PHARMOS/RXDAILY (or SBMJOB it)
Run the weekly pend + remittance cycleCALL PHARMOS/RXWEEK
Run the monthly close cycleCALL PHARMOS/RXMONTH
First-time build: create objects & compile programsCALL PHARMOS/RXSETUP then CALL PHARMOS/RXREFLD

The batch programs take no CALL parameters: each carries its own processing period as an in-source constant (the daily engine dates fills 20260801, the weekly run is 202632, the monthly batch is 202608), so a scheduled submission is a bare CALL. Only the three 5250 programs are interactive; every batch program runs to completion and DSPLYs a one-line result banner.

B.2 The menu & inquiry screens

Three interactive programs share the counter. RXMENU (over DDS RXMENUD) is a plain menu that CALLs the other two by option; RXFILLIQ is a plain (non-subfile) pricing inquiry; and RXPROFIQ is the app's subfile screen — a patient profile over a scrolling subfile of that patient's fill history.

RXMENU — the operator menu (RXMENUD)

PHARMOS/i Main Menu 1. Rx Fill Inquiry 2. Patient Profile Inquiry Option . . . . : _ F3=Exit Enter=Select

Key 1 to CALL RXFILLIQ, 2 to CALL RXPROFIQ; any other non-blank value shows Invalid option.. F3 (DDS CA03) exits. Each called program returns to the menu on its own F3.

RXFILLIQ — fill pricing inquiry (RXFILLD1)

Type a fill number and press Enter; the screen renders that dispense's full pricing breakdown. Values are formatted with the %editc(...:'K') edit code. The figures below are the tested oracle for fill F00000301 (patient PT0000001 on PRIMECARE, LISINOPRIL AWP 2.5000, qty 40 → billed 100.00, allowed 90.00, plan paid 80.00, copay 10.00).

Prescription Fill Inquiry - PHARMOS/i Fill number : F00000301 Patient . . . : PT0000001 NDC . . . . . : 00071015523 Qty dispensed : 40 Billed . . . . : 100.00 Allowed . . . : 90.00 Plan paid . . : 80.00 Copay . . . . : 10.00 Status . . . . : A FILL FOUND F3=Exit Enter=Inquire

An unknown fill number blanks the detail fields and shows FILL NOT FOUND. Status (FSTAT) is one letter: E entered, V validated, A adjudicated, D denied, X remitted.

RXPROFIQ — patient profile inquiry (RXPROFD, subfile)

Type a patient id; the screen shows the patient's name and plan, and a subfile (DDS record PSFL under control record PCTL, SFLPAG(5) per page, SFLSIZ(20)) of that patient's fill history, walked over the patient-keyed access path RXFILLP. The program uses the classic clear→load→display subfile discipline so a fresh enquiry never shows the previous patient's rows under a miss.

Patient Profile Inquiry - PHARMOS/i Patient id . : PT0000001 Name . . . . : ELENA VASQUEZ TORRES Plan . . . . : PLAN0001 Fill NDC Date Billed Plan pd Copay F00000301 00071015523 20260801 100.00 80.00 10.00 Patient found. F3=Exit Roll=Page Enter=Inquire
Subfile field (PSFL)Type (DDS)Shows
SFILNO9A outputFill number.
SNDC11A outputDrug NDC.
SFDT8A outputFill date (YYYYMMDD).
SBILL / SPLPD / SCOPY12A outputBilled / plan-paid / copay (edited).

Roll Up/Roll Down (DDS ROLLUP(25)/ ROLLDOWN(26)) page the subfile; SFLEND(*MORE) shows the more-pages indicator. F3 exits. Both inquiry screens are read-only — they display priced fills but never post.

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

Honest statement: PHARMOS/i does not model a true four-eyes maker–checker / separate-authorization workflow. There is no "one user dispenses, a second user approves" step in the code: a fill flows edit→eligibility→adjudication in one automated daily chain, and a plan remittance posts in one automated weekly pass. The pended worklist (RXWORK) is advisory only — it queues a fill for a human to look at but does not change FSTAT and does not block remittance (a fill can be both pended-for-review and remitted, as the cycles test proves). The manual documents the control model the application does have:

In sum, the control posture is structural/eligibility gating + all-or-nothing inventory + a durable idempotency ledger + a non-self-clearing AR, all enforced in the programs and their data, rather than a segregation-of-duties approval workflow.

C. Batch Jobs & the Periodic Cycle ↑ top

PHARMOS/i runs as three periodic CL cycles rather than one monolithic nightly job: a daily cycle (RXDAILY: intake → eligibility → adjudication), a weekly cycle (RXWEEK: pended worklist → plan remittance + EOB), and a monthly cycle (RXMONTH: GL distribution → AR aging → inventory valuation). Each CL program does ADDLIBLE LIB(PHARMOS) then CALLs its member programs in fixed order and sends a completion message. None of the batch programs take parameters; their processing period is an in-source constant.

-- the three cycles are bare submits; no parameters, no control row to set
SBMJOB CMD(CALL PGM(PHARMOS/RXDAILY)) JOB(RXDAILY)
SBMJOB CMD(CALL PGM(PHARMOS/RXWEEK))  JOB(RXWEEK)
SBMJOB CMD(CALL PGM(PHARMOS/RXMONTH)) JOB(RXMONTH)

C.1 Full batch program set

ProgramCyclePurposeReadsWrites / result banner
RXINTAKEDaily 1Fill intake edit: qty>0, days-supply>0, Rx exists.RXFILL, RXRXStamps V (valid) or D+edit code; RXINTAKE VALID=/REJECT=/SKIP=.
RXELIGDaily 2Patient/prescriber/drug/Rx eligibility; consumes a refill slot on pass.RXFILL, RXRX, RXPAT, RXPRVDR, RXDRUGStamps D+D1–D9 or leaves V; bumps RXREFUSD; RXELIG PASS=/DENY=/SKIP=.
RXADJUDDaily 3THE ENGINE: price, plan-pay/copay split, FIFO lot decrement, AR post.RXFILL, RXRX, RXPAT, RXPLAN, RXDRUG, RXLOTRXFILL priced+A / D+S1; RXLOT decremented; RXAR + RXLEDG(J) rows; RXADJUD ADJUDICATED=/PENDED=/SKIP=.
RXPENDWeekly 1Build the human worklist (W1 stock / W2 high-dollar / W3 override).RXFILLRXWORK rows; RXPEND PENDED=/SKIP=. Advisory: does not change FSTAT.
RXREMITWeekly 2Post one remittance per plan, stamp fills X, print the EOB.RXPLAN, RXFILL, RXPATRXREM + RXLEDG(R) rows; RXFILL→X; RXREMP print; RXREMIT PAYMENTS=/TOTAL=/SKIP=.
RXGLDSTMonthly 1Post balanced DR/CR GL rows for the month's movement.RXFILL, RXGLDISTRXGLDIST rows (8 for the tested book); RXGLDST BATCH=/ROWS=/COGS=.
RXAGEMonthly 2Age open copay AR into CURRENT / 31-60 / 61-90 / OVER-90.RXARReport only, no posting; RXAGE OPEN/CURRENT/31-60/61-90/OVER90/COUNT.
RXINVALMonthly 3Value every open lot at its own cost, snapshot per lot.RXLOT, RXLEDGRXINVVAL rows + RXLEDG(V) guard; RXINVAL PERIOD=/LOTS=/VALUE=.

C.2 Daily / weekly / monthly detail

Daily — RXDAILY (RXINTAKE → RXELIG → RXADJUD)

RXINTAKE walks every fill; only FSTAT='E' rows are touched (others skipped, so a re-run is a no-op). It denies E1 (qty ≤ 0), E2 (days-supply ≤ 0) or E3 (Rx not found), else stamps 'V'. RXELIG tests each 'V' fill against patient/prescriber/drug/Rx state (codes D1D9); on pass it consumes a refill slot now (RXREFUSD+1) so a fill that later pends for stock still reflects that this dispense used a refill. RXADJUD prices each remaining 'V' fill, applies the plan discount and copay rule (capped/floored), splits plan-pay/copay, does the all-or-nothing FIFO lot decrement, posts the copay to RXAR, writes the RXLEDG(J) guard, and stamps 'A'. A short-stock fill is denied S1 and touches no inventory.

Expected DSPLY (daily suite: 7 fills entered, 2 adjudicate, 1 pends S1, 3 deny elig, 1 rejects intake):
  RXINTAKE VALID=6 REJECT=1 SKIP=0     F7 rejected E1 (qty 0)
  RXELIG   PASS=3  DENY=3  SKIP=1       D2/D4/D6 denials; the E-reject is skipped
  RXADJUD  ADJUDICATED=2 PENDED=1 SKIP=3 2 priced, 1 pends S1 (insufficient stock)
FIFO by expiry is real. In the daily suite, fill F00000001 dispenses 600 units of a drug held across two lots (lot 1 qty 500 expiring 20270301, lot 2 qty 500 expiring 20271201). The engine draws 500 from the earlier-expiry lot first, then 100 from the later lot: lot 1 drains to 0, lot 2 drops to 400. The draw order follows LEXPDT, not lot number or file order — PICKLOT re-scans for the lowest-expiry open lot each pass.

Weekly — RXWEEK (RXPEND → RXREMIT)

RXPEND queues fills for human review: W1 (denied S1 insufficient stock, carries billed amount), W2 (adjudicated but plan liability over 200.00, carries plan-paid), or W3 (denied D8/D9 — expired window or exhausted refills, both commonly overridden by a pharmacist call). It is advisory: FSTAT is unchanged. RXREMIT groups every adjudicated ('A') fill by plan (joining through the patient, since a fill carries no plan id), writes one RXREM header per plan, stamps those fills 'X' with the remittance number, writes an RXLEDG(R) row, and prints the remittance advice / EOB.

Expected DSPLY (cycles suite: W1 pends W2 high-dollar, W3 pends W1 stock; both plans owe one fill each):
  RXPEND   PENDED=2 SKIP=1
  RXREMIT  PAYMENTS=2 TOTAL=289.40 SKIP=0   PRIMECARE 215.00 + VALUERX 74.40

Monthly — RXMONTH (RXGLDST → RXAGE → RXINVAL)

RXGLDST (SQLRPGLE) accumulates the month over every 'A' or 'X' fill and posts four balanced DR/CR pairs — COGS/inventory, plan-receivable/plan-revenue, cash/receivable (only remitted 'X' fills), copay-receivable/copay-revenue. It posts only the movement since the last close by re-querying RXGLDIST itself, so a second run in the same period posts nothing. RXAGE (COBOL) reads every open RXAR row and buckets it by age against report date 20260831. RXINVAL (SQLRPGLE) values every open lot at its own cost (LQTYOH×LCOST, not a blend) and snapshots one RXINVVAL row per lot, guarded by a period-derived RXLEDG(V) row.

Expected DSPLY (cycles suite: both fills remitted -> both hit every GL bucket):
  RXGLDST BATCH=202608 ROWS=8 COGS=246.00   4 balanced pairs; SUM(DR)=SUM(CR)
  RXAGE OPEN     28.60                       10.00 + 18.60 open copay
  RXAGE CURRENT  28.60                       both 30 days old -> CURRENT
  RXINVAL PERIOD=202608 LOTS=4 VALUE=...     lot-by-lot at own cost

C.3 Ordering & dependencies

D. Data Files (data dictionary) ↑ top

All files are in library PHARMOS, grounded in src/sources.mjs. Dates are stored as signed 8S 0 in YYYYMMDD form (or YYYYMM for periods); prices are packed 9P 4 (four fractional digits so a fractional-cent unit price is exact); money is packed 11P 2; percentages/rates are 5P 4 (e.g. 0.8500). The two SQL tables use DECIMAL columns.

RXPAT — Patient master (key PATID)

FieldTypeMeaning
PATID9APatient id (key), e.g. PT0000001.
PNAME30APatient name.
PDOB8S 0Date of birth (YYYYMMDD).
PPLAN8AThird-party plan id (→RXPLAN).
PALRGY1ADocumented-allergy flag Y/N (informational; not enforced by any program).
PSTAT1AA active, I inactive (inactive → eligibility D2).

RXPRVDR — Prescriber master (key PRVID)

FieldTypeMeaning
PRVID9APrescriber id (key).
PRNAME30APrescriber name.
PRDEA / PRNPI9A / 10ADEA number / NPI.
PRSTAT1AA active, S suspended (suspended → eligibility D4).

RXDRUG — Drug / NDC master (key NDC)

FieldTypeMeaning
NDC11A11-digit National Drug Code (key).
DNAME30ADrug name.
DAWP9P 4Average Wholesale Price per unit — the billing benchmark.
DCOST9P 4Pharmacy's own per-unit acquisition cost (distinct from DAWP by design).
DGENERIC1AGeneric Y/N.
DSCHED1AControlled-substance schedule 0–5 (0 = not controlled).
DSTAT1AA active, D discontinued (discontinued → eligibility D6).

RXPLAN — Third-party plan master (key PLANID)

FieldTypeMeaning
PLANID8APlan id (key).
PLDESC30APlan description.
PLCONTR5P 4Contracted reimbursement rate off billed AWP (allowed = billed × PLCONTR).
PLPCTPD5P 4Plan's stated pay percentage of allowed (informational alongside the copay split).
PLCOTYP1ACopay type: F flat, P percentage.
PLCOPAY11P 2Flat copay amount (used when PLCOTYP='F').
PLCOPCT5P 4Copay percentage of allowed (used when PLCOTYP='P').
PLSTAT1AA active.

Seeded plans: PLAN0001 PRIMECARE (contract 0.9000, pays 0.8000, FLAT 10.00); PLAN0002 VALUERX (contract 1.0000, pays 0.7000, PERCENTAGE 0.2000).

RXRX — Prescription header (key RXNO)

FieldTypeMeaning
RXNO9APrescription number (key).
PATID / PRVID / NDC9A / 9A / 11APatient / prescriber / drug.
RXWDT / RXEXPDT8S 0Date written / refill-expiry date (a fill after RXEXPDT → D8).
RXQTY7P 2Prescribed quantity.
RXDAYSUP3S 0Days supply.
RXREFTOT / RXREFUSD2S 0Refills authorised / used (RXREFUSD ≥ RXREFTOT → D9).
RXSTAT1AA active, X expired, D discontinued (D → D7).

RXFILL — Fill / dispense event (key FILLNO)

FieldTypeMeaning
FILLNO9AFill number (key), e.g. F00000101.
RXNO / PATID / NDC9A / 9A / 11ASource Rx / patient / drug.
FDT8S 0Fill date (YYYYMMDD).
FQTY / FDAYSUP7P 2 / 3S 0Quantity dispensed / days supply.
FBILLED11P 2Billed = DAWP × FQTY.
FALLOWD11P 2Allowed = FBILLED × PLCONTR.
FPLANPD11P 2Plan pay = FALLOWD − FCOPAY.
FCOPAY11P 2Patient copay (invariant: FPLANPD + FCOPAY = FALLOWD).
FCOST11P 2Cost of goods = DCOST × FQTY.
FSTAT1AE entered, V validated, A adjudicated, D denied, X remitted.
FDENCD2ADenial/pend reason (E1–E3, D1–D9, S1).
FLOTNO6S 0Lot reference field.
FREMNO9ARemittance number once the plan has paid (set by RXREMIT).

Alternate access paths: RXFILLP keyed by (PATID, FILLNO) — the patient profile subfile; RXFILLD keyed by (NDC, FILLNO) — drug-utilisation/recall enquiry.

RXLOT — Inventory lot (composite key NDC, LOTNO)

FieldTypeMeaning
NDC / LOTNO11A / 6S 0Drug + lot number (composite key — a drug has many lots).
LEXPDT8S 0Lot expiry date — the FIFO ordering key (earliest first).
LQTYRCV / LQTYOH9P 2Quantity received / on hand (decremented by a fill).
LCOST9P 4This lot's own acquisition cost (used for lot-by-lot valuation).
LSTAT1AA active (only A lots with LQTYOH>0 are swept).

RXAR — Patient copay AR ledger (key ARSEQ)

FieldTypeMeaning
ARSEQ8S 0AR sequence (key; = the fill's ledger key 10000000 + fill digits).
PATID / FILLNO9AOwing patient / originating fill.
ARDT8S 0Receivable date.
ARAMT / ARPAID11P 2Amount owed (the copay) / amount paid.
ASTAT1AO open, P paid (raised as O; never self-cleared).

RXLEDG — Durable posting / re-runnability ledger (key LSEQ)

FieldTypeMeaning
LSEQ8S 0Stable business key (key), disjoint range per program — see F.2.
LTYPE1AJ adjudication, R remittance, G GL feed, V inventory value.
LKEY / LREF9ABusiness key / reference (patient, remit no, etc.).
LDT8S 0Post date.
LAMT / LAMT211P 2Primary / secondary amount (e.g. plan-pay + copay).
LMEMO30AFree-text memo.

RXREM — Weekly plan remittance header (key REMNO)

FieldTypeMeaning
REMNO9ARemittance number (key), e.g. RM000100.
PLANID8APaying plan.
REMRUN6S 0Weekly run period (202632 in source).
REMAMT / REMCNT11P 2 / 5P 0Remitted total / fills paid.
REMDT / REMSTAT8S 0 / 1ARemittance date / status (I).

RXWORK — Weekly pended-fill worklist (key WKSEQ)

FieldTypeMeaning
WKSEQ8S 0Worklist sequence (key; = 20000000 + fill digits).
FILLNO9APended fill.
WKRUN6S 0Weekly run period.
WKRSN2AW1 stock, W2 high-dollar, W3 override.
WKAMT / WKSTAT / WKMEMO11P 2 / 1A / 30AAmount / status (O) / memo.

RXGLDIST — Monthly GL distribution (SQL, PK GDSEQ)

ColumnTypeMeaning
GDSEQ / GDBATCHDEC(8) / DEC(6)Row sequence (PK, from 50000000) / batch period.
GDACCT / GDDRCRCHAR(12) / CHAR(1)GL account (e.g. 5100-RXCOGS) / D debit or C credit.
GDAMTDEC(11,2)Amount.
GDCAT / GDREF / GDDTCHAR(3) / CHAR(9) / DEC(8)Category (COG/PLN/CSH/COP) / reference / date. Index RXGLDACC on (GDACCT, GDDRCR).

RXINVVAL — Monthly inventory valuation (SQL, PK IVSEQ)

ColumnTypeMeaning
IVSEQ / IVPERIODDEC(8) / DEC(6)Row sequence (PK, from 70000000) / valuation period.
NDCCHAR(11)Drug valued.
IVQTYOH / IVCOSTDEC(9,2) / DEC(11,2)Lot qty on hand / that lot's own cost. Index RXINVPER on (IVPERIOD, NDC).

Relationships

E. Operations Runbook ↑ top

E.1 Day-in-the-life

  1. First-time build only: CALL PHARMOS/RXSETUP (creates every object, compiles every program) then CALL PHARMOS/RXREFLD (seeds plans, prescribers, drugs, patients, opening lots). The seed banner should read RXREFLD PLANS=2 PRESCRIBERS=3 DRUGS=4 PATIENTS=3 LOTS=4.
  2. Confirm the job's library list includes PHARMOS (LIBL = QSYS QGPL PHARMOS QTEMP, CURLIB = PHARMOS).
  3. Fills for the day arrive as RXFILL rows in FSTAT='E' (from the counter feed).
  4. Submit the daily cycle: SBMJOB CMD(CALL PGM(PHARMOS/RXDAILY)).
  5. Post-check the three daily banners (see below).
  6. Use CALL PHARMOS/RXMENU during the day to inspect any fill's pricing (option 1) or a patient's fill history (option 2).

Post-checks after the daily cycle:

Weekly (on the remittance day): submit RXWEEK. Confirm RXPEND PENDED= matches the fills needing review, and RXREMIT PAYMENTS=/TOTAL= equals one payment per plan that has adjudicated fills, summing the plan-paid amounts. The remittance advice / EOB prints to the RXREMP spooled file.

E.2 Month-end close

  1. Confirm every business day's RXDAILY and every weekly RXWEEK has run for the month (all owed plans remitted, so cash settles correctly).
  2. Submit RXMONTH. Confirm the three banners: RXGLDST BATCH=202608 ROWS=8, the RXAGE aging lines, and RXINVAL PERIOD=202608 LOTS=n VALUE=....
  3. Reconcile the GL distribution (it is always balanced):
SELECT GDACCT, GDDRCR, SUM(GDAMT) FROM PHARMOS.RXGLDIST
  WHERE GDBATCH = 202608 GROUP BY GDACCT, GDDRCR ORDER BY GDACCT;
-- balance check: SUM(debits) must equal SUM(credits)
SELECT (SELECT SUM(GDAMT) FROM PHARMOS.RXGLDIST WHERE GDDRCR='D')  AS DR,
       (SELECT SUM(GDAMT) FROM PHARMOS.RXGLDIST WHERE GDDRCR='C')  AS CR
  FROM SYSIBM.SYSDUMMY1;

Reconciling figures (the same ones the cycles simulation checks against a hand-derived oracle, for the tested book where both fills are remitted):

E.3 Failure & re-run rules

Every batch program is designed to be safely re-runnable; the re-runnability guard is the durable RXLEDG ledger (F.2). A healthy run's banner shows work done on the first pass and zero on a repeat.

SituationBehaviourAction
Re-run RXINTAKE / RXELIGOnly FSTAT='E' (resp. 'V') fills are touched; already-advanced fills are skipped.Safe no-op. Re-submit RXDAILY freely; each stage re-processes only its own inbound status.
Re-run RXADJUDThe stable per-fill ledger key (J = 10000000 + fill digits) is CHAINed first; an already-adjudicated fill is skipped.Idempotent — never decrements a lot twice, never double-copays, never re-posts AR.
Short-stock fill (S1)Availability is checked across all lots WITHOUT writing; if short the fill denies S1 and inventory is untouched.Restock the lot(s), then re-run RXADJUD — the fill will adjudicate cleanly with no partial decrement to unwind.
Re-run RXPENDWorklist key derived from the fill; the row is already there.Safe no-op — the fill is skipped, not queued twice.
Re-run RXREMITStable per-plan ledger key (R = 30000000 + plan digits); the plan already has its remittance row.Idempotent — a plan can never be paid twice for the same fills (still 2 headers, not 4).
Re-run RXGLDST in the same monthPosts only the MOVEMENT: it re-queries RXGLDIST for what already posted and finds nothing new.Idempotent per period — a second run posts ZERO rows (still 8, not 16).
Re-run RXINVAL in the same periodPeriod-derived ledger key (V = 70000000 + period) is CHAINed before any RXINVVAL row is written.Idempotent per period — no second snapshot (still one row per lot).
Re-run RXAGEPure read/report — posts nothing.Naturally re-runnable; reports the same totals from the same open rows.
Because every money movement writes an RXLEDG row (with the plan-pay and copay amounts, the reference and a memo) and every posting is keyed off a stable business key, any cycle's effect is fully reconstructable after the fact for reconciliation and recovery, and no cycle can double-post on a re-run.

F. Developer Reference ↑ top

The complete program surface, from src/sources.mjs. All objects are in library PHARMOS. The DDS/RPG/COBOL/CL sources are held as JS string constants and loaded into the library's source physical files (QDDSSRC, QRPGLESRC, QCBLLESRC, QSQLSRC, QCLSRC) by seedPharmos() in src/seed.mjs, mirroring the emulator's own DEMOLIB convention.

F.1 Programs

RXREFLD (RPG) — reference seed
Writes the seeded plans, prescribers, drugs, patients and opening lots; the fixtures the whole app and its tests are built on (incl. two lots of one drug at different expiry so FIFO has something to prove, and a deliberately thin controlled-substance lot for the out-of-stock path).
RXINTAKE (RPG) — daily intake edit
Reads every fill; for FSTAT='E' validates qty>0 (E1), days-supply>0 (E2) and Rx exists (E3 via CHAIN(E) on RXRX), then re-CHAINs the fill and stamps 'V' or 'D'+code. The re-CHAIN after the Rx lookup is deliberate — the RXRX CHAIN moved the file cursor.
RXELIG (RPG) — daily eligibility
For each 'V' fill, CHAINs patient (D1/D2), drug (D5/D6), Rx (D3/D7/D8/D9) and prescriber (D3/D4). On pass it uses a genuine RPG indicator *IN80 as the "this fill passed" boolean and consumes a refill slot (RXREFUSD+1) at eligibility, so a later stock-pend still counts the dispense against refills. (See F.5 for why *IN80 replaced an N-type field.)
RXADJUD (RPG) — the adjudication engine
The program the app exists for. Per 'V' fill, in fill-number order: CHAIN the durable J ledger key first (skip if present); price billed = DAWP×FQTY; read the plan through the patient; allowed = billed×PLCONTR; copay by plan type, capped at allowed and floored at 0; plan-pay = allowed−copay; cost = DCOST×FQTY. Then the two-pass FIFO decrement (PICKLOT): pass 1 sums availability writing nothing (pend S1 if short), pass 2 draws from the lowest-LEXPDT open lot each iteration until satisfied. Finally writes the copay to RXAR (open), the RXLEDG(J) row, and stamps the fill priced + 'A'.
RXPEND (RPG) — weekly worklist builder
Queues fills: W1 (denied S1, carries billed), W2 (adjudicated, FPLANPD>200.00), W3 (denied D8/D9, carries billed). Advisory only: writes RXWORK, does not change FSTAT. Idempotent on a fill-derived key.
RXREMIT (RPG) — weekly plan remittance + EOB
For each plan, CHAINs the R ledger key (skip if present); totals that plan's adjudicated fills (joining fill→patient→plan since RXFILL has no plan id); writes one RXREM header, stamps each fill 'X'+FREMNO, writes RXLEDG(R), and prints the RXREMP remittance advice with overflow handling.
RXGLDST (SQLRPGLE) — monthly GL distribution
Accumulates COGS/plan/cash/copay over A/X fills, then re-queries RXGLDIST for what has already posted and inserts only the difference as four balanced DR/CR pairs via free-form embedded SQL (gdrow subroutine). Self-idempotent per period without a per-run counter.
RXINVAL (SQLRPGLE) — monthly inventory valuation
CHAINs the period-derived V ledger key first (refuse if present), then values each open lot at LQTYOH×LCOST (its own cost) and inserts one RXINVVAL row per lot via the ivrow embedded-SQL subroutine.
RXAGE (COBOL) — monthly AR aging
Sequentially reads RXAR; for each ASTAT='O' row computes age = 20260831 − ARDT and buckets CURRENT (≤30) / 31-60 / 61-90 / OVER-90, DISPLAYing each total. Pure report; posts nothing.
RXFILLIQ / RXPROFIQ / RXMENU (RPG) — interactive
Plain fill-pricing inquiry / patient-profile subfile inquiry (clear→load→display over RXFILLP) / menu that CALLs the other two by option. All read-only.
RXSETUP / RXDAILY / RXWEEK / RXMONTH (CL)
Setup (DLTF/CRTPF/CRTLF/CRTDSPF/CRTPRTF/RUNSQLSTM/CRTBNDRPG/CRTBNDCBL for every member) and the three cycle drivers, each ADDLIBLE PHARMOS then CALLing its members in order.

F.2 Re-runnability & the ledger (RXLEDG)

Every posting program contributes to one durable ledger, RXLEDG (UNIQUE on LSEQ), in a disjoint numeric range whose key is derived from a stable business key, so a re-run regenerates the same key and CHAIN(EN) refuses a double post. This is the same idiom the estate's MEDCLAIM/i uses for its posting ledger.

ProgramLTYPEKey formulaGuarantee
RXADJUDJ10000000 + fill digitsNever decrement a lot / post AR twice.
RXREMITR30000000 + plan slotNever pay a plan twice for the same fills.
RXGLDSTG50000000 + batch slot (plus a self-query of RXGLDIST)Post only the period's movement.
RXINVALV70000000 + periodOne valuation snapshot per period.

RXAR.ARSEQ reuses the adjudication key (10000000 + fill digits) and RXWORK.WKSEQ uses 20000000 + fill digits, so those rows are equally un-duplicable. The ranges never overlap.

F.3 Denial & pend codes

CodeRaised byMeaning
E1 / E2 / E3RXINTAKEQuantity not positive / days-supply not positive / prescription not found.
D1 / D2RXELIGPatient not found / patient inactive.
D3 / D4RXELIGPrescriber not found / prescriber suspended.
D5 / D6RXELIGDrug not found / drug discontinued.
D7 / D8 / D9RXELIGRx discontinued / past refill-expiry window / refills exhausted.
S1RXADJUDInsufficient stock across all lots — fill pends, inventory untouched.
W1 / W2 / W3RXPENDWorklist reasons: stock (from S1) / high-dollar (>200.00) / override (from D8/D9).

F.4 Embedded-SQL & RPG patterns

F.5 Platform finding — PG-PHARMOS-001

Building PHARMOS/i surfaced one genuine platform finding, logged in pharmacy-app/FINDINGS.md (the many other issues hit during the build were the author's own app bugs — a missing DSPF SFILE keyword, an invalid edit code, 9A ids one char too long, a duplicate JS source key — and were fixed in source, not logged):

PG-PHARMOS-001 (Minor, confirmed, not fixed): the RPG-to-COBOL front end (ibmi/compile.js) mis-compiles an RPG D-spec declared with the boolean/indicator type N when it is later assigned *ON/*OFF — it emits a numeric COMPUTE WFLAG = "0" against a PIC X(1) receiver, which GnuCOBOL rejects. First hit in RXELIG, where a work field WPASSTHIS declared 1N carried "this fill passed" across two CHAINs. Workaround (in shipped source): replaced the N field with a genuine RPG indicator *IN80 — the primitive every other estate app uses for exactly this kind of cross-statement boolean, so the fix carries no new risk. The finding was left unfixed because no existing estate app uses the N D-spec type, so there is no regression risk in leaving it, and the house rule is to log rather than silently work around. No other PHARMOS/i program uses the N type.

G. Glossary ↑ top

Adjudication
Pricing a fill against the patient's plan: split the allowed amount into what the plan pays and what the patient owes as copay. Here: RXADJUD, with the invariant plan-pay + copay = allowed.
Allowed amount
Billed × the plan's contracted reimbursement rate (PLCONTR) — the network-priced amount the plan and patient split.
AWP — Average Wholesale Price
A public per-unit billing benchmark (DAWP) the pharmacy bills against; distinct by design from the pharmacy's own acquisition cost (DCOST).
Copay
The patient's share of the allowed amount: a flat amount (plan type F) or a percentage of allowed (plan type P), capped so it never exceeds allowed and floored at zero.
EOB / remittance advice
The printed statement (RXREMP) a plan's weekly remittance produces, listing the fills paid and the remitted total.
FIFO by expiry
Drawing dispensed quantity from inventory lots earliest-expiry-first (LEXPDT), all-or-nothing — RXADJUD's PICKLOT sweep.
Idempotent
Safe to run again with the same result. RXADJUD, RXREMIT, RXGLDST and RXINVAL are idempotent via the stable-key RXLEDG guard; RXAGE is idempotent because it only reports.
Ingredient cost / cost of goods
The pharmacy's own cost of the drug dispensed (DCOST×quantity), tracked for margin and the monthly inventory valuation, independent of the plan pricing chain.
Ledger (RXLEDG)
The durable posting/re-runnability ledger; each posting program writes a row keyed by a stable business key in a disjoint range, so a re-run finds it and refuses to double-post.
Lot
A received batch of a drug (RXLOT, composite key NDC+LOTNO) with its own expiry and cost; FIFO dispensing and lot-by-lot valuation both depend on the composite key.
NDC — National Drug Code
The 11-digit drug identifier that is the spine of RXDRUG and RXLOT.
Pended fill
A fill queued onto the human worklist (RXWORK) for review — advisory only; it does not change the fill's status or block remittance.
Plan pay
What the third-party plan owes on a fill: allowed − copay (FPLANPD).
SBMJOB
Submit Job — the IBM i command that queues a program to run as a batch job (e.g. SBMJOB CMD(CALL PGM(PHARMOS/RXDAILY))).
Subfile
A 5250 display construct listing many rows on one screen (DDS SFL/SFLCTL). RXPROFIQ's fill-history list is a subfile.