# 1. Architecture Overview

## 1.1 What this package does

`@medicus.ai/medicus-report-pdf-generator` (`package.json:2`) converts JSON health-report data into PDF documents for several distinct products:

- **Core "Medicus" lab report** (biomarkers/panels/insights) — module `lib/pdf_generator.min.js`, called via `generateMedicusPDF`
- **Wellbeing report** (questionnaire scores, PHQ‑9/GAD‑7 style) + optional merged lab "SmartReport" — module `lib/wellbeing_report_generator.js`, called via `generateNascoPDF` and `generateFullPdf`
- **Corporate/aggregate analytics report** (population-level, for employers) — module `lib/corporate_report_generator.js`, called via `generateCorporateReportPDF`
- **SanusX consumer wellbeing report** ("Health Hero" avatar report) — module `lib/sanusx_report_generator.js`, called via `generateSanuxPDF`

All four are independent implementations. They do **not** share a common rendering module — each has its own copy of the "read HTML fragment from disk → string-replace `{{tokens}}` → load into jsdom → manipulate with jQuery → serialize → hand to Puppeteer" pattern. See [03-templating-and-rendering.md](03-templating-and-rendering.md).

## 1.2 Directory map

```
index.js                    Public API — the only file a host app requires
run.js                       Optional child-process wrapper around generateMedicusPDF
lib/
  pdf_generator.js            Core report renderer — SOURCE, but NOT what actually runs (see 1.4)
  pdf_generator.min.js        Core report renderer — the file index.js actually requires
  wellbeing_report_generator.js  Wellbeing + SmartReport renderer, plus PDF conversion for it
  big_integral_questionnaire.js  Renders one static "Big Integral Questionnaire" table (Maison Sante only)
  corporate_report_generator.js  Corporate/analytics report renderer
  sanusx_report_generator.js     SanusX report renderer
  template.js                  Shared rendering helpers used ONLY by wellbeing_report_generator.js
                                (biomarker/panel/insight HTML builders, V3-suffixed functions)
  sendEmail.js                 Nodemailer wrapper, used by the wellbeing/corporate/sanusx flows
app/
  i18n.config.js               i18n config for the core "Medicus" report (locales/)
  i18n_wellbeing.config.js     i18n config for wellbeing/corporate/sanusx (locales/wellbeing/)
  services/localeService.js    Thin wrapper exposing .t(key) / .setLocale(lang)
config/                       One JSON per brand (whitelabelling) + default.json
templates/                    HTML fragments — shared (templates/blocks) + per-brand folders
locales/                      Translation JSON — main + locales/wellbeing subfolder
assets/                       CSS/JS/fonts/images served into the rendered HTML, some per-brand
output/                       Default write location for generated PDFs/HTML/logs (not cleaned up)
testing-reports/              Example payloads used by test.js / preview scripts
tests/                        Ad hoc test scripts (not a real test runner/framework)
```

## 1.3 Entry points (`index.js`)

`index.js` exports a flat object of async functions. There is no class, no server, no routing — just functions a host app calls directly.

- **`generateHTMLStaging`** — pipeline: Core. Build HTML only (re-exported straight from `pdf_generator.min.js`).
- **`generateMedicusPDF(base64Object, isDebugging, isDownloadable, onlyHTML)`** — pipeline: Core. Full core lab report → PDF.
- **`generateNascoPDF(data, isDebugging, isDownloadable, shouldSendEmail)`** — pipeline: Wellbeing. Plain wellbeing report, optionally emailed.
- **`generateFullPdf(data, isDebugging, isDownloadable, shouldSendEmail, selectedLabs, showHeaderLogo)`** — pipeline: Wellbeing (extended). Branded wellbeing report + merged SmartReport + **encryption**.
- **`generateSanuxPDF(data, isDebugging, isDownloadable, shouldSendEmail)`** — pipeline: SanusX. SanusX consumer report, optionally emailed.
- **`generateCorporateReportPDF(json, isDebugging, isDownloadable)`** — pipeline: Corporate. Corporate/analytics report.
- **`sendEmail(json)`** — fire a generic notification email (no PDF pipeline).
- **`generateQrCode(json)`** — pipeline: Core (`generatePatientQR`). Rasterizes a caller-supplied HTML string (e.g. an inline SVG QR code) to PDF — despite the name, does not generate a QR code itself.

