# Health & Safety Module

**Folder:** `CRUD/hs/` · **Reached from:** navbar → Health & Safety · **Entry page:** `hs_dashboard.php`

The compliance side of the business: the **IMS document registry**, **risk assessments**, **COSHH**, **RIDDOR** incidents, **toolbox talks**, **accreditations** and the **legislation register** they all map back to.

This is the most sprawling module in the portal (68 files) and carries the most historical layering — there are several superseded copies of the same thing, documented below so you don't edit the wrong one.

> Documented to the format set by [sites.md](sites.md).

---

## How the pieces relate

```mermaid
flowchart TD
    D[H&S Dashboard<br/>hs_dashboard.php] --> DR[Document Registry<br/>document_registry.php]
    D --> RA[Risk Assessments<br/>risk_assessment/dashboard.php]
    D --> CO[COSHH<br/>coshh/coshh_management.php]
    D --> RI[RIDDOR<br/>riddor/riddor_management.php]
    D --> TB[Toolbox Talks<br/>toolbox_talks_dashboard.php]
    D --> AC[Accreditations<br/>accreditation_manager.php]

    LEG[Legislation Registry<br/>legislation_registry.php] -.mapped to.-> DR
    DR --> VER[Versions + branded cover<br/>lib/document_pdf.php]
    VER --> SIGN[Signing log<br/>document_signing_log.php]

    RA --> GRA[GRA — general]
    RA --> RAA[RA — specific]
    RA --> MS[MS — method statement]

    CO --> PROD[COSHH products<br/>coshh_product.php]
    PROD --> GHS[GHS hazard symbols<br/>ghs_symbols.php]
    PROD --> PPE[PPE symbols<br/>coshh_controls.php]

    AS[Assessments<br/>assessments_dashboard.php] --> FLOW[start → flow → view]
```

---

## Documents & legislation

![Document Registry](../screenshots/hs__document_registry.png)

The IMS document registry — policies, procedures and forms, with version control.

| Page | Purpose |
|---|---|
| `document_registry.php` | The registry (711 lines) |
| `document_add.php` / `document_edit.php` | Add / edit a document |
| `document_file.php` | Serve a stored document |
| `document_regenerate.php` | Rebuild the branded combined PDF |
| `document_mapper.php` | Map documents to legislation (`?id=`) |
| `document_coverage_matrix.php` | Coverage: which legislation is covered by which document |
| `document_signing_log.php` | Append-only signing/audit record (`?id=`) |
| `lib/document_pdf.php` | **Cover-page generator** — builds a branded MJH cover from document + version metadata and merges the uploaded body behind it (mPDF + FPDI) |

The cover generator also produces a **document-bound watermarked signature**: the stored signature image is never placed as-is, it's stamped with the document code and version so a copy lifted from one document is obviously wrong elsewhere. Signature files are served by `signature_file.php` and managed in `signature_manage.php`.

![Legislation Registry](../screenshots/hs__legislation.png)

`legislation_registry.php` holds the legislation; `legislation_mapper.php` links it to documents.

## Accreditations & assessments

![Accreditation Manager](../screenshots/hs__accreditation.png)

`accreditation_manager.php` tracks accreditations and their sections (`api_get_accreditation_sections.php` feeds the section list).

![Assessments](../screenshots/hs__assessments.png)

`assessments_dashboard.php` → `start_assessment.php` → `assessment_flow.php` → `view_assessment.php`, with `manage_assessments.php` for setup and `hs_review.php` for review.

## Toolbox talks

![Toolbox Talks](../screenshots/hs__toolbox.png)

`toolbox_talks_dashboard.php`, `record_toolbox_talk.php`, `toolbox_talk_detail.php`, `toolbox_talk_employees.php` — record a talk and who attended.

---

## Risk assessments — `risk_assessment/`

![RA Dashboard](../screenshots/hs__ra_dashboard.png)

**This is the current system.** Three linked document types:

| Type | Meaning | Files |
|---|---|---|
| **GRA** | General Risk Assessment | `gra_index.php`, `gra_create.php`, `gra_view.php`, `gra_edit.php` |
| **RA** | Specific Risk Assessment | `ra_index.php`, `ra_create.php`, `ra_view.php`, `ra_edit.php` |
| **MS** | Method Statement | `ms_index.php`, `ms_create.php`, `ms_view.php`, `ms_edit.php` |

`dashboard.php` is the entry point (linked 10 times across the portal). `setup_ra_system.php` is the one-off setup. The `*_edit.php` files are thin redirect shims to the matching `*_create.php`.

