# Greenfield Compounding Software — investigation & decision log

> **Status:** discovery / architecture decided; PK audit complete (both passes); recreate-model synthesis next.
> **Owner:** Kyle · **Created:** 2026-05-29 · **Last updated:** 2026-05-29
> **Pattern:** investigation directory (entry point = this README; analytical artifacts added as siblings).
> **App repo (code + PK audit artifacts):** [`AdvancedRxPharmacy/advancedrx-compounding`](https://github.com/AdvancedRxPharmacy/advancedrx-compounding) — this doc is the SF-side anchor; the app + audit live in the sibling repo.

## 0. Analytical artifacts in this directory (merged 2026-06-11)

- [`refill-orderline-linkage.md`](refill-orderline-linkage.md) — Refill→OrderLine linkage for the reservation lifecycle (release-on-fill + reconciliation sweep). Verified 2026-05-30.
- [`due-date-source.md`](due-date-source.md) — the authoritative "due / needed-by" date (`Order.Ship_Out_Date_Due_date__c`).
- [`bud-sufficiency-and-days-supply.md`](bud-sufficiency-and-days-supply.md) — fill-side lot BUD-sufficiency rule + days-supply + shipping-method fields.

## 1. Why this exists

AdvancedRx's compounding lab runs on **PK Software (PKS4)** — a ~20-year-old Windows program on a **Firebird** database. Finished compounds and lots are **manually re-entered into Salesforce** today. PK has **no API**, carries lots of fields/features AdvancedRx doesn't use, and is a daily data-entry tax. We are scoping a **greenfield replacement** compounding application that AdvancedRx owns.

**Hard requirements gathered (2026-05-29):**

- Talk to bench **scales** — OHAUS Pioneer / Explorer / Adventurer (RS-232 / USB serial).
- Talk to **label printers** (ZPL/Zebra-class; Dymo secondary).
- **Barcode scanning, QC, printing, scale integration.**
- **Integrate with Salesforce** — one-way **app → SF** (push finished compound + lot up; automate today's manual entry). SF reads only a de-identified work queue.
- **AI builds and maintains everything** (Claude Code / Codex) — stack chosen for AI-codeability over personal familiarity.
- **ZERO PHI in the compounding software** — keeps it out of HIPAA scope; patient↔lot linkage stays in Salesforce.
- **Build, not buy** — custom large-batch QC won't fit COTS (PioneerRx / MedKeeper / Wolters Kluwer Simplifi / Apothatech all rejected).
- **Limit blast radius** — isolate from the existing PHI Azure e-script pipeline.

## 2. Three production workflows the app must support

1. **Make-to-fill** — a specific order/Rx needs a compound; check stock, make if needed.
2. **Make-to-stock (anticipatory)** — compound bulk batches ahead of demand (e.g., 10,000 caps), driven by inventory levels.
3. **Precount / prefill** — the prefill department packages **bulk** finished goods down into **dispensable units** (e.g., bottles of 180). Wants a **work queue telling them what to precount**, driven by SF demand. This is a _bulk → packaged_ conversion event, same compound/lot/BUD.

→ A compound can be **stocked in two forms at once** (bulk loose units **and** packaged bottles). This requires a **multi-UoM / packaging layer** — the one genuinely new data model.

## 3. Architecture — decision ledger (decided 2026-05-29)

Backed by two external deep-research briefs (platform/SF-integration, and bench-hardware) pasted into the originating session.

| Decision                    | Choice                                                                                                                                                                                                                         | Rationale                                                                                                                                                                                                             |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Build vs buy                | **Build**                                                                                                                                                                                                                      | Custom large-batch QC won't fit COTS                                                                                                                                                                                  |
| Inventory system of record  | **New app (Postgres)** is the authoritative ledger; **Salesforce mirrors** finished-goods availability + stays SoR for patient/Rx/orders/dispensing (**refined 2026-05-29** — see `advancedrx-compounding/docs/data-model.md`) | App owns production + offline bench; SF emits dispense events back. Approximate storefront availability (lab fulfills) → no hard real-time allocation. Multi-bucket/precount/device inventory don't fit SF's `Lot__c` |
| Platform                    | **Render** (conventional always-on: web + worker + cron + Postgres in one `render.yaml`)                                                                                                                                       | Lowest infra surface for an AI to build/maintain for years; beat Railway/Fly/Cloudflare on the AI-maintainability lens                                                                                                |
| Stack                       | **TypeScript end-to-end**                                                                                                                                                                                                      | Largest AI corpus, tightest type-check feedback loop, one language                                                                                                                                                    |
| Datastore                   | **Managed Postgres + Drizzle ORM**                                                                                                                                                                                             | Conventional; pairs with pg-boss                                                                                                                                                                                      |
| Queue (resilient SF writes) | **pg-boss** (Postgres-backed; retries + dead-letter, no Redis)                                                                                                                                                                 | Queue state lives in the DB the agent already inspects                                                                                                                                                                |
| SF auth                     | **External Client App** (NOT Connected App — restricted by default Spring '26) + OAuth Client Credentials + dedicated **API-only Integration User** (free license) on a least-privilege permission set                         | Modern required path; least privilege                                                                                                                                                                                 |
| SF write                    | **Idempotent UPSERT on `Lot__c.Source_System_ID__c`**; Composite API for related writes; Bulk 2.0 only for large reconciliation                                                                                                | Field already exists for this; retries safe                                                                                                                                                                           |
| PHI boundary                | Dedicated **`Compounding_Queue_Item__c`** object (or restricted view) exposing only non-PHI fields; Integration User gets Read FLS on those fields ONLY (never Contact/Rx); **poll on a cron** (not CDC/Platform Events)       | PHI physically absent from the queried surface                                                                                                                                                                        |
| Scales                      | **Web Serial API** (Chromium-only) to OHAUS; pre-grant via `SerialAllowUsbDevicesForUrls`; OHAUS `IP`/`SP` commands, `?`=unstable flag                                                                                         | Balances present as serial ports; native USB option = virtual COM                                                                                                                                                     |
| Label printers              | **Zebra Browser Print** (localhost helper, raw ZPL you author); Dymo secondary; OS print dialog only for batch-record sheets                                                                                                   | Byte-exact, reprintable, vendor-maintained                                                                                                                                                                            |
| Bench browser               | **Managed Chromium (Chrome/Edge), kiosk-locked, version-pinned** — HARD requirement                                                                                                                                            | Web Serial / Browser Print don't exist on Firefox/Safari/iOS                                                                                                                                                          |
| New IT prereq               | **Chrome Local Network Access policy** (`LocalNetworkAccessAllowedForUrls`) for the printer bridge                                                                                                                             | Chrome 142 (Oct 2025) gates page→localhost; expanded to WebSockets in 147                                                                                                                                             |
| Offline                     | PWA: IndexedDB queue + replay-on-reconnect; device I/O is network-independent                                                                                                                                                  | Bench keeps weighing/printing if WAN drops                                                                                                                                                                            |
| Azure isolation             | If any Azure used: **separate subscription**; but the app runs on **Render**, fully separate from the PHI pipeline                                                                                                             | Hard blast-radius boundary                                                                                                                                                                                            |

### Architecture sketch

```
[ Bench (managed Chrome) ]──WebSerial──[ OHAUS scale ]   ──ZPL──[ Zebra printer (Browser Print) ]
        │  HTTPS  (PWA buffers offline)
        ▼
[ Render: web + worker + cron ]  ── Postgres (Drizzle) · pg-boss queue (retry+DLQ) ──  ZERO PHI
        │  OAuth client-credentials (External Client App, dedicated PHI-free integration user)
        │  idempotent UPSERT on Lot__c.Source_System_ID__c ;  cron-poll de-identified queue
        ▼
[ Salesforce ] — finished-goods availability MIRROR; patient/Rx/orders/dispense SoR (emits dispense events back to app)
```

## 4. Existing Salesforce schema (already present, mostly dormant)

The SF data model for compounding + inventory **largely exists already** (built around an earlier PK→SF integration attempt). Confirmed via live SOQL 2026-05-29:

- **`Compound__c`** — master formulation. `Default_Yield__c`, `Beyond_Use_Days__c`, `Product__c` (→ Product2), `Compound_ID__c` + `Formula_ID__c` (External IDs), `Count_of_Ingredients__c` (rollup of Compound_Ingredient).
- **`Compound_Ingredient__c`** — BOM lines (master-detail to both Compound and Chemical; Qty + Units Grams/ML).
- **`Lot__c`** — finished-goods batch. `Lot_Number__c` (unique), `Actual_Yield__c`, `Used_Units__c`, `Remaining_Units__c` (formula = `Actual_Yield__c − Used_Units__c`), `Expiry_Date__c` (labeled "Beyond Use Date"), `Units__c` (13-value picklist), `Source_System_ID__c` (External ID = PK LogMain ID).
- **`Chemical__c`** — raw-material master. `Inventory_Units__c` (Σ Chemical_Inventory), `Lot_Units__c` (Σ ChemicalLot_Junction.Units_Used), `Available_Units__c` (formula = Inventory − Lot), `activated` flag (in PK).
- **`Chemical_Inventory__c`** — raw-material lot inventory. **Built but UNUSED today** (per 2024 transcript — Kyle planned to rebuild it).
- **`ChemicalLot_Junction__c`** — raw-material lot genealogy (master-detail to Chemical + Lot; `Units_Used__c`).
- **`Refill__c`** — fill event (UI label "LotRefill"). `Lot__c` lookup, `Qty__c`.

**Critical gaps (the "no inventory source of truth"):**

- **No decrement-on-dispense.** `Lot__c.Used_Units__c` is a bare number **nothing writes** — confirmed not maintained today. `InventoryAPI.cls` is a read-only lookup (no stock logic, not even `with sharing`). The `LotRefill` flow is **BeforeSave-only** (audit fields); there is **no AfterSave sibling** to decrement the lot. → SF build item: add `LotRefill_Insert_Update_Related_Records` (AfterSave) to decrement on fill.
- **No multi-UoM / packaging layer** (bulk vs packaged) — must be designed.
- `Chemical_Inventory__c` is abandoned — free to model fresh.

## 5. PK (PKS4 / Firebird) facts — for the audit & eventual migration

- **Engine:** Firebird (the "robin" Kyle recalled = **FlameRobin**, the GUI tool, not the engine).
- **Database file:** `D:\DATA\PKS4\Data\cmpdwin.pkf` on host **`SERVER01`** (custom `.pkf` extension — why `*.fdb`/`*.gdb` searches missed it).
- **Connection (read-only):** Firebird, host `SERVER01`, port `3050`, user **`READONLYUSER`**, password **`ReadOnly1234$`**. Connected successfully via **DBeaver** (JDBC/Jaybird — no ODBC) on 2026-05-29.
- **Two historical pipelines (from 2024-03-22 transcript):** (a) PK exports CSV files to a file server (`server O2`); (b) ADF + Self-hosted Integration Runtime over Firebird ODBC. A medallion bronze→silver→gold ADF pipeline pushed PK→SF every 15 min, upserting on External IDs. **The new app makes that continuous sync obsolete** — we connect to PK only to (a) audit and (b) migrate once.

### PK → Salesforce table map — CONFIRMED (audit pass 1, 2026-05-29)

Pass 1 ran on the DB-access PC (logged in as `advancedrx\kyle03`; outputs landed in `C:\Users\KYLE03\pk-audit\` — the originally-specified `kyle` profile wasn't writable by that account). No CLI SQL client existed (Firebird is client-only on that box; no JDK/Python), so queries ran **read-only via DBeaver's bundled JRE 21 + Jaybird JDBC** through two tiny compiled Java helpers (Eclipse compiler downloaded). Outputs: `schema.csv` (8,324 columns), `rowcounts.csv` (339 tables), `pk-audit-findings.md`, `samples\` (non-PHI), `sql\` (reproducibility).

**Results:**

- **339 user tables; 183 (54%) are DEAD (0 rows).**
- Model confirmed exactly:

| PK table               | rows        | → SF object                    | Key                                            |
| ---------------------- | ----------- | ------------------------------ | ---------------------------------------------- |
| `CHEMICAL`             | live        | `Chemical__c`                  | chemical ID                                    |
| `CHEMINV`              | live        | `Chemical_Inventory__c`        | inventory ID                                   |
| `FORMULA`              | live        | `Compound__c`                  | **`FORMULA_ID`**                               |
| `FORMCHEM` ("FormCam") | live        | `Compound_Ingredient__c` (BOM) | —                                              |
| `LOGMAIN`              | 41,849 lots | `Lot__c`                       | **`LOGMAIN_ID` → `Source_System_ID__c`**       |
| `LOGCHEM` ("LogCam")   | 288K        | `ChemicalLot_Junction__c`      | per-lot weights in **`LOGCHEM.QUANTITY_USED`** |

- **Sync gate CONFIRMED:** `LOGMAIN.FINALIZED = 'T'` (only ~11,089 of 41,849 lots) AND exclude `ISTEMPLOT = 'T'`.
- **QC effectively absent in PK** — `LOGCAPCALC` / `LOGTABCALC` / `LOGLABRESULT` all 0 rows; `QC_CHECK` NULL everywhere → confirms large-batch QC lives in a spreadsheet, which the new app will own. _Caveat: `LOGMAIN` has sterile-batch fields — pass 2 checks whether sterile work is done._
- **No precount/prefill table** — packaging is **field-level on `FORMULA` / `FORMLIST`** plus `UNITS` / `PACKAGING` lookup tables (multi-UoM layer is ours to design).
- **Finished-goods inventory (`FORMINV`) is 0 rows / dead** → PK never tracked finished-goods inventory; it's net-new (built in the **app's Postgres ledger**, with availability mirrored to SF — see §3 / `advancedrx-compounding/docs/data-model.md`).
- **PHI tables** (`PATIENT`, `RXMAIN`, `RXFILL`, `PATALLERGY`, …) exist and were left untouched. The compounding core keys to `FORMULA`, **not** to patients → PHI-free design confirmed achievable.

### PK data-quality landmines (carry into migration)

- AWP values with infinite/many decimals → round to 2.
- Formula names > 80 chars → truncate / use a Full Name field.
- Inventory IDs stored as decimals (`197.0`) → round to integer.
- Deactivated chemicals (`activated = false`) → filter out.
- **Only sync finalized/completed lots** — there is (or must be) a completion flag; LifeFile historically required `finalized = true`. Partially-built Chem/LogChem/LogMain must not sync. **Weights actually used live in LogChem/LogCam** and are NOT in SF today (the new app captures these natively).

## 6. Open items (the only real unknowns left)

1. **PK database audit** — **Pass 1 + Pass 2 DONE 2026-05-29.** Pass 2 produced the recreate map (column-usage, relationships, reference-data, packaging model, `recreate-model.md`) — see §5 + §7 results. **Remaining: synthesize the recreate model against the live Salesforce schema** (split "lives in SF" vs "new app/Postgres"), done in the main session once files are brought back.
2. **Large-batch QC workflow** — lives in a **spreadsheet** (not PK, not SF). Pass 2 confirmed QC/sterile is effectively absent in PK → **non-sterile USP 795 confirmed; sterile model dropped.** The new app absorbs QC and becomes its system of record. Need the spreadsheet to model it (its columns = the QC data model).
3. **Real packaging configs** — concrete bulk-vs-packaged examples + pack sizes + conversion factors, to model the multi-UoM layer. Pass 2: PK models packaging _materials_ (capsule shells/containers) as consumable inventory via the **`FORMDEV`/`LOGDEV` device BOM** (parallel to the chemical BOM); but finished-goods packaging **FORM** (bulk loose vs bottle-of-N) is **not tracked in PK** — genuinely NEW in our app (the precount/prefill workflow). Still need real bulk-vs-packaged examples from Kyle.
4. **"Lot complete" — RESOLVED (pass 3, 2026-05-29).** The real signal is **`LOGMAIN.WASINVENTORYTAKENOUT='T'`** (98.9% of lots; steady 97–100% every year incl. post-2021); `FINALIZED` was an abandoned manual subset. New-app model: **`DRAFT → POSTED`** lifecycle where _posted = inventory committed = the SF availability-push gate_. Migration filter: `WASINVENTORYTAKENOUT='T' AND QUANTITY_MADE>0 AND DATE_MADE IS NOT NULL AND LOGMAIN_ID>0`. Detail: `advancedrx-compounding/docs/pk-audit/lot-complete-signal.md`.
5. **USP <795> / state-board recordkeeping** — deep-research prompt drafted but not yet run; sequence after the PK audit so it can be sharpened to gap-vs-requirement.
6. **PK → new-app/SF data migration** — one-time extraction off Firebird (no API; use read-only ODBC/JDBC or `gbak`).

## 7. PK audit — method & status

Run read-only on the DB-access PC by a Claude Code instance (this session's tools can't reach that machine). Output path on that PC: **`C:\Users\KYLE03\pk-audit\`** (the writable profile). Bring outputs back to this machine for synthesis.

- **Pass 1 — DONE 2026-05-29.** Produced `schema.csv`, `rowcounts.csv`, `pk-audit-findings.md`, `samples\`, `sql\`. Confirmed the model, the `FINALIZED`/`ISTEMPLOT` sync gate, QC-absent, `FORMINV` dead, 54% dead tables. See §5.
- **Pass 2 — DONE 2026-05-29 — "recreate map for live records only."** Produced (in `C:\Users\KYLE03\pk-audit\`) for the in-scope (non-PHI) compounding subgraph:
  1. `live-table-classification.csv` — classify all ~156 live tables (core-compounding / reference-lookup / config-system / PHI-excluded / out-of-scope) with a one-line purpose each.
  2. `column-usage.csv` — per-column fill-rate (% non-null, % non-blank for text), distinct count, min/max for dates/numbers, on in-scope tables → finds DEAD columns inside live tables (the "14 of 128" reduction). Flags 100%-null/blank/constant columns as drop candidates.
  3. `relationships.md` — declared PKs / unique / FK constraints (from `RDB$` system tables) + inferred relationships by ID-column naming; note whether PK enforces FKs at DB level or app-only.
  4. `reference-data.md` — full enumeration of in-scope lookup tables (`UNITS`, `PACKAGING`, dosage form, schedule, drug class, vendor, etc.) + their distinct values (the controlled vocabularies).
  5. `packaging-model.md` — every `FORMULA`/`FORMLIST` field + `UNITS`/`PACKAGING` lookup that encodes units / pack size / packaging / dispense form / default yield, with ~10 sample formulas showing bulk vs packaged (models the multi-UoM layer).
  6. Sterile-batch check on `LOGMAIN`; recency/volume profile of the core transactional tables.
  7. `recreate-model.md` — synthesized minimal entity model to rebuild PK's _active_ compounding domain: each entity with ONLY its used columns (type/length/required), PK + relationships, controlled vocabularies, and a per-entity map to an existing SF object vs new app/Postgres state. Mermaid ER if feasible.
- **PHI discipline:** skip patient/Rx/clinical tables entirely (we are NOT recreating them — they stay in SF). Metadata (names, counts) is fine; never sample their data. Data samples only for non-PHI tables, ≤10 rows.
- Orientation prompts for both passes were handed to Kyle (2026-05-29).

### Pass 2 results (2026-05-29)

All 7 deliverables produced. Highlights:

- **Column dead-weight is severe** (993 cols across 34 in-scope tables, computed in 22s): `FORMULA` 196→**68** used, `CHEMICAL` 128→**41**, `LOGMAIN` 64→**31**. The recreate model keeps only used columns.
- **Integrity is app-enforced** — only 40 declared FKs. The critical `LOGCHEM.LOGMAIN_ID` / `LOGCHEM.CHEMINV_ID` lot→inventory genealogy links have **no DB FK**, yet `CHEMINV_ID` is 99.8% populated (a real link).
- **Two-BOM / device pattern:** chemicals via `FORMCHEM`/`LOGCHEM`; **devices (capsule shells, containers) via `FORMDEV`/`LOGDEV`** — packaging _materials_ modeled as consumable inventory items. Formal pack-size fields (`DISPENSING_PACK_SIZE`, `SPECIFIED_PACK`, `TRACK_STOCKONHAND`) are **dead** → PK never tracked finished-goods packaging FORM; the bulk-vs-bottle precount model is genuinely NEW in our app.
- **Controlled vocabularies enumerated:** `UNITS` (with conversions), `FSTYLE` (dosage forms), `ADMINROUTE`, `PACKAGING`, `HAZARDS`, `SPECIES` (**veterinary compounding** — model species).
- **Sterile/QC effectively absent:** 2 of 41,849 lots sterile, zero sterility/potency data → confirms non-sterile USP 795. **Sterile model dropped.**
- **⚠ Sync-gate assumption BROKEN:** `LOGMAIN.FINALIZED='T'` was abandoned ~2021 (2,155 finalized in 2021 → **1 in 2025**, while production grew to ~4,600 lots/yr). A FINALIZED-only filter misses ~98% of post-2021 lots; `ISTEMPLOT` is never 'T' (no-op). **Need a new "real/complete lot" definition** — see §6 item 6.

### Pass 3 results (2026-05-29)

Embedded-logic + lot-signal + data-quality audit. Published to `advancedrx-compounding/docs/pk-audit/` (`db-logic.md`, `lot-complete-signal.md`, `data-quality-profile.md`, `pack-sizes.csv`, `device-items.csv` + 334 proc/trigger/view source dumps).

- **Lot-complete signal RESOLVED** → `WASINVENTORYTAKENOUT='T'` → `DRAFT → POSTED` lifecycle (see §6 item 6).
- **Critical logic is NOT in PK's DB** — 264 procs (262 are `GEN_NEXT_*` key generators), 55 triggers (~45 are an `LF_PK_EVENT` change-data-capture feed), 11 views (all external-integration exports), 0 computed cols, 0 UDFs. **Lot-numbering, BUD dating, stock roll-up, and inventory-decrement live in the Delphi app → net-new in our app** (they're our inventory-ledger logic anyway). Only real DB rules: a NIOSH-hazard cascade + app-level cascade-deletes (which double as the missing FK graph).
- **`LF_PK_EVENT` CDC feed** → usable as an incremental change feed for a parallel-run migration cutover.
- **Data quality excellent except units:** primary ETL task is normalizing free-typed `QUANTITY_UNITS` to a clean 3-dimension UOM enum; otherwise ~4 non-finite numerics, 2 bad dates, ~21 orphan/sentinel `*_ID ≤ 0` rows (thin ETL guard, then enforce real FKs).
- **Pack size is fully net-new:** `FORMLIST.PACK_SIZE` empty; batch size = `LOGMAIN.QUANTITY_MADE`, hugely variable per compound — confirms make-to-fill + make-to-stock mix.

## 9a. Scope & roadmap (2026-05-29)

The app is a **compounding-ops + inventory platform**, phased — not a single-purpose bench tool:

- **Phase 1** — compounding operations + compounding inventory + an **in-app kanban work board that replaces Trello** for assigning compounders' daily production work (distinct from SF's dispensing fulfillment, which stays in SF), all on a **generic `item`/`lot`/`stock-location`/`ledger` inventory core**.
- **Phase 2+** — **pharmacy-wide inventory that eventually replaces Zoho**: warehouse / OTC / supply items extend the same generic core (additive, no rework). Not built at once; designed-for from day one.

Full detail: [`advancedrx-compounding/docs/architecture.md`](https://github.com/AdvancedRxPharmacy/advancedrx-compounding/blob/main/docs/architecture.md) → Scope & roadmap.

## 8. References

- Existing Azure PHI pipeline (do NOT entangle): `business-context/integrations/azure-integration.md`.
- SF integration conventions: `.claude/rules/apex.md` (Named Credentials / External Client App, upsert on External ID, dedicated integration user).
- Originating session: architecture Q&A + two deep-research briefs + 2024-03-22 PK integration transcript.

## 9. Workflow & inventory model — CONFIRMED 2026-05-29 (Kyle)

Resolves several §6 open items. Bench-detail, QC, and label specifics are deferred to dedicated **lab** sessions.

**Finalize / sync gate.** PK's `FINALIZED` step was a **LifeFile-era** requirement, dropped once they moved to SF (nothing needed it). The new app **reinstates a finalize step** as the natural end-of-bench action; it gates the push to Salesforce. (Migration of _historical_ lots is still open — reference data must migrate regardless.)

**Inventory / packaging model (the core data model):**

- A **`Lot`** (compounded batch) holds a **bulk pool** that is itself **dispensable** (e.g., a one-off 42-cap script pulls from bulk).
- **Precount** converts **bulk → sealed packs**, **one-way, never reversed**, in **multiple fixed pack sizes per compound** (example: Mometasone 1 mg, ~5,000 bulk caps, prefilled into bottles of 60 / 90 / 180).
- Stock for a compound = **bulk count + a count of sealed units per pack size**. A dispense draws from **bulk OR a pack bucket**.
- _Modeling consequence:_ no reversal transactions; pack configs are per-compound and multiple; bulk and each packaged size are distinct stock buckets sharing one lot/BUD.

**Device / component inventory.** Track and **deduct device stock**: **capsule shells consumed at compound time, bottles consumed at precount time** (+ droppers etc.). PK models this via the `FORMDEV`/`LOGDEV` device BOM; **Salesforce has no device object** → net-new modeling in the synthesis.

**Automated work queues.** Today deciding what to compound/precount is a **manual hunt through Salesforce**. The app should **auto-generate the compounding queue AND the precount queue** (precounters are first-class users). Driving signal (par/reorder levels per compound + pack size, order backlog, or both) is **still to be defined** — likely with ops/lab.

**Weigh flow (shape confirmed; details to lab):** scan + validate chemical → show target weight → **±10% tolerance** → save actual weight (recorded per ingredient) → with an **override** path for special cases (liquids, etc.). No per-capsule/tablet weight checks (PK's were empty).

**Deferred to lab sessions:** QC (a major module — held for now), detailed weigh-flow rules + overrides, label content/templates.

**Dropped (confirmed dead/unused):** sterile compounding model; veterinary / `SPECIES`.

**Migration — DECIDED 2026-05-29:** migrate **everything** into the new app's **Postgres** (storage is cheap there; full lot + 288K-row chemical-genealogy history powers reporting, **recall traceability**, and demand forecasting). But **sync only the active/operational subset to Salesforce** — current on-hand + finalized lots. Rationale: **SF data storage is limited/expensive** (the 2024 ADF attempt hit the sandbox storage limit loading PK), and live inventory views must show current stock, not ~30K expired/depleted lots. Distinguish active vs archived via a status flag → reporting sees all, operations sees current.

**Work-queue automation signal — DEFERRED to a lab/ops session.** Model the data to support par/reorder levels, order/demand-driven, or a hybrid; pick the algorithm later.
