# 3. Templating & Rendering Pipeline

## 3.1 There is no template engine

Despite the `templates/` folder full of `.html` files, this project does **not** use Handlebars, EJS, Mustache, or any templating library. Every generator (core, wellbeing, corporate, sanusx) hand-rolls the same two techniques:

**(a) Plain string `{{token}}` replacement**, for fragments Puppeteer needs as raw strings (Chrome's `headerTemplate`/`footerTemplate` print options only accept static HTML strings, so these can't be manipulated after the fact):
```js
headerTemplate = headerTemplate
    .replace("{{logo}}", img)
    .replace("{{patient_name}}", patientName)
    .replace("{{report_ref}}", fitNumber(data.reportNumber))
    // ...
```

Block files contain literal `{{...}}` tokens, e.g. `templates/blocks/header.html` (`{{patient_name}}`, `{{report_ref}}`, `{{division_name}}`, `{{report_date}}`), `templates/blocks/doctor-note.html` (`{{note-id}}`), `templates/blocks/panel-details.html` (`{{panel-id}}`, `{{panel-title}}`).

Note: `lib/sendEmail.js` uses a **different** placeholder convention for its own hand-built HTML — single-brace `{token}` (e.g. `{logo}`, `{header_text}`) — not `{{double-brace}}`. If you're hunting for a placeholder and it's an email string, look for single braces.

**(b) jQuery-over-jsdom DOM manipulation**, for the main body of each report. The pattern:

1. Load a page-skeleton HTML file (e.g. `templates/ltr_no_pages.html`) with `fs.readFileSync`.
2. `let dom = new JSDOM(html); $ = require('jquery')(dom.window);` — construct a real DOM in Node and bind jQuery to it.
3. Load each block fragment, do its `{{token}}` replacements, then inject it into the DOM by selector: `$('#content').append(newPanelHtml)`, `$("#reference").text(data.reportNumber)`, `$('.patient-table').html(...)`, `$(selector).remove()`, etc.
4. Serialize the whole document back to a string with `dom.serialize()` and write it to a temp/output HTML file.
5. Puppeteer loads that file via a `file://` URL and rasterizes it to PDF (see 3.3).

This exact five-step pattern is duplicated **independently** in `lib/pdf_generator.min.js`, `lib/wellbeing_report_generator.js`, `lib/corporate_report_generator.js`, and `lib/sanusx_report_generator.js` — there is no shared "render engine" module between them, only convention.

`lib/template.js` is a partial exception: it contains a newer, parallel set of rendering functions (suffixed `V3` — `renderBiomarkerV3`, `renderPanelV3`, `renderNoteV3`, etc.) that build HTML purely via JS template-literal string concatenation, with no `fs.readFileSync` of block `.html` files and no jsdom/jQuery for those specific pieces. `template.js` is used **only** by `wellbeing_report_generator.js` (for rendering the merged "SmartReport" biomarker section) — it is not shared with the core report's own (older, still jsdom/jQuery-based) biomarker rendering in `pdf_generator.min.js`, even though the two do very similar things. This suggests the codebase is mid-migration from "external HTML blocks + jQuery/jsdom" toward "HTML generated inline in JS," but the migration has only reached the wellbeing pipeline so far.

## 3.2 Top-level page templates — live vs. dead

Only two root-level templates are actually loaded at runtime by the core report: **`templates/ltr_no_pages.html`** and **`templates/rtl_no_pages.html`**, chosen by a single `isRtl` boolean. Both are minimal skeletons — a `<head>` pulling in `charts.min.js`/`qrcode.min.js`, empty containers (`#patient-container`, `#more-link`, `#pin`, `<canvas id="canvas">`), and one empty `<div id="content">` that everything else gets appended into. The RTL variant additionally sets `dir="rtl" lang="ar"` and loads `arabic.min.css`/`translation.min.js`. Neither contains manual page-break markup — pagination is left entirely to the print engine, which is why they're named `*_no_pages` (as opposed to the older, page-per-`<div class="paper">` style below).

`templates/imc/first-header-template.html` is swapped in only for one specific lab type (`labType === 9`, a wide custom header).