> ⚠️ **Three folders look like the risk-assessment system. Only one is current** — see [Known issues](#known-issues).

---

## COSHH — `coshh/`

![COSHH Management](../screenshots/hs__coshh_management.png)

Control of Substances Hazardous to Health: the product register with hazard classification.

| Page | Purpose |
|---|---|
| `coshh_management.php` | Product register |
| `coshh_product.php` | One product (1,702 lines — the biggest page in the module) |
| `manage_hazard_statements.php` | H-codes (`hazard_statements`) |
| `manage_precautionary_statements.php` | P-codes (`precautionary_statements`) |
| `manage_precautionary_combinations.php` | Combined P-codes |
| `api_statements.php` | Statement lookup (JSON) |
| `zone_mapper.php` | Storage zone mapping — **unreferenced** |

![COSHH Product](../screenshots/hs__coshh_product.png)

### Hazard & PPE symbols

- **`ghs_symbols.php`** — the GHS pictogram definitions (GHS01–GHS09), each with a label, colour, inline SVG icon and a **keywords array** used to auto-suggest classifications.
- **`coshh_controls.php`** — PPE symbols. Prefers official **ISO 7010 SVGs** from `coshh/ppe_img/` when present, and **falls back to hand-drawn inline SVG** otherwise.

> `ppe_img/` is currently **empty**, so the hand-drawn fallback is what renders. That's a working state, not a fault. `download_ppe_images.php` / `download_ghs_images.php` fetch the official set, and `create_ppe_images.php` generates them locally — all optional enhancements.

---

## RIDDOR — `riddor/`

![RIDDOR](../screenshots/hs__riddor.png)

Reportable injuries, diseases and dangerous occurrences.

| Page | Purpose |
|---|---|
| `riddor_management.php` | Incident list |
| `riddor_incident_recorder.php` | Record an incident |
| `riddor_incident_detail.php` | View / edit one |
| `riddor_matrix.php` | Overview grid |

> **This is the live RIDDOR.** A second copy exists at `CRUD/fleet/fors/riddor/` — see Known issues.

---

## Known issues

Reviewed 2026-07-21. 47 of 53 tested pages were clean 200s; the rest were id-required pages redirecting correctly.

| # | Issue | Status |
|---|---|---|
| 1 | **`coshh/coshh_product.php` fatalled part-way through rendering.** Line 1544 did `explode(',', $def['keywords'])`, but `ghs_symbols.php` defines `keywords` as an **array** — a `TypeError` on PHP 8. Everything after that point on a live 1,702-line compliance page failed to render. | **Fixed** — reads the array directly (accepting either shape), and the output is now escaped. Page grew ~3.6 KB and the GHS keyword hints render ("e.g. explos, unstable") |
| 2 | **`risk_assessments/ra_master_dashboard.php` has a parse error** — an unclosed `if (!empty($legacyStats)):` at line 116 with no matching `endif`. Git shows the error was present in its **only commit**, so the file has **never worked**. It is also unreferenced and duplicated by a second `ra_master_dashboard.php`. | **Reported, not fixed** — repairing dead code would mean inventing the intent. The missing `endif` belongs at ~line 153. Recommend deletion |
| 3 | **Three folders look like the risk-assessment system.** `risk_assessment/` is **current** (GRA/RA/MS, `dashboard.php` linked 10×). `risk_assessments/` (plural) is the **older system**, still referenced by 2 files. `risk_assessment/legacy/` is referenced by **zero** files and duplicates the plural folder — `risk_assessment_view.php` is byte-identical, `risk_assessment_form.php` differs by 4 lines, `risk_assessment_management.php` by 538. | **Open** — `risk_assessment/legacy/` (3 files, ~1,085 lines) is dead and safe to remove; the singular-vs-plural split needs a decision |
| 4 | **RIDDOR exists twice** (question raised during the [fleet](fleet.md) review). `hs/riddor/` is referenced by 2 files; `fleet/fors/riddor/` by **zero**. The pairs have diverged — `riddor_management` differs by 312 lines, `riddor_matrix` by 225. | **Answered** — `hs/riddor/` is live, the fleet copy is dead and diverged. Recommend deleting `fleet/fors/riddor/` |
| 5 | `coshh/ppe_img/` is empty so the hand-drawn PPE fallback renders instead of official ISO 7010 images. | **Not a fault** — graceful fallback by design. Run `download_ppe_images.php` if the official set is wanted |
| 6 | Other unreferenced files: `h_and_s_dashboard.php` (313 lines — looks live but unlinked), `coshh/zone_mapper.php`, `coshh/create_ppe_images.php`, `risk_assessment/ra_master_dashboard.php`. | **Open** — `h_and_s_dashboard.php` likely needs a link rather than deletion |
| 7 | `setup_coshh_db.php`, `setup_document_versions.php`, `risk_assessment/setup_ra_system.php` are setup scripts still reachable; `setup_document_versions.php` also runs schema changes on load. | **Open** — same self-migrating pattern noted in [fleet.md](fleet.md#known-issues) |

> Auth guards were added to `api_get_accreditation_sections.php`, `coshh/api_statements.php`, `coshh/migrate_msds_tracking.php` and `risk_assessment/{gra,ms,ra}_edit.php` during the [unauthenticated-endpoint sweep](../security-audit-2026-07.md#unauthenticated-endpoint-sweep-2026-07-21-during-the-module-review).
