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.
PHARMOS/i runs the full life of a retail pharmacy dispense:
FSTAT='E') is structurally
validated: dispensed quantity positive, days supply positive, and the fill must reference a
prescription that exists. Clean fills advance to validated (FSTAT='V'); broken ones are
denied with an edit code.Every program obeys one pricing chain, stated in the source header and proven by the test oracles:
DAWP) × quantity dispensed (FQTY)
— what the pharmacy charges before any contract discount.PLCONTR).PLCOPAY, plan type F) or
allowed × the plan's copay percentage (PLCOPCT, plan type P), then
capped so it never exceeds the allowed amount and floored at zero.DCOST) ×
quantity — the pharmacy's own cost, tracked independently of the plan pricing chain, for margin
and for the monthly inventory valuation.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.
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.
| Object | Type | Role |
|---|---|---|
| RXPAT | PF | Patient master. |
| RXPRVDR | PF | Prescriber master. |
| RXDRUG | PF | Drug / NDC master (AWP, cost, schedule). |
| RXPLAN | PF | Third-party plan master (discount, copay rule). |
| RXRX | PF | Prescription header (refills, expiry). |
| RXFILL | PF | Fill / dispense event (the pricing spine). |
| RXFILLP / RXFILLD | LF | Fills keyed by patient / by drug (access paths). |
| RXLOT | PF | Inventory lots, composite key (NDC, LOTNO), FIFO by expiry. |
| RXAR | PF | Patient copay AR ledger. |
| RXLEDG | PF | Durable posting / re-runnability ledger. |
| RXREM | PF | Weekly plan remittance headers. |
| RXWORK | PF | Weekly pended-fill worklist. |
| RXGLDIST | SQL table | Monthly GL distribution (balanced DR/CR). |
| RXINVVAL | SQL table | Monthly inventory valuation snapshot. |
| RXFILLD1 / RXPROFD / RXMENUD | DSPF | Fill inquiry / patient profile / menu. |
| RXREMP | PRTF | Remittance-advice / EOB printer file. |
| RXREFLD | RPG | Seed reference data (plans, drugs, patients, lots). |
| RXINTAKE / RXELIG / RXADJUD | RPG | Daily engine: edit / eligibility / adjudicate. |
| RXPEND / RXREMIT | RPG | Weekly worklist / plan remittance + EOB. |
| RXGLDST / RXINVAL | SQLRPGLE | Monthly GL post / inventory valuation. |
| RXAGE | COBOL | Monthly AR aging report. |
| RXFILLIQ / RXPROFIQ / RXMENU | RPG | Interactive fill inquiry / patient profile / menu. |
| RXSETUP | CL | Create every object, compile every program. |
| RXDAILY / RXWEEK / RXMONTH | CL | The 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.
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 this | Type on the command line |
|---|---|
| Open the operator menu (routes to both inquiry screens) | CALL PHARMOS/RXMENU |
| Open the single-fill pricing inquiry directly | CALL PHARMOS/RXFILLIQ |
| Open the patient-profile subfile inquiry directly | CALL PHARMOS/RXPROFIQ |
| Run the daily fill/adjudication cycle | CALL PHARMOS/RXDAILY (or SBMJOB it) |
| Run the weekly pend + remittance cycle | CALL PHARMOS/RXWEEK |
| Run the monthly close cycle | CALL PHARMOS/RXMONTH |
| First-time build: create objects & compile programs | CALL 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.
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.
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.
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).
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.
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.
| Subfile field (PSFL) | Type (DDS) | Shows |
|---|---|---|
| SFILNO | 9A output | Fill number. |
| SNDC | 11A output | Drug NDC. |
| SFDT | 8A output | Fill date (YYYYMMDD). |
| SBILL / SPLPD / SCOPY | 12A output | Billed / 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.
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:
S1) and touches no lot — there is never a partially
decremented lot.J, remittance R, GL G,
inventory-value V) with CHAIN(EN) before it acts; a re-run finds the row and
refuses, so inventory is never decremented twice, a copay never posted twice, a plan never paid twice.ASTAT='O'); it is never marked paid by the program that raised it — only an explicit
remittance record settles cash. Balances are not invented.RXLEDG row (plan-pay + copay amounts,
reference, memo), giving a reconstructable trail per fill, per plan and per period.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.
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)
| Program | Cycle | Purpose | Reads | Writes / result banner |
|---|---|---|---|---|
| RXINTAKE | Daily 1 | Fill intake edit: qty>0, days-supply>0, Rx exists. | RXFILL, RXRX | Stamps V (valid) or D+edit code; RXINTAKE VALID=/REJECT=/SKIP=. |
| RXELIG | Daily 2 | Patient/prescriber/drug/Rx eligibility; consumes a refill slot on pass. | RXFILL, RXRX, RXPAT, RXPRVDR, RXDRUG | Stamps D+D1–D9 or leaves V; bumps RXREFUSD; RXELIG PASS=/DENY=/SKIP=. |
| RXADJUD | Daily 3 | THE ENGINE: price, plan-pay/copay split, FIFO lot decrement, AR post. | RXFILL, RXRX, RXPAT, RXPLAN, RXDRUG, RXLOT | RXFILL priced+A / D+S1; RXLOT decremented; RXAR + RXLEDG(J) rows; RXADJUD ADJUDICATED=/PENDED=/SKIP=. |
| RXPEND | Weekly 1 | Build the human worklist (W1 stock / W2 high-dollar / W3 override). | RXFILL | RXWORK rows; RXPEND PENDED=/SKIP=. Advisory: does not change FSTAT. |
| RXREMIT | Weekly 2 | Post one remittance per plan, stamp fills X, print the EOB. | RXPLAN, RXFILL, RXPAT | RXREM + RXLEDG(R) rows; RXFILL→X; RXREMP print; RXREMIT PAYMENTS=/TOTAL=/SKIP=. |
| RXGLDST | Monthly 1 | Post balanced DR/CR GL rows for the month's movement. | RXFILL, RXGLDIST | RXGLDIST rows (8 for the tested book); RXGLDST BATCH=/ROWS=/COGS=. |
| RXAGE | Monthly 2 | Age open copay AR into CURRENT / 31-60 / 61-90 / OVER-90. | RXAR | Report only, no posting; RXAGE OPEN/CURRENT/31-60/61-90/OVER90/COUNT. |
| RXINVAL | Monthly 3 | Value every open lot at its own cost, snapshot per lot. | RXLOT, RXLEDG | RXINVVAL rows + RXLEDG(V) guard; RXINVAL PERIOD=/LOTS=/VALUE=. |
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 D1–D9); 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)
LEXPDT, not lot number or file order — PICKLOT re-scans for
the lowest-expiry open lot each pass.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
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
'V' fills are checked), eligibility before adjudication (only eligible 'V' fills
are priced, and the refill slot is consumed at eligibility so a later stock-pend still counts it).RXPEND and RXREMIT only act on fills that reached
'A'/'D' in the daily cycle — run the day's adjudication first or the plan
is under-remitted and the worklist is empty.RXGLDST keys off adjudicated/remitted
pricing and cash settles only on 'X' (remitted) fills, so the weekly remittance must run
before the month's cash line is correct; the inventory valuation must be computed on
already-decremented lots.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.
| Field | Type | Meaning |
|---|---|---|
| PATID | 9A | Patient id (key), e.g. PT0000001. |
| PNAME | 30A | Patient name. |
| PDOB | 8S 0 | Date of birth (YYYYMMDD). |
| PPLAN | 8A | Third-party plan id (→RXPLAN). |
| PALRGY | 1A | Documented-allergy flag Y/N (informational; not enforced by any program). |
| PSTAT | 1A | A active, I inactive (inactive → eligibility D2). |
| Field | Type | Meaning |
|---|---|---|
| PRVID | 9A | Prescriber id (key). |
| PRNAME | 30A | Prescriber name. |
| PRDEA / PRNPI | 9A / 10A | DEA number / NPI. |
| PRSTAT | 1A | A active, S suspended (suspended → eligibility D4). |
| Field | Type | Meaning |
|---|---|---|
| NDC | 11A | 11-digit National Drug Code (key). |
| DNAME | 30A | Drug name. |
| DAWP | 9P 4 | Average Wholesale Price per unit — the billing benchmark. |
| DCOST | 9P 4 | Pharmacy's own per-unit acquisition cost (distinct from DAWP by design). |
| DGENERIC | 1A | Generic Y/N. |
| DSCHED | 1A | Controlled-substance schedule 0–5 (0 = not controlled). |
| DSTAT | 1A | A active, D discontinued (discontinued → eligibility D6). |
| Field | Type | Meaning |
|---|---|---|
| PLANID | 8A | Plan id (key). |
| PLDESC | 30A | Plan description. |
| PLCONTR | 5P 4 | Contracted reimbursement rate off billed AWP (allowed = billed × PLCONTR). |
| PLPCTPD | 5P 4 | Plan's stated pay percentage of allowed (informational alongside the copay split). |
| PLCOTYP | 1A | Copay type: F flat, P percentage. |
| PLCOPAY | 11P 2 | Flat copay amount (used when PLCOTYP='F'). |
| PLCOPCT | 5P 4 | Copay percentage of allowed (used when PLCOTYP='P'). |
| PLSTAT | 1A | A 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).
| Field | Type | Meaning |
|---|---|---|
| RXNO | 9A | Prescription number (key). |
| PATID / PRVID / NDC | 9A / 9A / 11A | Patient / prescriber / drug. |
| RXWDT / RXEXPDT | 8S 0 | Date written / refill-expiry date (a fill after RXEXPDT → D8). |
| RXQTY | 7P 2 | Prescribed quantity. |
| RXDAYSUP | 3S 0 | Days supply. |
| RXREFTOT / RXREFUSD | 2S 0 | Refills authorised / used (RXREFUSD ≥ RXREFTOT → D9). |
| RXSTAT | 1A | A active, X expired, D discontinued (D → D7). |
| Field | Type | Meaning |
|---|---|---|
| FILLNO | 9A | Fill number (key), e.g. F00000101. |
| RXNO / PATID / NDC | 9A / 9A / 11A | Source Rx / patient / drug. |
| FDT | 8S 0 | Fill date (YYYYMMDD). |
| FQTY / FDAYSUP | 7P 2 / 3S 0 | Quantity dispensed / days supply. |
| FBILLED | 11P 2 | Billed = DAWP × FQTY. |
| FALLOWD | 11P 2 | Allowed = FBILLED × PLCONTR. |
| FPLANPD | 11P 2 | Plan pay = FALLOWD − FCOPAY. |
| FCOPAY | 11P 2 | Patient copay (invariant: FPLANPD + FCOPAY = FALLOWD). |
| FCOST | 11P 2 | Cost of goods = DCOST × FQTY. |
| FSTAT | 1A | E entered, V validated, A adjudicated, D denied, X remitted. |
| FDENCD | 2A | Denial/pend reason (E1–E3, D1–D9, S1). |
| FLOTNO | 6S 0 | Lot reference field. |
| FREMNO | 9A | Remittance 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.
| Field | Type | Meaning |
|---|---|---|
| NDC / LOTNO | 11A / 6S 0 | Drug + lot number (composite key — a drug has many lots). |
| LEXPDT | 8S 0 | Lot expiry date — the FIFO ordering key (earliest first). |
| LQTYRCV / LQTYOH | 9P 2 | Quantity received / on hand (decremented by a fill). |
| LCOST | 9P 4 | This lot's own acquisition cost (used for lot-by-lot valuation). |
| LSTAT | 1A | A active (only A lots with LQTYOH>0 are swept). |
| Field | Type | Meaning |
|---|---|---|
| ARSEQ | 8S 0 | AR sequence (key; = the fill's ledger key 10000000 + fill digits). |
| PATID / FILLNO | 9A | Owing patient / originating fill. |
| ARDT | 8S 0 | Receivable date. |
| ARAMT / ARPAID | 11P 2 | Amount owed (the copay) / amount paid. |
| ASTAT | 1A | O open, P paid (raised as O; never self-cleared). |
| Field | Type | Meaning |
|---|---|---|
| LSEQ | 8S 0 | Stable business key (key), disjoint range per program — see F.2. |
| LTYPE | 1A | J adjudication, R remittance, G GL feed, V inventory value. |
| LKEY / LREF | 9A | Business key / reference (patient, remit no, etc.). |
| LDT | 8S 0 | Post date. |
| LAMT / LAMT2 | 11P 2 | Primary / secondary amount (e.g. plan-pay + copay). |
| LMEMO | 30A | Free-text memo. |
| Field | Type | Meaning |
|---|---|---|
| REMNO | 9A | Remittance number (key), e.g. RM000100. |
| PLANID | 8A | Paying plan. |
| REMRUN | 6S 0 | Weekly run period (202632 in source). |
| REMAMT / REMCNT | 11P 2 / 5P 0 | Remitted total / fills paid. |
| REMDT / REMSTAT | 8S 0 / 1A | Remittance date / status (I). |
| Field | Type | Meaning |
|---|---|---|
| WKSEQ | 8S 0 | Worklist sequence (key; = 20000000 + fill digits). |
| FILLNO | 9A | Pended fill. |
| WKRUN | 6S 0 | Weekly run period. |
| WKRSN | 2A | W1 stock, W2 high-dollar, W3 override. |
| WKAMT / WKSTAT / WKMEMO | 11P 2 / 1A / 30A | Amount / status (O) / memo. |
| Column | Type | Meaning |
|---|---|---|
| GDSEQ / GDBATCH | DEC(8) / DEC(6) | Row sequence (PK, from 50000000) / batch period. |
| GDACCT / GDDRCR | CHAR(12) / CHAR(1) | GL account (e.g. 5100-RXCOGS) / D debit or C credit. |
| GDAMT | DEC(11,2) | Amount. |
| GDCAT / GDREF / GDDT | CHAR(3) / CHAR(9) / DEC(8) | Category (COG/PLN/CSH/COP) / reference / date. Index RXGLDACC on (GDACCT, GDDRCR). |
| Column | Type | Meaning |
|---|---|---|
| IVSEQ / IVPERIOD | DEC(8) / DEC(6) | Row sequence (PK, from 70000000) / valuation period. |
| NDC | CHAR(11) | Drug valued. |
| IVQTYOH / IVCOST | DEC(9,2) / DEC(11,2) | Lot qty on hand / that lot's own cost. Index RXINVPER on (IVPERIOD, NDC). |
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.PHARMOS
(LIBL = QSYS QGPL PHARMOS QTEMP, CURLIB = PHARMOS).RXFILL rows in FSTAT='E' (from the counter feed).SBMJOB CMD(CALL PGM(PHARMOS/RXDAILY)).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:
RXINTAKE VALID=/REJECT= — every clean fill validated; every structural reject carries
an E1–E3 code.RXELIG PASS=/DENY= — denials reconcile to the D-code reasons (D2 inactive patient, D4
suspended prescriber, D6 discontinued drug, etc.); each pass consumed one refill (RXREFUSD).RXADJUD ADJUDICATED=/PENDED= — each adjudicated fill has
FPLANPD + FCOPAY = FALLOWD exactly; each pend is S1 and left its lots
untouched; one RXAR open row and one RXLEDG(J) row per adjudicated fill.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.
RXDAILY and every weekly RXWEEK has run for the
month (all owed plans remitted, so cash settles correctly).RXMONTH. Confirm the three banners:
RXGLDST BATCH=202608 ROWS=8, the RXAGE aging lines, and
RXINVAL PERIOD=202608 LOTS=n VALUE=....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):
FCOST) over A/X fills — e.g. 180.00 (LISINOPRIL 1.8000×100)
+ 66.00 (ATORVASTATIN 2.2000×30) = 246.00 → DR 5100-RXCOGS / CR 1310-INVENT.FPLANPD) = 215.00 + 74.40 = 289.40 →
DR 1210-PLNREC / CR 4100-RXPLNI.FPLANPD) over 'X' fills only = 289.40
→ DR 1010-CASH / CR 1210-PLNREC.FCOPAY) = 10.00 (flat) + 18.60 (percentage) = 28.60
→ DR 1220-COPREC / CR 4200-RXCOPI.LQTYOH×LCOST) over open lots, each lot at
its own cost (never a blend) — one RXINVVAL row per lot.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.
| Situation | Behaviour | Action |
|---|---|---|
| Re-run RXINTAKE / RXELIG | Only 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 RXADJUD | The 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 RXPEND | Worklist key derived from the fill; the row is already there. | Safe no-op — the fill is skipped, not queued twice. |
| Re-run RXREMIT | Stable 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 month | Posts 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 period | Period-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 RXAGE | Pure read/report — posts nothing. | Naturally re-runnable; reports the same totals from the same open rows. |
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.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.
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.'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.)'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'.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.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.gdrow subroutine). Self-idempotent per period without a per-run counter.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.ASTAT='O' row computes age = 20260831 − ARDT
and buckets CURRENT (≤30) / 31-60 / 61-90 / OVER-90, DISPLAYing each total. Pure report; posts nothing.RXFILLP) / menu that CALLs the other two by option. All read-only.ADDLIBLE PHARMOS then CALLing its members in order.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.
| Program | LTYPE | Key formula | Guarantee |
|---|---|---|---|
| RXADJUD | J | 10000000 + fill digits | Never decrement a lot / post AR twice. |
| RXREMIT | R | 30000000 + plan slot | Never pay a plan twice for the same fills. |
| RXGLDST | G | 50000000 + batch slot (plus a self-query of RXGLDIST) | Post only the period's movement. |
| RXINVAL | V | 70000000 + period | One 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.
| Code | Raised by | Meaning |
|---|---|---|
| E1 / E2 / E3 | RXINTAKE | Quantity not positive / days-supply not positive / prescription not found. |
| D1 / D2 | RXELIG | Patient not found / patient inactive. |
| D3 / D4 | RXELIG | Prescriber not found / prescriber suspended. |
| D5 / D6 | RXELIG | Drug not found / drug discontinued. |
| D7 / D8 / D9 | RXELIG | Rx discontinued / past refill-expiry window / refills exhausted. |
| S1 | RXADJUD | Insufficient stock across all lots — fill pends, inventory untouched. |
| W1 / W2 / W3 | RXPEND | Worklist reasons: stock (from S1) / high-dollar (>200.00) / override (from D8/D9). |
RXFILLR (or lot) by its key before UPDATE, because the intervening CHAIN moved
the file cursor — a documented, deliberate discipline throughout.PICKLOT scans lots with a partial-key
READE to find the lowest-expiry lot, then re-CHAINs the winner by its full
KLIST (NDC, LOTNO) so a partial key can't match the wrong lot.*IN80 in RXELIG carries "this
fill passed" across CHAINs — the proven primitive, not a custom work field (see F.5).exec sql select coalesce(...) into :hv ... and exec sql insert ... with host
variables, checking sqlcod after the insert; RXGLDST's self-query of RXGLDIST is what makes
it post only the movement.CHAIN(EN) against RXLEDG on the
stable key, with an indicator testing whether the row already exists before any write.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):
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.PLCONTR) — the network-priced
amount the plan and patient split.DAWP) the pharmacy bills against; distinct by design
from the pharmacy's own acquisition cost (DCOST).RXREMP) a plan's weekly remittance produces, listing the fills paid
and the remitted total.LEXPDT), all-or-nothing
— RXADJUD's PICKLOT sweep.DCOST×quantity), tracked for margin and
the monthly inventory valuation, independent of the plan pricing chain.RXLOT, composite key NDC+LOTNO) with its own expiry and cost;
FIFO dispensing and lot-by-lot valuation both depend on the composite key.RXWORK) for review — advisory only; it does not
change the fill's status or block remittance.FPLANPD).SBMJOB CMD(CALL PGM(PHARMOS/RXDAILY))).