The remaining root-level files — `templates/base.html`, `templates/template.html`, `templates/ltr.html`, `templates/no_pages.html`, `templates/empty.html`, `templates/first_page_head.html` — are **not referenced by any code path** (confirmed by grep across all of `lib/`). They are static mockups/prototypes from earlier iterations of the pagination model (manual `<div class="paper">` per-page blocks with repeated headers/footers) or trivial smoke-test fixtures. Don't assume editing them affects output — they're dead weight, safe candidates for removal but currently harmless if left alone.

## 3.3 PDF conversion (Puppeteer)

Each generator launches its own Puppeteer instance and calls `page.pdf(...)` with its own options — there is no shared "renderPdf" helper across the four pipelines.

**Core report** (`pdf_generator.min.js`) calls `page.pdf()` **twice**, because Chrome's print header/footer can't vary within a single call:
- Once for `pageRanges: '1'` with the "first page" header template and different top margin (page 1 needs the patient-info/QR header).
- Once for `pageRanges: '2-'` with the regular header template.

The two resulting PDF buffers are then merged into one file using `hummus` (`createWriterToModify(...).appendPDFPagesFromPDF(...)`).

**Wellbeing / Corporate / SanusX** each call `page.pdf()` once, with their own `headerTemplate`/`footerTemplate`/margins tuned per report type. The SanusX generator additionally passes `pageRanges: '1'`, meaning **any content overflowing page 1 is silently dropped** — a fragile single-page assumption worth remembering if SanusX report content ever grows (e.g. longer translated strings pushing content past one page).

The corporate report generator sets page content **twice** — once via `page.setContent(...)` and again by navigating to the serialized temp HTML file via `page.goto("file://...")` — and separately injects jQuery from a public CDN (`cdn.jsdelivr.net`) mid-render via `page.addScriptTag`. This means corporate-report rendering has a live external-network dependency at PDF-generation time, unlike the other three pipelines which bundle their own JS/CSS as local `assets/` files.

## 3.4 Charts

There is no server-side chart *image* generation. The npm `chart.js` package is `require`d at the top of `pdf_generator.js`/`.min.js` but never actually called — a dead import. Instead:

1. Server-side, biomarker history is serialized into a plain JS array (`{id, dataset, dataColor, dataLabel}` per biomarker) and injected into a `<script>` tag as `var chartsData = [...]`.
2. The page template loads a **separately bundled, minified browser copy of Chart.js 2.x** — `assets/charts.min.js` (not the npm package).
3. `assets/js.js`'s `drowBioCharts()` function runs **inside the headless Chrome page itself**, before `page.pdf()` is called, and instantiates real `Chart(ctx, {type:'line', ...})` objects against `<canvas>` elements.

So charts are rendered client-side, in-browser, immediately before the PDF snapshot — a legitimate technique, but it means the npm `chart.js` dependency and `assets/charts.min.js` are two independent copies of the same library that can silently drift out of version sync.

The corporate report's bar charts don't use a charting library at all: `renderBarChart()` emits plain `<div class="bar-chart" data-value=".." data-total="..">` elements, and `assets/corporate_report/js/js.js` computes their pixel width client-side via `$(this).css("width", "calc(" + percent + "% + 60px)")`. SanusX's score "gauges" are similarly library-free — static PNG segment-circle images overlaid with a CSS-rotated needle (`transform: rotateZ(...)`), not a canvas or SVG chart.

## 3.5 QR codes — two unrelated mechanisms

Don't confuse these:

1. **The in-report patient PIN/QR box** (page 1 "more info" panel): the server builds a plain concatenated string (`'v_' + patientName + '_!_' + pin`, misleadingly assembled near variables named `base64text`/`base64name`/`base64pin` that are computed but never actually used), injects it as a script variable, and `assets/js.js`'s `drawQRCode()` calls `QRCode.toCanvas(...)` **client-side, in-browser**, using the bundled `assets/qrcode.min.js`, targeting a `<canvas id="canvas">` in the page template.
2. **`generatePatientQR(data)`** (exported from `index.js` as `generateQrCode`) — despite its name, this function does **not generate a QR code**. It takes an **already-fully-rendered HTML string** from the caller (e.g. containing an inline `<svg>` QR code the caller produced elsewhere), wraps it in jsdom, writes it to a temp file, and rasterizes it to a `letter`-format PDF with no margins/headers via Puppeteer. It is a generic "HTML string → PDF" utility that happens to be used for QR codes by convention, not a QR-code generator itself. See `tests/test-qr-code.js` for the expected caller-side pattern (pre-render the QR as inline SVG, then pass the whole HTML string in).
