# 4. Report Pipelines

Four independent report types. Each has its own generator module, its own template folder(s), and its own quirks.

---

## 4.A Core "Medicus" Lab Report

**Module:** `lib/pdf_generator.min.js` (the file that actually runs — see [doc 1.4](01-architecture-overview.md#14-critical-fact-the-core-report-renderer-that-actually-runs-is-pdf_generatorminjs-not-pdf_generatorjs)). `lib/pdf_generator.js` is a stale reference copy.

**Purpose:** a clinical lab report — biomarkers grouped into panels, each with reference ranges, history charts, doctor notes, and AI/clinical "insights." No questionnaire concept at all.

**Entry points:** `generateHTMLStaging(data, isDebugging)` → HTML; `generatePDF(html)` → PDF buffer via Puppeteer; both wired together by `index.js`'s `generateMedicusPDF`. `generatePatientQR(data)` is a separate, generically-named "HTML string → PDF" utility (see [doc 3.5](03-templating-and-rendering.md#35-qr-codes--two-unrelated-mechanisms)).

**Data shape (top-level fields observed in the code and `assets/data/data2.json`):**
```
language, labType (9 = "IMC lab", triggers customHeader), labLogo, labName, labAddress,
labPhoneNumber, patientName, patientPin, showPIN, timeZoneOffset, reportDate (unix seconds),
reportNumber, reportDivision, reportTitle, copyright, moreLink, showCompactView, showDetailsView,
profileItems: [{title, value}],
panels: [{ panelId, name, panelDetails, notes,
           biomarkers: [{ id, name, fullName, unit, value, formattedValue, color, isNormal,
                          showInCompactView, showInDetailsView, ranges[], history[],
                          childrenBiomarkers[], doctorNotes[], relatedInsights[],
                          status, insightsCount }],
           insights: [...] }],
summary: [{ ...stat insights, stackedInsight, relatedBiomarkers, isClinicalReading }],
doctorNote: [{ id, createdByName, content }],
reportSummary, reportProperties,
signatures: [{ name, signatureURL }], labStamp, approvalDate
```

**Templates:** always `templates/blocks/*.html` (shared, non-brand) plus `templates/imc/first-header-template.html` for `labType === 9`. No per-brand template folder is ever consulted in this pipeline — whitelabelling here happens entirely through data values the host app bakes into the payload (logo URL, colors would need to be pre-applied since there's no `config/` lookup at all in this file).

**Notable quirks:**
- Module-level mutable state (`$`, `debug`, `OUT_FILE`, `Pdf_file`, `shouldRenderCustomHeader`) is reassigned per call with no isolation — concurrent calls in the same process can interfere with each other (contrast with `generateFullPdf`'s UUID-based temp files).
- Every render leaves a timestamped HTML file behind in the package's own `output/` directory; nothing is cleaned up.
- `labType === 9` ("IMC") special-casing runs throughout what's otherwise framed as the generic/core pipeline.
- Pinned to very old `puppeteer@^1.15.0` and `hummus@1.0.111`.

---

## 4.B Wellbeing Report (+ optional merged "SmartReport")

**Module:** `lib/wellbeing_report_generator.js` (2000+ lines), using shared rendering helpers from `lib/template.js` for the SmartReport section, and `lib/big_integral_questionnaire.js` for one Maison-Sante-only static table.

**Purpose:** a patient-facing wellbeing/lifestyle report — physical/psychological/lifestyle scores, PHQ‑9/GAD‑7-style questionnaire answers (when a doctor reviewed it), and tips. Optionally merged with a full lab biomarker report ("SmartReport").

There are **two distinct entry functions** with materially different behavior:

**`generateHTMLWellbeingReport`** (the plain flow):
- Signature: `(data, isDebugging, clientName, language)`
- Template folder: always `templates/wellbeing/` (generic), regardless of client
- Data source: top-level `data.calculation` / `data.partsScore` / `data.insights.tips`
- Called from: `generateNascoPDF` (`index.js`)
- PDF conversion: `generatePDFWellbeingReport(html)`

**`generateHTMLWellbeingReportWithSmartReport`** (the branded/extended flow):
- Signature: `(data, isDebugging, clientName, language, selectedLabs=[], showHeaderLogo=true, hideWellbeingUI=false)`
- Template folder: `templates/{Mediclinic, Pha, or maisonsante}/` — client-specific, throws if the client has no dedicated folder
- Data source: `data.wellbeing` (scores), `data.profileInfo` (metadata), `data.Profile` + `data.IsDoctor` (Q&A), `data.SR` (merged SmartReport lab data)
- Called from: `generateFullPdf` (`index.js`) — the only flow that also encrypts the output
- PDF conversion: `generatePDFReport(data, hideWellbeingUI)` — adds running header/footer, `data.footer` support

**Brand/theme support:** `loadColorTheme` explicitly supports **mediclinic, pha, nasco, maisonsante**. Any other client string (`bionext`, `diagnostikare`, `sanitas`, `sanusx`, `Najeeb.ai`) silently falls back to the mediclinic color theme.

**SmartReport merge (`data.SR`):** a full lab/biomarker report payload (same general shape as the corporate report's item list: `items[]` of `biomarker`, `panel`, `insight`, `biomarkerNote`, `allergyItems`, etc.), parsed as string or object, run through `generateReportHtml()` (reusing `renderReportItems`/`renderSummary`/`renderHTMLTemplate` from `lib/template.js`), and injected into `#smart-report`. If `hideWellbeingUI` is true or `data.wellbeing` is empty, the quiz-UI sections are removed from the DOM so only the lab report shows.

**`selectedLabs` / `showHeaderLogo`:** used to decide which lab logos appear in the header, normalized against `config.labs_logo`/`labs_logo_aliases` (Pha only — see [doc 2.1](02-configuration-and-whitelabelling.md#211-the-nested-corporate_report-object)) so different spellings of a lab name (`"lab corp"`, `"LabCorp"`) resolve to one config entry.

**Known bug:** `index.js` passes `showHeaderLogo || true` into the generator. Since `false || true === true`, **the header logo can never actually be suppressed**, contradicting both the parameter name and its JSDoc in `index.js`.

**Localization:** uses `app/i18n_wellbeing.config.js` / `locales/wellbeing/*.json` (not the main `locales/` set — see [doc 6](06-internationalization.md)).

**Encryption:** only `generateFullPdf` in `index.js` encrypts the final buffer, using `node-qpdf2` with `reportData.pdfPassword`. `wellbeing_report_generator.js` itself has no encryption logic — it only ever returns a plaintext PDF buffer.

**Other quirks worth knowing:**
- Per-client special-casing is scattered as inline conditionals rather than being config-driven: PHA-only CSS overrides, PHA-only bottom margin, hardcoded per-client addresses/phone numbers in the footer builder, tips category ordering swapped for `pha`, header-logo sizing branched by client name. Onboarding a new client for this flow means hunting through many such `if (client === ...)` branches, not editing one config file.
- `generateHeaderInfo()`'s output is computed but discarded — its target selector `.report-info` is commented out in all three brand `blocks/header.html` files. The live equivalent is `renderPatientTable()`, which injects into `.patient-table`.
- `templates/{wellbeing,Mediclinic,Pha,maisonsante}/wellbeing_template.html` files are not referenced by any `.js` file — orphaned, superseded by `ltr_no_pages.html`/`rtl_no_pages.html`.
- Shared mutable module state (`config`, the singleton `localeService`/`i18n`, a module-level `debug` flag) is reassigned per call; file paths were hardened against concurrent-request collisions (per an in-code comment referencing a real past bug) but config/locale/debug state was not similarly isolated — concurrent requests for different clients/languages can still race.

### 4.B.1 `lib/big_integral_questionnaire.js` — read this before assuming it's a real feature

This module is **not** a scoring engine and does **not** read real questionnaire answers from the input payload. It is a presentational renderer for one static demo table ("Key to the Big Integral Questionnaire" — a functional-medicine systems review, unrelated to the PHQ‑9/GAD‑7 wellbeing scores) built entirely from `getDummyData()`, which returns 11 hardcoded rows with `result: 0` for every row and a fixed fake patient (`"Final Test 3"`, age 37). The single export, `renderBigIntegralQuestionnaire({ clientConfig })`, merges `clientConfig.big_integral_questionnaire` colors/toggles (from `config/maisonsante.json`) and renders the table. It is invoked only when `client.toLowerCase() === 'maisonsante' && data.IsDoctor`, targeting `#big-integral-section` (present only in `templates/maisonsante/ltr_no_pages.html`). `preview-big-integral.js` is a standalone dev harness confirming this is a design/preview scaffold, not a wired, data-driven feature. **If a stakeholder asks "why doesn't the Big Integral Questionnaire reflect the patient's real answers" — that's not a bug, it was never wired to real data.**

---

## 4.C Corporate Report

**Module:** `lib/corporate_report_generator.js`.

**Purpose:** an aggregate/analytics PDF summarizing results across a *population* of participants (e.g. an employer's workforce), not a single patient — cover page, "Participant Analytics" section (per-metric bar charts), then one page per health "element" with a summary, bar charts, textual "relations," a gender-breakdown table, recommendations, and sources. Built for HR/benefits stakeholders.

**Entry points:** `generateHTMLCorporateReport(data, isDebugging)` → HTML; `generatePDFCorporateReport(html)` → PDF; wired together by `index.js`'s `generateCorporateReportPDF(json, isDebugging, isDownloadable)`.

**Data shape:** `data.clientName` (branding selector), `data.language`, `data.participantAnalytics: [...]` (drives the Participant Analytics bar charts — the `participant_analytics.html` template itself is just an empty `<div class="container">`; all its content is generated in JS), `data.elements: [{ summary, chartValues, secondChartValues, relations, dataByGender, recommends, sources }]`.

**Templates:** always `templates/corporate_report/*` (`cover_page.html`, `participant_analytics.html`, `page.html`, `footer.html`), regardless of client — only colors/logo vary by `config/{client}.corporate_report`.

**RTL:** this generator correctly branches to `templates/corporate_report/rtl_no_pages.html` vs `ltr_no_pages.html` based on `data.language` containing `"ar"` — unlike SanusX (4.D), RTL is not broken here.

**Notable quirks:**
- Sets page content twice (`page.setContent` then `page.goto(file://...)`) and injects jQuery from a public CDN mid-render (`page.addScriptTag({ url: 'https://cdn.jsdelivr.net/...' })`) — a live external-network dependency at render time.
- Contains a copy-pasted, entirely dead `combinePDFBuffers` function (and the `hummus`/`memory-streams` requires that exist only to support it) — never actually invoked in this file.
- Onboarding a new client's custom cover-page background image requires editing shared CSS by client-name string (`.cover-overlay-container.bionext { background-image: url(...) }` in `assets/corporate_report/css/report.css`) — bypasses the otherwise config-JSON-driven theming model.
- Corporate-report translation strings live inside the `locales/wellbeing/` bucket (via `app/i18n_wellbeing.config.js`), not a dedicated namespace — a naming trap when hunting for a string to translate.

---

## 4.D SanusX Report

**Module:** `lib/sanusx_report_generator.js`.

**Purpose:** a branded, single-consumer wellbeing product ("Your Health Hero") — an individual is scored on physical/psychological axes and assigned an avatar archetype (monkey/tiger/owl) with a "superpower," a strength/weakness, three predicted-vs-actual insight cards, and three tips cards. The SanusX-brand analogue of the wellbeing report.

**Entry points:** `generateHTMLSanusXReport(data, isDebugging, clientName, language)` → HTML; `generateSanusXReport(html)` → PDF; wired together by `index.js`'s `generateSanuxPDF`.

**Data shape:** `data.highestAvatarScorePartId` (1/2/3, maps to monkey/tiger/owl), score fields feeding a rounded physical/psychological score bucketed into low/med/high (drives a static PNG "speedometer" image + CSS-rotated needle, not a real chart), `data.PartScoresHighestValues.{body,mind,lifeStyle}[0].isPredictiveElement` (drives "predicted right/wrong" copy), `data.insights.tips` (3 items, split into lifestyle/body/mind cards).

**Templates:** always `templates/sanusx/*`, regardless of `clientName`.

**Notable quirks — more fragile than the other three pipelines:**
- **Ignores `clientName` for branding entirely.** Config is hardcoded to `require('../config/sanusx.json')`. The `client` parameter is only used to add a CSS class (`$(".score-main").addClass(client)`) that currently matches no rule in `assets/sanusx/css/sanusx_report.css` — a no-op.
- **RTL is broken/incomplete.** There's a comment (`/*check if the language is an RTL language*/`) with no logic after it — `templates/sanusx/ltr_no_pages.html` is loaded unconditionally regardless of `language`. `templates/sanusx/rtl_no_pages.html` exists on disk but references asset paths from the *wellbeing* report (`assets/wellbeing/css/nasco_report.css`) that don't exist under `assets/sanusx/` — it is dead, unreachable code, not a working alternative.
- **`page.pdf({ pageRanges: '1', ... })` hard-limits output to one page** — any overflow (e.g. from longer translated strings) is silently dropped rather than flowing to a second page.
- `templates/sanusx/blocks/tips.html` exists on disk but is never read by this generator (dead file) — the actual tips rendering is done by an in-file `renderInsightTips()` function building HTML via string concatenation.
- Reuses `sendNascoEmail` (named after a different client) for its email flow, same as the wellbeing and corporate pipelines — a shared helper with a client-specific name baked into shared infrastructure.
