# How one audit report is built, and how ISO 14001 fits in (example only)

This folder is a SAMPLE written before the ISO 14001 work was done. **Status 2026-11-03: implemented** — see the
note at the end. Nothing in this folder is wired into the app.
It shows, with one concrete story, how a report flows today for ISO 9001:2015
and what we would ADD (never change) so the same flow works for ISO 14001:2026
and for every other standard that has its own format.

Written 2026-11-02. Based on the real code in `src/audit-report-builder/`.

---

## 1. The story: one company, two audits

Company: **Green Palm Trading LLC** (company_id 120)
Audit row: **audit_schedule_rows.id = 900**, Stage 2, booked for TWO standards:

| standards.id | name            |
|--------------|-----------------|
| 4            | ISO 9001:2015   |
| 7            | ISO 14001:2026  |  <- example id, check your standards table

The auditor presses "Create report" twice, once per standard:

```
POST /audit-reports  { "audit_schedule_row_id": 900, "standard_id": 4 }   -> report A (9001)
POST /audit-reports  { "audit_schedule_row_id": 900, "standard_id": 7 }   -> report B (14001)
```

Both requests go through the SAME code. The difference comes only from
**what the database holds for standard 4 vs standard 7**. That is the key idea.

---

## 2. What happens inside, step by step

```
POST /audit-reports { row 900, standard 7 }
        |
        v
[1] report-builder.service.ts  create()
        |-- loads company, certificate, team, attendance from CRM  (same for every standard)
        |
        |-- [2] resolveTemplate(standard 7, 'stage2')
        |       looks in table  report_templates  for the most specific ACTIVE row:
        |          standard_id = 7 AND stage_type = 'stage2'   (best)
        |          standard_id = 7 AND stage_type = NULL
        |          standard_id = NULL AND stage_type = 'stage2'
        |          standard_id = NULL AND stage_type = NULL     (generic, exists today)
        |       -> saves the winner's id into  audit_reports.template_id
        |
        |-- [3] libraryFor(standard 7, 'stage2')
        |       loads clause rows from table  clause_library
        |          WHERE standard_id = 7 AND scope = 'full'
        |       -> one audit_report_clauses row per clause
        |       (if standard 7 has NO rows it borrows another standard's list = wrong clauses!)
        |
        |-- [4] defaultSectionBody(key, stage, "ISO 14001:2026", isIso9001=false)
        |       fills objective / criteria / audit brief / effectiveness text
        |       today: only 9001 has rich text, others get one generic sentence
        v
   report B saved (status draft)

GET /audit-reports/B/pdf
        |
        v
[5] report-render.service.ts  templateFile(report)
        reads report.template_id -> report_templates.hbs_file
        if the file is NOT the old 'form-04-2a.hbs'  -> use it
        else fallback: stage1 ? form-04-1-stage1.hbs : form-04-2-stage2.hbs
        |
        v
[6] Handlebars compiles  templates/<hbs_file>  + view model  -> HTML
[7] report-pdf.service.ts  -> Puppeteer -> PDF (DRAFT watermark until approved)
```

---

## 3. Report A (9001) today vs Report B (14001) today

| Step | Report A, standard 4 (9001)                         | Report B, standard 7 (14001) TODAY                         |
|------|-----------------------------------------------------|------------------------------------------------------------|
| [2]  | generic row -> template_id = 1 (form-04-2a)          | same generic row -> template_id = 1                        |
| [3]  | 64 clauses of 9001 from clause_library               | standard 7 has no rows -> BORROWS the 9001 clauses (wrong) |
| [4]  | full QMS wording (regex /9001/ matches)              | one generic sentence ("...against ISO 14001:2026")         |
| [5]  | hbs_file is form-04-2a -> fallback form-04-2-stage2  | same fallback -> 9001 layout, QMS text in the template     |

So today a 14001 report prints a 9001-shaped form with 9001 clauses. That is the problem.

---

## 4. Report B (14001) AFTER the additions (nothing of 9001 changed)

| Step | What we ADD                                                                  | Result for report B                                  |
|------|-------------------------------------------------------------------------------|------------------------------------------------------|
| [2]  | 4 new rows in `report_templates` for standard 7 (see `report_templates.example.sql`) | template_id = the 14001 row for its stage       |
| [3]  | 14001 clause rows into `clause_library` (see `clause-library-import.example.json`)  | its own EMS clauses, no borrowing                |
| [4]  | new file `report.constants.iso14001.ts` + a lookup that is tried BEFORE the old function | EMS objective / criteria / effectiveness text |
| [5]  | new files `templates/form-14001-stage1.hbs`, `templates/form-14001-stage2.hbs` (and surveillance / recert if the sample differs) | 14001 layout |

Report A is not affected because:
- its template_id was resolved from rows for standard 4 / generic, and those rows are untouched;
- its clauses come from standard 4 rows, untouched;
- its default text still goes through the existing `isIso9001` branch;
- `form-04-1-stage1.hbs` and `form-04-2-stage2.hbs` are never opened.

---

## 5. Adding the NEXT standard (ISO 45001, 27001, ...)

Repeat the same four additions with its own standards.id:

1. new `.hbs` files        `templates/form-<std>-stage1.hbs`, `templates/form-<std>-stage2.hbs`
2. new DB rows             `report_templates` (standard_id = that id, one per stage_type, hbs_file = the files above)
3. clause list             `POST /audit-reports/clause-library/import` with that standard_id, or a new seed file
4. default wording         `report.constants.<std>.ts`

No existing file is edited except the small registry that lists the per-standard constants files
(one new line per standard). A standard with none of these still prints the generic form exactly as today.

---

## 6. One thing that is NOT template-driven

`services/report-docx.service.ts` (Word export) is hand-coded to the 9001 form, it does not read `.hbs`.
If 14001 Word export must look like the 14001 sample, that needs a NEW class
`report-docx-iso14001.service.ts` chosen by template_id. PDF and HTML are template-driven and need nothing extra.

---

## Files in this folder

| File                                   | What it shows                                                    |
|----------------------------------------|------------------------------------------------------------------|
| README.md                              | this walkthrough                                                 |
| report_templates.example.sql           | the 4 rows that route standard 7 to 14001 templates (NOT run)    |
| clause-library-import.example.json     | body for POST /audit-reports/clause-library/import (short extract)|
| create-report.example.http             | the two create calls and what each returns                       |

---

## Implemented on 2026-11-03 (what actually changed vs this plan)

- ISO 14001:2026 is `standards.id = 33` (not 7 as assumed above).
- Clause rows: `seed/iso14001.seed.ts` + `migrations/005_iso14001_templates_and_clauses.sql` (applied). Seed endpoint `POST /audit-reports/clause-library/seed/iso14001 { "standard_id": 33 }`.
- `report_templates`: 4 rows for standard 33, Form 04-2b / Issue 01 / Rev 00 / 01-08-2026.
- No separate `form-14001-*.hbs` files and no separate Word class: the 14001 form has the same layout as the 9001 form, so the
  shared templates and `report-docx.service.ts` now read the standard-specific wording from `report.standards.ts` (`profileFor`).
  The template rows point at the shared `form-04-1-stage1.hbs` / `form-04-2-stage2.hbs`.