**Payload convention:** every PDF-producing function expects the "real" data wrapped one level up: the caller passes a JSON *string* whose parsed object has a `data` field containing **base64-encoded JSON** (the actual report content), plus sibling fields like `client`, `language`, `host`/`port`/`authUser`/`authPass`/`sendFromEmail`/`secure` (SMTP config, only used if emailing) and, for `generateFullPdf`, `pdfPassword`. `generateMedicusPDF` is the odd one out — it takes the base64 payload directly as its first argument rather than nested inside a JSON envelope.

```js
// Illustrative shape of the outer envelope for generateNascoPDF / generateFullPdf / generateSanuxPDF
{
  "data": "<base64-encoded JSON string — the actual report payload>",
  "client": "Mediclinic",      // whitelabel selector, see doc 2
  "language": "en",            // or "ar", "ar-AE", "de", ...
  "host": "smtp.example.com",  // only read if shouldSendEmail is true
  "port": 587,
  "authUser": "...",
  "authPass": "...",
  "sendFromEmail": "noreply@...",
  "secure": true,               // NOTE: currently ignored, see doc 7
  "pdfPassword": "..."          // generateFullPdf only — PDF is encrypted with this
}
```

Every function decodes with `Buffer.from(base64Object, 'base64').toString('utf8')` then `JSON.parse` — this is **encoding, not encryption**; the payload (including any SMTP credentials riding alongside it) is trivially recoverable by anyone who can see the request. See [07-email-notifications.md](07-email-notifications.md) for the security implication.

`run.js` is a thin optional wrapper that runs `generateMedicusPDF` in a forked child process (`process.on('message', ...)` / `process.send(...)`), for host apps that want report generation isolated from their main event loop. It is not required — most callers use `index.js` directly.

## 1.4 Critical fact: the core report renderer that actually runs is `pdf_generator.min.js`, not `pdf_generator.js`

`index.js:1` requires `./lib/pdf_generator.min` — **not** `./lib/pdf_generator`. These are commonly assumed to be a source-file/build-artifact pair (the kind that a bundler regenerates), but they are not:

- There is **no build script** anywhere in the repo that produces `pdf_generator.min.js` from `pdf_generator.js` (no `webpack.config.js` exists, despite `webpack` being a devDependency).
- Git history shows the two files are edited **completely independently**: `lib/pdf_generator.js` was last touched by commit `f31298e` ("fix range") on **2021-03-25**. `lib/pdf_generator.min.js` was last touched by commit `8bb73da` ("Reformat Code") on **2025-07-30** — over four years later — and that commit only reformatted/beautified the minified file (it went from a single packed line to ~525 readable lines); it did not re-minify from `pdf_generator.js`, which was untouched.
- Today the two files happen to be logically equivalent in most places (verified by spot comparison), but this is coincidental, not enforced by any tooling. **A change made only to `lib/pdf_generator.js` will never ship** — `index.js` never loads it.

**Practical rule: when working on the core "Medicus" lab-report pipeline, edit `lib/pdf_generator.min.js`.** Treat `lib/pdf_generator.js` as a legacy reference copy at best, and flag it for removal or for wiring up an actual build step. All other generators (`wellbeing_report_generator.js`, `corporate_report_generator.js`, `sanusx_report_generator.js`) do not have this problem — each is required directly from its single, non-minified source file.

## 1.5 Data flow, at a glance

```
Host app                     This package                              Output
─────────                    ────────────                              ──────
JSON payload  ──base64──▶   index.js export
                                │
                                ▼
                         decode base64 → JSON.parse
                                │
                                ▼
                     generateHTML*(data, isDebugging, client, language)
                       - loads config/{client}.json (branding)         ┐
                       - loads templates/{brand or shared}/*.html       │ see doc 2 & 3
                       - loads locales/{...}/{language}.json            │
                       - fs.readFileSync HTML fragments,                │
                         string .replace("{{token}}", value)            │
                       - loads shell HTML into jsdom + jQuery            │
                       - DOM-injects rendered fragments                 ┘
                                │
                                ▼  (HTML string, plus metadata: header/footer HTML, output file path)
                     generatePDF*(html) — Puppeteer
                       - page.setContent / page.goto(file://...)
                       - page.pdf({ headerTemplate, footerTemplate, margin, ... })
                       - (core report only) two page.pdf() calls merged via hummus,
                         to give page 1 a different header than the rest
                                │
                                ▼  PDF Buffer
                (generateFullPdf only) node-qpdf2 encrypts the buffer with reportData.pdfPassword
                                │
                                ▼
                 return Buffer (isDownloadable) | base64 string | sendNascoEmail(...) result
```

## 1.6 Key third-party dependencies and why they're there

- **`puppeteer`** (pinned `^1.15.0`, very old) — headless Chrome → PDF rendering, in all four generators. Each generator launches its own Puppeteer instance independently — no shared "renderPdf" helper.
- **`hummus`** (`1.0.111`) — merging two separately-rendered PDF buffers (core report only, to give page 1 a distinct header). Effectively unmaintained upstream.
- **`node-qpdf2`** — encrypting the final PDF with a password (`generateFullPdf` only). Dynamically `import()`-ed (ESM) inside a CommonJS file.
- **`jsdom` + `jquery`** — server-side DOM construction and manipulation before handing HTML to Puppeteer. Used by every generator except the newer `template.js` V3 functions, which build HTML via plain string concatenation instead.
- **`i18n`** (`^0.8.3`, resolves to `0.8.6`) — translation string lookup. Two independently configured instances — see doc 6.
- **`nodemailer`** — sending report emails with the PDF attached. Transport config comes entirely from the caller's payload, not env vars.
- **`chart.js`** (npm) — declared but **not actually used server-side** — dead import in `pdf_generator.js`. The real chart rendering uses a separately bundled copy, `assets/charts.min.js`, executed **inside the headless Chrome page** before the PDF snapshot is taken. Two independent copies of Chart.js that can drift out of sync.
- **`qr-image` / `qrcode`** (npm) — also effectively unused server-side for the in-report PIN/QR box — that box is rendered client-side via the bundled `assets/qrcode.min.js`. `generatePatientQR` in `index.js`/`pdf_generator.min.js` is a generic "rasterize this HTML string" utility, not a QR generator itself.
- **`canvas`, `get-canvas-context`, `self-adapt-fontsize`, `textfit`, `big-text.js`** — legacy font-fitting/canvas helpers. Present in `package.json` but not central to the current rendering path — treat as legacy.

## 1.7 File/temp-file handling — an evolving pattern worth knowing

Older code paths (`generateMedicusPDF`, `generateNascoPDF`, `generateSanuxPDF`, the internals of `pdf_generator.js`/`.min.js`) write intermediate HTML and the final PDF to **fixed, shared filenames** inside the package's own `output/` directory (e.g. `output/sample.pdf`, `output/nasco-sample.pdf`, `output/LOGS.txt`) and never delete them. Two concurrent requests through the same process can clobber each other's files.

`generateFullPdf` (`index.js:194-314`) is the one flow that was hardened against this: it generates a `crypto.randomUUID()` per call and builds all temp paths (`wb-{callId}-in.pdf`, `wb-{callId}-enc.pdf`, `wb-{callId}-mail.pdf`) inside `os.tmpdir()`, then cleans them up in a `finally` block via a best-effort `safeUnlink`. **If you add a new PDF-producing flow, follow this newer pattern, not the older fixed-filename one.**

## 1.8 Appendix: other root-level files (accounted for, not part of the runtime pipeline)

A handful of files at the repo root are not required by `index.js` and are not wired into any of the four report pipelines. Listed here so nothing is silently unexplained:

- **`test.js`**, **`tests/test.js`**, **`tests/test-qr-code.js`**, **`preview-big-integral.js`** — ad hoc developer scripts, not a test framework. See [`HANDOVER.md` §9](../HANDOVER.md#9-runningtesting-this-locally) for how to use them.
- **`downloadfile.js`** — a one-off helper that fetches a pinned Chromium build for Puppeteer on Windows. Not required if Puppeteer's own Chromium download already succeeded during `npm install`.
- **`data converter.js`** (53 lines) — a standalone script that parses an ad hoc "Antimicrobial Agent / Sensitivity" text format into structured rows. Not `require`d by anything in `lib/`, `index.js`, or `run.js` — an orphaned, one-off data-conversion utility from some prior integration, not part of the active rendering pipeline.
- **`base64/cairo-font.js`** — a 2-line file containing a single, large base64-encoded font data URI. Not `require`d anywhere — orphaned/unused; the fonts actually used at render time are the files under `assets/fonts/`.
- **`example.txt`** — a large (1000+ line) saved fragment of previously-rendered report HTML, kept as a manual reference/comparison sample, not consumed by any code.

None of these affect report output; they're safe to leave alone or clean up opportunistically.
