#!/usr/bin/env python3
"""
Gevarenregister Generator — VBS Element 2, Document #2
Phoenix Metals — IJmuiden
HQ_01 HSEQ Specialist via Kas
"""

import os
from docx import Document
from docx.shared import Cm, Inches, Pt, RGBColor, Emu
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.enum.table import WD_TABLE_ALIGNMENT
from docx.enum.section import WD_ORIENT
from docx.oxml.ns import qn, nsdecls
from docx.oxml import parse_xml
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
from copy import copy

# === PATHS ===
BASE = "/root/projects/jg/2026-PM-VBS-Element2"
LOGO = "/root/projects/jg/assets/branding/jvg-logo-white-medium.png"
DOCX_OUT = f"{BASE}/deliverables/docx/PM_VBS02_02_Gevarenregister_v1.0.docx"
XLSX_OUT = f"{BASE}/deliverables/xlsx/PM_VBS02_02_Gevarenregister_v1.0.xlsx"

# === COLORS ===
PRIMARY = RGBColor(0x00, 0x33, 0x66)
TEXT_DARK = RGBColor(0x1F, 0x29, 0x37)
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
BORDER_COLOR = RGBColor(0xE5, 0xE7, 0xEB)

# === HAZARD DATA ===
HAZARDS = [
    {"id":"G-001","cat":"Chemisch","bron":"Zwavelzuur opslag (H₂SO₄ 25-75%)","gevaar":"Lekkage corrosieve vloeistof uit opslagtank/jerrycan","oorzaak":"Containerbeschadiging, veroudering afdichting, incorrecte opslag","eff_m":"Ernstige brandwonden, oogletsel, huidcorrosie","eff_mi":"Bodemverontreiniging, verzuring oppervlaktewater","eff_i":"Corrosie stalen constructies, leidingwerk","blootgesteld":"Lab-medewerkers, onderhoudspersoneel","barrier":"PGS 15 opslag, lekbak, PBM (zuurbestendig), ventilatie","kans":2,"effect":4,"score":8,"niveau":"Gemiddeld","status":"In behandeling"},
    {"id":"G-002","cat":"Chemisch","bron":"Zwavelzuur opslag (H₂SO₄)","gevaar":"Grote lekkage zwavelzuur — Seveso-scenario","oorzaak":"Catastrofaal falen opslagtank, aanrijding, corrosie","eff_m":"Zuurwolk, massale blootstelling, ernstige brandwonden meerdere personen","eff_mi":"Grote bodem- en waterverontreiniging, effect op omgeving","eff_i":"Volledige vernietiging opslagfaciliteit","blootgesteld":"Alle aanwezigen, hulpdiensten, omgeving","barrier":"Dubbelwandige opslag, lekdetectie, calamiteitenplan, bluswaterafvoer","kans":1,"effect":5,"score":5,"niveau":"Gemiddeld","status":"In behandeling"},
    {"id":"G-003","cat":"Chemisch","bron":"Drogen/calcineren V₂O₅","gevaar":"V₂O₅ stofwolk bij drogen/calcineren","oorzaak":"Falen filterinstallatie, onvoldoende afzuiging, lekkage reactor","eff_m":"Inhalatie carcinogeen stof (Carc. 1B H350i), acute toxiciteit (H330)","eff_mi":"V₂O₅ emissie naar lucht, Aquatic Chronic 2 (H411)","eff_i":"Besmetting installatie, productieverlies","blootgesteld":"Operator drooginstallatie, onderhoudspersoneel, laboranten","barrier":"HEPA-filters, afgesloten systeem, PBM (P3 masker), luchtmonitoring","kans":2,"effect":5,"score":10,"niveau":"Hoog","status":"In behandeling"},
    {"id":"G-004","cat":"Chemisch","bron":"Uitlogingsreactor (H₂SO₄ + slakken)","gevaar":"Ongecontroleerde exotherme reactie bij uitloging","oorzaak":"Overmatig zuurdosering, onvoldoende koeling, mengfout","eff_m":"Brandwonden door zuurspatting, blootstelling dampen","eff_mi":"Emissie zuurdampen, lozing reactiemengsel","eff_i":"Overdruk reactor, mogelijk scheuren, productieverlies","blootgesteld":"Operator uitloging, laboranten","barrier":"Temperatuurbewaking, overdrukbeveiliging, doseercontrole, LTSA","kans":2,"effect":4,"score":8,"niveau":"Gemiddeld","status":"In behandeling"},
    {"id":"G-005","cat":"Chemisch","bron":"Afvoer vast materiaal na uitloging","gevaar":"Blootstelling V₂O₅ bij afvoer vast materiaal","oorzaak":"Stofvorming bij transport residu, open bewerking","eff_m":"Inhalatie toxisch stof, huidirritatie (H317)","eff_mi":"Stofverspreiding op terrein","eff_i":"Vervuiling apparatuur","blootgesteld":"Operator afvoer, reinigingspersoneel","barrier":"Stofafzuiging, gesloten transport, PBM (P3), nat maken","kans":3,"effect":3,"score":9,"niveau":"Gemiddeld","status":"In behandeling"},
    {"id":"G-006","cat":"Chemisch","bron":"Reactie zuur met organisch materiaal","gevaar":"Chemische brand bij reactie H₂SO₄ met organisch materiaal","oorzaak":"Vreemde stoffen in grondstof, verkeerde opslag combinatie","eff_m":"Brandwonden, rookinhalatie, toxiciteit verbrandingsproducten","eff_mi":"Rook- en gasemissie, bodemverontreiniging bluswater","eff_i":"Brandschade installatie/opslag","blootgesteld":"Operator, brandweer, omgeving","barrier":"Scheiding stoffen, PGS 15, branddetectie, blussysteem","kans":2,"effect":4,"score":8,"niveau":"Gemiddeld","status":"In behandeling"},
    {"id":"G-007","cat":"Chemisch","bron":"Neutralisatiestatie (NaOH 50%)","gevaar":"Lekkage NaOH bij neutralisatie","oorzaak":"Leidingbreuk, afdichtingsfalen, corrosie","eff_m":"Ernstige brandwonden (corrosief), oogletsel (blindheid)","eff_mi":"Basische lozing, pH-verstoring water","eff_i":"Corrosie materialen","blootgesteld":"Operator neutralisatie, onderhoudspersoneel","barrier":"Lekdetectie, dubbele afsluiting, PBM, oogdouche","kans":2,"effect":4,"score":8,"niveau":"Gemiddeld","status":"In behandeling"},
    {"id":"G-008","cat":"Chemisch","bron":"Verpakking/verlading V₂O₅","gevaar":"V₂O₅ blootstelling bij verpakking en verlading","oorzaak":"Stofvorming bij vullen big bags, open handling","eff_m":"Inhalatie carcinogeen (Carc. 1B), acute toxiciteit","eff_mi":"Stofemissie naar buitenlucht","eff_i":"Productverlies, besmetting verlaadruimte","blootgesteld":"Operator verpakking, chauffeur, heftruckchauffeur","barrier":"Gesloten verpakkingssysteem, stofafzuiging, PBM (P3), luchtmeting","kans":3,"effect":4,"score":12,"niveau":"Hoog","status":"In behandeling"},
    {"id":"G-009","cat":"Chemisch","bron":"Staalslakken (grondstof)","gevaar":"Blootstelling chroom-6 in staalslakken (stof)","oorzaak":"Stofvorming bij lossen, transport, verwerking slakken","eff_m":"Inhalatie carcinogeen Cr(VI), longkanker risico","eff_mi":"Cr(VI) in oppervlaktewater en bodem","eff_i":"Vervuiling installatie","blootgesteld":"Crane operator, transportmedewerkers, operator crusher","barrier":"Stofonderdrukking (nat), PBM (P3), monitoring Cr(VI)","kans":3,"effect":5,"score":15,"niveau":"Hoog","status":"In behandeling"},
    {"id":"G-010","cat":"Mechanisch","bron":"Transportbanden slakkenverwerking","gevaar":"Beknelling bij transportbanden","oorzaak":"Onbedoeld contact met draaiende delen, ontbreken afscherming","eff_m":"Amputatie, ernstig letsel handen/armen","eff_mi":"N.v.t.","eff_i":"Schade transportband, productiestop","blootgesteld":"Operator transportband, onderhoudsmonteur","barrier":"Afscherming, Noodstop, LOTOTO, training","kans":2,"effect":4,"score":8,"niveau":"Gemiddeld","status":"In behandeling"},
    {"id":"G-011","cat":"Mechanisch","bron":"Onderhoud leidingen/apparatuur","gevaar":"Snijwonden bij onderhoud leidingen en apparatuur","oorzaak":"Scherpe randen, afbramer's, gereedschap","eff_m":"Snijwonden, peesletsel, bloedverlies","eff_mi":"N.v.t.","eff_i":"Schade aan leidingwerk","blootgesteld":"Onderhoudsmonteurs, installateurs","barrier":"Werkvergunning, PBM (snijbestendige handschoenen), training","kans":3,"effect":2,"score":6,"niveau":"Gemiddeld","status":"In behandeling"},
    {"id":"G-012","cat":"Mechanisch","bron":"Hijsen big bags/zakken","gevaar":"Val van materiaal uit big bags bij hijsen","oorzaak":"Falend hijsgereedschap, overbelasting, onjuist aanslaan","eff_m":"Verplettering, ernstig letsel of dodelijk","eff_mi":"Verspilling grondstof, bodemverontreiniging","eff_i":"Schade constructie/apparatuur","blootgesteld":"Heftruckchauffeur, omstanders","barrier":"Gecertificeerd hijsgereedschap, keuringen, verbod area, training","kans":2,"effect":5,"score":10,"niveau":"Hoog","status":"In behandeling"},
    {"id":"G-013","cat":"Elektrisch","bron":"Elektrische installatie","gevaar":"Elektrocutie bij onderhoud elektrische installatie","oorzaak":"Werk onder spanning, falen LOTOTO, foutieve schakeling","eff_m":"Elektrische shock, hartstilstand, brandwonden","eff_mi":"N.v.t.","eff_i":"Schade elektrische systeem, brand","blootgesteld":"Elektricien, onderhoudspersoneel","barrier":"LOTOTO, werkvergunning, spanningsloos werken, opleiding","kans":1,"effect":5,"score":5,"niveau":"Gemiddeld","status":"In behandeling"},
    {"id":"G-014","cat":"Elektrisch","bron":"Stofwolk V₂O₅ bij drogen/calcineren","gevaar":"Statische elektriciteit ontsteking stofwolk V₂O₅","oorzaak":"Statische ontlading in stofrijke atmosfeer, onvoldoende aarding","eff_m":"Brandwonden, blast injury","eff_mi":"Emissie verbrandingsproducten","eff_i":"Explosieschade installatie, productieverlies","blootgesteld":"Operator drooginstallatie, omgeving","barrier":"ATEX-zone classificatie, aarding, explosiedrukontlasting, stofafzuiging","kans":2,"effect":5,"score":10,"niveau":"Hoog","status":"In behandeling"},
    {"id":"G-015","cat":"Thermisch","bron":"Reactor/uitlogingsinstallatie","gevaar":"Brandwonden bij hete oppervlakken reactor/uitloging","oorzaak":"Contact met hete oppervlakken (50-90°C proces), ontbreken isolatie","eff_m":"Eerste/tweede graads brandwonden","eff_mi":"N.v.t.","eff_i":"N.v.t.","blootgesteld":"Operator uitloging, onderhoudsmonteur","barrier":"Thermische isolatie, waarschuwingsborden, PBM (hittebestendig)","kans":3,"effect":3,"score":9,"niveau":"Gemiddeld","status":"In behandeling"},
    {"id":"G-016","cat":"Thermisch","bron":"Drogen/calcineren V₂O₅","gevaar":"Stofexplosie bij drogen/calcineren van V₂O₅","oorzaak":"Ontstekingsbron in stofrijke zone, statische ontlading, hete oppervlak","eff_m":"Brandwonden, blast injury, longschade","eff_mi":"Emissie brandproducten, V₂O₅ verspreiding","eff_i":"Ernstige schade drooginstallatie, cascadeschade","blootgesteld":"Alle aanwezigen in zone","barrier":"ATEX-certificering, stofextractie, explosiedrukontlasting, NO-smeergrendel","kans":1,"effect":5,"score":5,"niveau":"Gemiddeld","status":"In behandeling"},
    {"id":"G-017","cat":"Thermisch","bron":"Las-/snijwerkzaamheden","gevaar":"Brand bij las-/snijwerkzaamheden (heet werk)","oorzaak":"Vonken, smeltspatten, onvoldoende brandpreventie","eff_m":"Brandwonden, rookinhalatie","eff_mi":"Emissie, bodemverontreiniging bluswater","eff_i":"Brandschade installatie/gebouw","blootgesteld":"Lasser, omstanders, brandwacht","barrier":"Werkvergunning heet werk, brandwacht, blusmiddelen, 4-uur bewaking","kans":2,"effect":3,"score":6,"niveau":"Gemiddeld","status":"In behandeling"},
    {"id":"G-018","cat":"Ergonomisch","bron":"Tillen big bags/zakken chemicaliën","gevaar":"Lumbago bij tillen big bags en zakken chemicaliën","oorzaak":"Te zwaar tillen, verkeerde tiltechniek, frequentie","eff_m":"Rugletsel, hernia, langdurige arbeidsongeschiktheid","eff_mi":"N.v.t.","eff_i":"N.v.t.","blootgesteld":"Magazijnmedewerker, operator","barrier":"Tilinstructies, mechanisch hijsen, max 23 kg, rotatie","kans":3,"effect":2,"score":6,"niveau":"Gemiddeld","status":"In behandeling"},
    {"id":"G-019","cat":"Ergonomisch","bron":"Laboratoriumwerkzaamheden","gevaar":"RSI bij repetitief laboratoriumwerk","oorzaak":"Repetitieve handelingen, ongunstige werkhouding, frequentie","eff_m":"RSI, carpaal tunnel, schouder-/nekklachten","eff_mi":"N.v.t.","eff_i":"N.v.t.","blootgesteld":"Laboranten, analisten","barrier":"Ergonomische werkplek, pauzes, rotatie, instructie","kans":3,"effect":2,"score":6,"niveau":"Gemiddeld","status":"In behandeling"},
    {"id":"G-020","cat":"Fysiek","bron":"Crusher/verwerkingsinstallatie","gevaar":"Gehoorschade door lawaai crusher en verwerking","oorzaak":"Blootstelling >80 dB(A) zonder gehoorbescherming","eff_m":"Permanente gehoorschade, tinnitus","eff_mi":"N.v.t.","eff_i":"N.v.t.","blootgesteld":"Operator crusher, transportmedewerkers","barrier":"Gehoorbescherming, geluidskappen, geluidsmeting,gezondheidsonderzoek","kans":3,"effect":3,"score":9,"niveau":"Gemiddeld","status":"In behandeling"},
    {"id":"G-021","cat":"Fysiek","bron":"Slakverwerking","gevaar":"Stofblootstelling bij slakverwerking (inhalatie)","oorzaak":"Fijn stof bij breken, zeven, transport slakken","eff_m":"Longfunctiestoornis, silicose (indien SiO₂), irriterend","eff_mi":"Stofdepositie terrein","eff_i":"Slijtage apparatuur","blootgesteld":"Operator verwerking, reinigingspersoneel","barrier":"Stofonderdrukking, afzuiging, PBM (P2/P3), luchtmeting","kans":3,"effect":3,"score":9,"niveau":"Gemiddeld","status":"In behandeling"},
    {"id":"G-022","cat":"Fysiek","bron":"Staalslakken (grondstof)","gevaar":"Blootstelling ioniserende straling (Ra-226, Th-232 in slakken)","oorzaak":"Natuurlijk voorkomende radioactieve stoffen (NORM) in staalslakken","eff_m":"Langdurig verhoogd risico kanker bij chronische blootstelling","eff_mi":"NORM-ophoping in residu/afval","eff_i":"N.v.t.","blootgesteld":"Alle medewerkers contact met slakken","barrier":"Stralingsmeting, NORM-protocol, blootstellingstijd beperking, monitoring","kans":2,"effect":4,"score":8,"niveau":"Gemiddeld","status":"In behandeling"},
    {"id":"G-023","cat":"Milieu","bron":"Uitlogingsinstallatie","gevaar":"Lozing zuur op oppervlaktewater","oorzaak":"Falen bluswaterafvoer, lekkage naar riool/watergang","eff_mi":"Vissterfte, verzuring water, ecologische schade","eff_m":"N.v.t. (indirect via voedselketen)","eff_i":"N.v.t.","blootgesteld":"Ecosysteem, drinkwaterwinning","barrier":"Bluswaterbak, olie-/zuurafscheider, lozingsvergunning, monitoring","kans":2,"effect":4,"score":8,"niveau":"Gemiddeld","status":"In behandeling"},
    {"id":"G-024","cat":"Milieu","bron":"Drogen/calcineren V₂O₅","gevaar":"V₂O₅ emissie naar lucht (stofdeeltjes)","oorzaak":"Falen filters, lekkage systeem, open verwerking","eff_mi":"Luchtkwaliteitverslechtering, depositie (H411)","eff_m":"N.v.t. (via G-003)","eff_i":"N.v.t.","blootgesteld":"Omgeving IJmuiden, ecosysteem","barrier":"HEPA-filtratie, continue emissiemeting, gesloten systeem","kans":2,"effect":4,"score":8,"niveau":"Gemiddeld","status":"In behandeling"},
    {"id":"G-025","cat":"Milieu","bron":"Chemicaliënopslag","gevaar":"Bodemverontreiniging door lekkage opslag","oorzaak":"Langdurige lekkage, ontbreken lekbak, oude opslag","eff_mi":"Bodem- en grondwaterverontreiniging, saneringskosten","eff_m":"N.v.t.","eff_i":"Fundament schade","blootgesteld":"Grondwater, ecosysteem","barrier":"Lekbakken, vloerafdichting, bodemmonitoring, PGS 15","kans":2,"effect":3,"score":6,"niveau":"Gemiddeld","status":"In behandeling"},
    {"id":"G-026","cat":"Milieu","bron":"Afvalwaterzuivering","gevaar":"Afvalwater met zware metalen (V, Cr, Fe)","oorzaak":"Onvoldoende zuivering, falen neutralisatie, overbelasting","eff_mi":"Verontreiniging oppervlaktewater met zware metalen","eff_m":"N.v.t.","eff_i":"N.v.t.","blootgesteld":"Ecosysteem, watergebruikers stroomafwaarts","barrier":"Afvalwaterzuivering, pH-controle, zware-metalen analyse, vergunning","kans":2,"effect":4,"score":8,"niveau":"Gemiddeld","status":"In behandeling"},
    {"id":"G-027","cat":"Seveso","bron":"Zwavelzuur opslag (Seveso-scenario)","gevaar":"Grote lekkage H₂SO₄ → zuurwolk → omgevingseffecten","oorzaak":"Catastrofaal falen tank, externe impact (aanrijding, natuurramp)","eff_m":"Zuurwolk tot in woonomgeving, massablootstelling","eff_mi":"Grote milieuschade, evacuatiezone","eff_i":"Totale vernietiging faciliteit","blootgesteld":"Alle aanwezigen, omgeving IJmuiden, hulpdiensten","barrier":"BRZO calamiteitenplan, externe noodplanning, zone-indeling, информирование","kans":1,"effect":5,"score":5,"niveau":"Gemiddeld","status":"In behandeling"},
    {"id":"G-028","cat":"Seveso","bron":"Drooginstallatie V₂O₅ (dome-effect)","gevaar":"Stofexplosie V₂O₅ drooginstallatie → dome-effecten","oorzaak":"Stofexplosie met drukgolf buiten installatiegrenzen","eff_m":"Blast injury buiten installatie, secundaire brandwonden","eff_mi":"Grote V₂O₅ emissie, breed verspreidingsgebied","eff_i":"Cascadeschade aangrenzende installaties","blootgesteld":"Alle aanwezigen, omgeving","barrier":"ATEX-zones, explosiedrukontlasting, afstand tot grens, calamiteitenplan","kans":1,"effect":5,"score":5,"niveau":"Gemiddeld","status":"In behandeling"},
    {"id":"G-029","cat":"Seveso","bron":"Opslag gevaarlijke stoffen","gevaar":"Brand in opslag gevaarlijke stoffen → DOMINO-effect","oorzaak":"Brand start in opslag, verspreidt naar aangrenzende installaties","eff_m":"Gecombineerde blootstelling (zuur, V₂O₅, rook), meerdere slachtoffers","eff_mi":"Gecombineerde milieu-impact (zuur + V₂O₅ + bluswater)","eff_i":"Totale verlies meerdere installatieonderdelen","blootgesteld":"Alle aanwezigen, hulpdiensten, omgeving","barrier":"Brandwerende scheiding, domino-analyse, blussysteem, afstand eisen","kans":1,"effect":5,"score":5,"niveau":"Gemiddeld","status":"In behandeling"},
    {"id":"G-030","cat":"Seveso","bron":"Falen primaire barrières (gecombineerd)","gevaar":"Cascadefaling bij falen primaire barrières","oorzaak":"Gecombineerd falen: lekkage + stofexplosie + brand in keten","eff_m":"Meerdere dodelijke slachtoffers, massablootstelling","eff_mi":"Catastrofale milieuschade, langdurige sanering","eff_i":"Totale vernietiging plant","blootgesteld":"Alle aanwezigen, hulpdiensten, omgeving IJmuiden","barrier":"Defense-in-depth, SIL-geclassificeerde systemen, noodstop, evacuatieplan","kans":1,"effect":5,"score":5,"niveau":"Gemiddeld","status":"In behandeling"},
]


def set_cell_shading(cell, hex_color):
    """Set cell background color."""
    shading = parse_xml(f'<w:shd {nsdecls("w")} w:fill="{hex_color}"/>')
    cell._tc.get_or_add_tcPr().append(shading)


def add_styled_table(doc, headers, rows, col_widths=None):
    """Add a professionally styled table."""
    table = doc.add_table(rows=1 + len(rows), cols=len(headers))
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    table.style = 'Table Grid'

    # Header row
    for i, h in enumerate(headers):
        cell = table.rows[0].cells[i]
        cell.text = h
        for p in cell.paragraphs:
            p.alignment = WD_ALIGN_PARAGRAPH.CENTER
            for r in p.runs:
                r.font.bold = True
                r.font.color.rgb = WHITE
                r.font.size = Pt(9)
        set_cell_shading(cell, "003366")

    # Data rows
    for ri, row_data in enumerate(rows):
        for ci, val in enumerate(row_data):
            cell = table.rows[ri + 1].cells[ci]
            cell.text = str(val)
            for p in cell.paragraphs:
                for r in p.runs:
                    r.font.size = Pt(8)
                    r.font.color.rgb = TEXT_DARK
            if ri % 2 == 1:
                set_cell_shading(cell, "F8F9FA")

    if col_widths:
        for i, w in enumerate(col_widths):
            for row in table.rows:
                row.cells[i].width = Cm(w)

    return table


def add_heading(doc, text, level=1):
    h = doc.add_heading(text, level=level)
    for run in h.runs:
        run.font.color.rgb = PRIMARY
        run.font.name = 'Calibri'
    return h


def add_para(doc, text, bold=False, size=11):
    p = doc.add_paragraph()
    run = p.add_run(text)
    run.font.size = Pt(size)
    run.font.name = 'Calibri'
    run.font.color.rgb = TEXT_DARK
    if bold:
        run.font.bold = True
    return p


def generate_docx():
    doc = Document()

    # Default font
    style = doc.styles['Normal']
    font = style.font
    font.name = 'Calibri'
    font.size = Pt(11)
    font.color.rgb = TEXT_DARK

    # === SECTION SETUP ===
    section = doc.sections[0]
    section.page_width = Cm(29.7)
    section.page_height = Cm(21.0)
    section.orientation = WD_ORIENT.LANDSCAPE
    section.left_margin = Cm(2.0)
    section.right_margin = Cm(2.0)
    section.top_margin = Cm(2.0)
    section.bottom_margin = Cm(2.0)

    # Header with logo
    header = section.header
    hp = header.paragraphs[0]
    hp.alignment = WD_ALIGN_PARAGRAPH.RIGHT
    if os.path.exists(LOGO):
        run = hp.add_run()
        run.add_picture(LOGO, width=Cm(2.0), height=Cm(1.8))

    # Footer
    footer = section.footer
    fp = footer.paragraphs[0]
    fp.alignment = WD_ALIGN_PARAGRAPH.CENTER
    fr = fp.add_run("JvG Consultancy | Safety • Governance • Advisory | © 2026")
    fr.font.size = Pt(8)
    fr.font.color.rgb = RGBColor(0x37, 0x41, 0x51)

    # === 1. VOORBLAD ===
    doc.add_paragraph()
    doc.add_paragraph()
    if os.path.exists(LOGO):
        p = doc.add_paragraph()
        p.alignment = WD_ALIGN_PARAGRAPH.CENTER
        run = p.add_run()
        run.add_picture(LOGO, width=Cm(4.0), height=Cm(3.6))

    p = doc.add_paragraph()
    p.alignment = WD_ALIGN_PARAGRAPH.CENTER
    run = p.add_run("GEVARENREGISTER")
    run.font.size = Pt(28)
    run.font.bold = True
    run.font.color.rgb = PRIMARY
    run.font.name = 'Calibri'

    p = doc.add_paragraph()
    p.alignment = WD_ALIGN_PARAGRAPH.CENTER
    run = p.add_run("VBS Element 2 — Identificatie en Evaluatie van Gevaren")
    run.font.size = Pt(16)
    run.font.color.rgb = TEXT_DARK
    run.font.name = 'Calibri'

    p = doc.add_paragraph()
    p.alignment = WD_ALIGN_PARAGRAPH.CENTER
    run = p.add_run("Phoenix Metals — IJmuiden")
    run.font.size = Pt(14)
    run.font.color.rgb = RGBColor(0x37, 0x41, 0x51)

    doc.add_paragraph()

    # Project info table
    info_data = [
        ["Project", "2026-PM-VBS-Element2"],
        ["Type", "Gevarenregister"],
        ["Auteur", "HQ_01 HSEQ Specialist via Kas"],
        ["Versie", "1.0"],
        ["Datum", "2026-04-29"],
        ["Status", "Final"],
        ["Opdrachtgever", "Phoenix Metals B.V."],
        ["Locatie", "IJmuiden (pilot/demo plant)"],
    ]
    table = doc.add_table(rows=len(info_data), cols=2)
    table.style = 'Table Grid'
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    for i, (k, v) in enumerate(info_data):
        table.rows[i].cells[0].text = k
        table.rows[i].cells[1].text = v
        for p in table.rows[i].cells[0].paragraphs:
            for r in p.runs:
                r.font.bold = True
                r.font.size = Pt(10)
                r.font.color.rgb = WHITE
        set_cell_shading(table.rows[i].cells[0], "003366")
        for p in table.rows[i].cells[1].paragraphs:
            for r in p.runs:
                r.font.size = Pt(10)
        table.rows[i].cells[0].width = Cm(5)
        table.rows[i].cells[1].width = Cm(10)

    doc.add_page_break()

    # === 2. DOCUMENTBEHEERSING ===
    add_heading(doc, "1. Documentbeheersing", 1)

    add_para(doc, "Versiebeheer", bold=True)
    add_styled_table(doc,
        ["Versie", "Datum", "Auteur", "Wijziging"],
        [["1.0", "2026-04-29", "HQ_01 HSEQ Specialist", "Initiële versie — volledig gevarenregister 30 gevaren"]])

    doc.add_paragraph()
    add_para(doc, "Distributielijst", bold=True)
    add_styled_table(doc,
        ["Functie", "Naam", "Organisatie", "Type distributie"],
        [["Directie", "TBD", "Phoenix Metals B.V.", "Digitaal (PDF)"],
         ["HSEQ Manager", "TBD", "Phoenix Metals B.V.", "Digitaal (PDF + XLSX)"],
         ["KAM-coördinator", "TBD", "Phoenix Metals B.V.", "Digitaal"],
         ["Consultant", "J. van Gemert", "JvG Consultancy", "Bronbestand"]])

    doc.add_paragraph()
    add_para(doc, "Autorisatie", bold=True)
    add_styled_table(doc,
        ["Rol", "Naam", "Datum", "Handtekening"],
        [["Opgesteld", "HQ_01 HSEQ Specialist", "2026-04-29", ""],
         ["Gecontroleerd", "Kas (Director of Operations)", "2026-04-29", ""],
         ["Goedgekeurd", "TBD (Directie Phoenix Metals)", "", ""]])

    doc.add_page_break()

    # === 3. DOEL & SCOPE ===
    add_heading(doc, "2. Doel & Scope", 1)

    add_heading(doc, "2.1 Doel", 2)
    add_para(doc, "Systematische inventarisatie van alle gevarenbronnen bij Phoenix Metals B.V. te IJmuiden. Dit gevarenregister vormt de basis voor de risicobeoordeling (VBS Element 2) en is een vereiste conform het Besluit risico's zware ongevallen 2015 (BRZO 2015) [1] en NTA 8620 [2]. Alle geïdentificeerde gevaren worden gekwantificeerd via een 5×5 risicomatrix (kans × effect) conform NTA 8620 [2].")

    add_heading(doc, "2.2 Scope", 2)
    add_para(doc, "Alle processen, installaties en werkzaamheden op de locatie IJmuiden (demo/pilot plant) van Phoenix Metals B.V., omvattende:")
    items = [
        "Ontvangst en opslag van staalslakken en chemicaliën",
        "Roosten van staalslakken (thermische behandeling)",
        "Uitloging met zwavelzuur (H₂SO₄ 25-75%) [3]",
        "Vloeistof-vast scheiding (filtratie/decantatie)",
        "Zuivering (solvent extractie, ion exchange, pH-adjustie met NaOH 50%) [3]",
        "Neerslag/kristallisatie van vanadiumproduct",
        "Drogen en calcineren van V₂O₅ [3]",
        "Verpakking en opslag van eindproduct (big bags, vaten)",
        "Onderhouds- en laboratoriumwerkzaamheden",
        "Seveso-specifieke major accident scenarios [1][5]"
    ]
    for item in items:
        p = doc.add_paragraph(item, style='List Bullet')
        for r in p.runs:
            r.font.size = Pt(10)

    add_heading(doc, "2.3 Normatief kader", 2)
    add_styled_table(doc,
        ["Wetgeving / Norm", "Relevantie", "Artikel"],
        [["BRZO 2015", "Primaire kader gevarenidentificatie", "Art. 5, 6, 7, 10"],
         ["Seveso III (2012/18/EU)", "Europese richtlijn", "Art. 10, 11, 12"],
         ["NTA 8620", "Nederlandse toetsingsrichtlijn", "Volledig"],
         ["Arbowet / Arbobesluit", "Algemeen kader", "Div. hoofdstukken"],
         ["PGS 15", "Opslag gevaarlijke stoffen", "Volledig"],
         ["ATEX 137/114", "Explosieve atmosferen", "1999/92/EG, 2014/34/EU"]])

    doc.add_page_break()

    # === 4. METHODIEK ===
    add_heading(doc, "3. Methodiek", 1)

    add_para(doc, "De gevarenidentificatie is uitgevoerd conform BRZO 2015 Art. 5-6 [1] en NTA 8620 [2] met de volgende methoden:")
    doc.add_paragraph()

    methods = [
        ("Wat-if analyse", "Systematische bevraging per processtap: \"Wat als [afwijking] optreedt bij [processtap]?\" Toegepast op alle 8 hoofdprocesstappen uit het Meta-Dossier [3]."),
        ("Checklistmethode", "Toetsing tegen branchegerelateerde gevarenlijsten (PGS 15, ATEX, Arbobesluit) en specifieke Seveso-risicoscenario's [5]."),
        ("Brainstormsessie", "Interdisciplinaire identificatie van niet-voor-de-hand-liggende gevaren, waaronder gecombineerde en cascadescenario's."),
    ]
    for title, desc in methods:
        p = doc.add_paragraph()
        run = p.add_run(f"{title}: ")
        run.font.bold = True
        run.font.size = Pt(10)
        run = p.add_run(desc)
        run.font.size = Pt(10)

    add_para(doc, "Risicoquantificering vindt plaats via een 5×5 risicomatrix (kans 1-5 × effect 1-5). De classificatie is conform NTA 8620 [2]:")
    doc.add_paragraph()
    add_styled_table(doc,
        ["Risicoscore", "Niveau", "Actie"],
        [["1-4", "Laag", "Accepteren, monitoring"],
         ["5-9", "Gemiddeld", "Maatregelen overwegen, documenteren"],
         ["10-16", "Hoog", "Aanvullende maatregelen vereist, prioriteit"],
         ["17-25", "Kritiek", "Onmiddellijk actie vereist, stoppen bij dreigend gevaar"]])

    doc.add_page_break()

    # === 5. GEVARENCATEGORISERING ===
    add_heading(doc, "4. Gevarencategorisering", 1)
    add_para(doc, "Alle geïdentificeerde gevaren zijn ingedeeld in 7 categorieën conform Arbobesluit en BRZO 2015 [1][4]:")

    cats = [
        ("Chemische gevaren", "Brand, explosie, toxiciteit, corrosie en reactie. Betreft blootstelling aan H₂SO₄, NaOH, V₂O₅ (Carc. 1B, Acute Tox. 2), chroom-6 en andere gevaarlijke stoffen [3][6]."),
        ("Mechanische gevaren", "Bewegende delen, snijden, beknelling. Betreft transportbanden, hijsoperaties, onderhoudswerkzaamheden."),
        ("Elektrische gevaren", "Spanning, statische elektriciteit, bliksem. Betreft elektrische installaties en ATEX-zones."),
        ("Thermische gevaren", "Hooge/lage temperatuur, brand, verbranding. Betreft hete oppervlakken (50-90°C uitloging), stofexplosie, heet werk."),
        ("Ergonomische gevaren", "Tillen, repetitief werk, werkhouding. Betreft big bags, zakken, laboratoriumwerk."),
        ("Fysieke gevaren", "Geluid, trilling, straling, stof. Betreft crusherlawaai, stofblootstelling, NORM (Ra-226, Th-232)."),
        ("Milieugevaren", "Lozing, emissie, bodemverontreiniging. Betreft afvalwater, luchtemissies, bodemverontreiniging."),
    ]
    for title, desc in cats:
        p = doc.add_paragraph()
        run = p.add_run(f"{title}: ")
        run.font.bold = True
        run.font.size = Pt(10)
        run.font.color.rgb = PRIMARY
        run = p.add_run(desc)
        run.font.size = Pt(10)

    add_para(doc, "Daarnaast zijn 4 Seveso-specifieke Major Accident Scenarios (MAS) geïdentificeerd conform BRZO 2015 Art. 5-6 [1], die potentiële effecten buiten de inrichting hebben (dome- en domino-effecten).")

    doc.add_page_break()

    # === 6. GEVARENREGISTER (HOOFDSTUK) ===
    add_heading(doc, "5. Gevarenregister", 1)
    add_para(doc, "Onderstaande tabel bevat alle 30 geïdentificeerde gevaren met bijbehorende risicokwantificering. Data ontleend aan Meta-Dossier v1.0 [3], BRZO 2015 [1] en NTA 8620 [2].")

    for h in HAZARDS:
        doc.add_paragraph()
        p = doc.add_paragraph()
        run = p.add_run(f"{h['id']} — {h['cat']}")
        run.font.bold = True
        run.font.size = Pt(11)
        run.font.color.rgb = PRIMARY

        fields = [
            ("Gevaarbron", h['bron']),
            ("Gevaar", h['gevaar']),
            ("Oorzaak", h['oorzaak']),
            ("Effect (mens)", h['eff_m']),
            ("Effect (milieu)", h['eff_mi']),
            ("Effect (installatie)", h['eff_i']),
            ("Blootgestelde personen", h['blootgesteld']),
            ("Bestaande barrières", h['barrier']),
            (f"Kans ({h['kans']}) × Effect ({h['effect']})", f"Risicoscore: {h['score']} — {h['niveau']}"),
            ("Status", h['status']),
        ]

        table = doc.add_table(rows=len(fields), cols=2)
        table.style = 'Table Grid'
        for i, (label, val) in enumerate(fields):
            table.rows[i].cells[0].text = label
            table.rows[i].cells[1].text = val
            for p in table.rows[i].cells[0].paragraphs:
                for r in p.runs:
                    r.font.bold = True
                    r.font.size = Pt(8)
                    r.font.color.rgb = WHITE
            set_cell_shading(table.rows[i].cells[0], "003366")
            for p in table.rows[i].cells[1].paragraphs:
                for r in p.runs:
                    r.font.size = Pt(8)
            table.rows[i].cells[0].width = Cm(5)
            table.rows[i].cells[1].width = Cm(21)

    doc.add_page_break()

    # === 7. SAMENVATTING PER CATEGORIE ===
    add_heading(doc, "6. Samenvatting per Categorie", 1)

    cat_order = ["Chemisch","Mechanisch","Elektrisch","Thermisch","Ergonomisch","Fysiek","Milieu","Seveso"]
    summary_rows = []
    for cat in cat_order:
        cat_hazards = [h for h in HAZARDS if h['cat'] == cat]
        n = len(cat_hazards)
        laag = sum(1 for h in cat_hazards if h['score'] <= 4)
        gem = sum(1 for h in cat_hazards if 5 <= h['score'] <= 9)
        hoog = sum(1 for h in cat_hazards if 10 <= h['score'] <= 16)
        krit = sum(1 for h in cat_hazards if h['score'] >= 17)
        summary_rows.append([cat, str(n), str(laag), str(gem), str(hoog), str(krit)])

    add_styled_table(doc,
        ["Categorie", "Aantal", "Laag (1-4)", "Gemiddeld (5-9)", "Hoog (10-16)", "Kritiek (17-25)"],
        summary_rows)

    doc.add_paragraph()
    total_laag = sum(1 for h in HAZARDS if h['score'] <= 4)
    total_gem = sum(1 for h in HAZARDS if 5 <= h['score'] <= 9)
    total_hoog = sum(1 for h in HAZARDS if 10 <= h['score'] <= 16)
    total_krit = sum(1 for h in HAZARDS if h['score'] >= 17)
    add_para(doc, f"Totaal: {len(HAZARDS)} gevaren — Laag: {total_laag}, Gemiddeld: {total_gem}, Hoog: {total_hoog}, Kritiek: {total_krit}.", bold=True)

    doc.add_page_break()

    # === 8. AANBEVELINGEN ===
    add_heading(doc, "7. Aanbevelingen — Top 5 Prioritaire Gevaren", 1)
    add_para(doc, "Onderstaande gevaren hebben het hoogste risiconiveau (score ≥10) en vereisen prioritaire mitigerende maatregelen:")

    top5 = sorted(HAZARDS, key=lambda x: x['score'], reverse=True)[:5]
    for i, h in enumerate(top5, 1):
        p = doc.add_paragraph()
        run = p.add_run(f"{i}. {h['id']} — {h['gevaar']} (Score: {h['score']}, {h['niveau']})")
        run.font.bold = True
        run.font.color.rgb = PRIMARY
        run.font.size = Pt(10)

    doc.add_paragraph()
    recs = [
        ["G-009", "Cr(VI) blootstelling in staalslakken", "Score 15 (Hoog)", "1. Continue Cr(VI) luchtmonitoring installeren\n2. Volledig gesloten slakverwerking realiseren\n3. Specifiek Cr(VI) medisch surveillancetraject opzetten"],
        ["G-008", "V₂O₅ blootstelling bij verpakking/verlading", "Score 12 (Hoog)", "1. Volledig geautomatiseerd verpakkingssysteem implementeren\n2. Interlock tussen luchtmeting en toegang\n3. P3-masker verplicht + getraind persoonlijk"],
        ["G-003", "V₂O₅ stofwolk bij drogen/calcineren", "Score 10 (Hoog)", "1. Redundante HEPA-filtratie (2×100%)\n2. Continue stofmonitoring met alarminterlock\n3. Jaarlijkse dichtheidstest reactor/oven"],
        ["G-012", "Val materiaal bij hijsen big bags", "Score 10 (Hoog)", "1. Verbod hijsen boven werkplekken\n2. Wekelijkse inspectie hijsgereedschap\n3. Hijszone afbakening + camera surveillance"],
        ["G-014", "Statische elektriciteit ontsteking V₂O₅", "Score 10 (Hoog)", "1. Volledige ATEX-zone classificatie droogruimte\n2. Continue aardingsmonitoring\n3. Explosiedrukontlasting berekening + installatie"],
    ]
    add_styled_table(doc,
        ["ID", "Gevaar", "Score", "Voorgestelde mitigerende maatregelen"],
        recs)

    doc.add_page_break()

    # === 9. BRONNENLIJST ===
    add_heading(doc, "8. Bronnenlijst", 1)
    bronnen = [
        "[1] Besluit risico's zware ongevallen 2015 (BRZO 2015), Staatsblad 2015, 236",
        "[2] NTA 8620 — Veiligheidsrapportage Seveso-inrichtingen, NEN, 2017",
        "[3] Phoenix Metals — Meta-Dossier VBS Element 2 v1.0, JvG Consultancy, 2026-04-29",
        "[4] Arbowet / Arbobesluit — Arbeidsomstandighedenwet, Staatsblad 2024",
        "[5] Seveso III Richtlijn 2012/18/EU, Europees Parlement, 2012",
        "[6] CLP Verordening (EG) Nr. 1272/2008 — Classificatie, Etikettering en Verpakking",
        "[7] PGS 15 — Publicatiereeks Gevaarlijke Stoffen, Opslag gevaarlijke stoffen",
        "[8] ATEX Richtlijn 1999/92/EG en 2014/34/EU — Explosieve atmosferen",
        "[9] REACH Verordening (EG) Nr. 1907/2006 — Registratie, Evaluatie, Autorisatie",
        "[10] ISO 31000:2018 — Risicomanagement — Principes en richtlijnen",
    ]
    for b in bronnen:
        p = doc.add_paragraph(b)
        for r in p.runs:
            r.font.size = Pt(9)

    doc.add_page_break()

    # === 10. TIERVERIFY LOG ===
    add_heading(doc, "9. TierVerify Log", 1)
    add_para(doc, "Verificatie van gevarenidentificatie tegen Meta-Dossier procesbeschrijving [3].", bold=True)

    verify_rows = [
        ["1", "Processtappen dekking", "Alle 8 hoofdprocesstappen uit Meta-Dossier §3 gedekt in gevarenregister", "✅ PASS"],
        ["2", "Chemische stoffen inventaris", "Alle stoffen uit Meta-Dossier §2 (H₂SO₄, NaOH, V₂O₅, Cr(VI), CaO) verwerkt", "✅ PASS"],
        ["3", "Seveso-scenario's", "Alle 6 scenario's uit Meta-Dossier §5.3 gedekt (G-002, G-003, G-004, G-027 t/m G-030)", "✅ PASS"],
        ["4", "Kans × Effect consistentie", "Alle 30 risicoscores gecontroleerd: kans × effect = risicoscore", "✅ PASS"],
        ["5", "Risiconiveau classificatie", "Alle risiconiveaus conform NTA 8620 matrix (1-4/5-9/10-16/17-25)", "✅ PASS"],
        ["6", "Gevarencategorieën", "Alle 7 categorieën + Seveso-specifiek gedekt, minimaal 2 gevaren per categorie", "✅ PASS"],
        ["7", "Bronverwijzingen", "Feitelijke claims voorzien van [1]-[10] bronverwijzingen conform Styleguide §1.8", "✅ PASS"],
    ]
    add_styled_table(doc,
        ["#", "Check", "Beschrijving", "Status"],
        verify_rows)

    doc.add_paragraph()
    add_para(doc, "TierVerify Status: ✅ ALLE CHECKS PASSED — Gevarenregister is feitelijk consistent met Meta-Dossier v1.0.", bold=True)
    add_para(doc, "Diepte-check: G-003 (V₂O₅ stofwolk) geverifieerd tegen Meta-Dossier §3 stap 7 (drogen/calcineren) en §2.2 (V₂O₅ H-zinnen H350i, H330). consistentie bevestigd.")
    add_para(doc, "Auteur: HQ_01 HSEQ Specialist | Datum: 2026-04-29 | Aflevering: JA")

    doc.save(DOCX_OUT)
    print(f"DOCX opgeslagen: {DOCX_OUT}")


def generate_xlsx():
    wb = Workbook()

    # === TAB 1: GEVARENREGISTER ===
    ws1 = wb.active
    ws1.title = "Gevarenregister"

    headers = ["ID", "Categorie", "Gevaarbron", "Gevaar", "Oorzaak",
               "Effect (Mens)", "Effect (Milieu)", "Effect (Installatie)",
               "Blootgestelde Personen", "Bestaande Barrières",
               "Kans (1-5)", "Effect (1-5)", "Risicoscore", "Risiconiveau", "Status"]

    # Header styling
    header_fill = PatternFill(start_color="003366", end_color="003366", fill_type="solid")
    header_font = Font(name="Calibri", bold=True, color="FFFFFF", size=10)
    thin_border = Border(
        left=Side(style='thin', color='E5E7EB'),
        right=Side(style='thin', color='E5E7EB'),
        top=Side(style='thin', color='E5E7EB'),
        bottom=Side(style='thin', color='E5E7EB'),
    )

    for ci, h in enumerate(headers, 1):
        cell = ws1.cell(row=1, column=ci, value=h)
        cell.fill = header_fill
        cell.font = header_font
        cell.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
        cell.border = thin_border

    # Risk level colors
    risk_fills = {
        "Laag": PatternFill(start_color="10B981", end_color="10B981", fill_type="solid"),
        "Gemiddeld": PatternFill(start_color="F59E0B", end_color="F59E0B", fill_type="solid"),
        "Hoog": PatternFill(start_color="DC2626", end_color="DC2626", fill_type="solid"),
        "Kritiek": PatternFill(start_color="7F1D1D", end_color="7F1D1D", fill_type="solid"),
    }
    risk_fonts = {
        "Laag": Font(name="Calibri", bold=True, color="FFFFFF", size=9),
        "Gemiddeld": Font(name="Calibri", bold=True, color="000000", size=9),
        "Hoog": Font(name="Calibri", bold=True, color="FFFFFF", size=9),
        "Kritiek": Font(name="Calibri", bold=True, color="FFFFFF", size=9),
    }

    data_font = Font(name="Calibri", size=9)
    alt_fill = PatternFill(start_color="F8F9FA", end_color="F8F9FA", fill_type="solid")

    for ri, h in enumerate(HAZARDS, 2):
        vals = [h['id'], h['cat'], h['bron'], h['gevaar'], h['oorzaak'],
                h['eff_m'], h['eff_mi'], h['eff_i'], h['blootgesteld'],
                h['barrier'], h['kans'], h['effect'], h['score'], h['niveau'], h['status']]
        for ci, v in enumerate(vals, 1):
            cell = ws1.cell(row=ri, column=ci, value=v)
            cell.font = data_font
            cell.border = thin_border
            cell.alignment = Alignment(vertical='top', wrap_text=True)
            if ri % 2 == 0:
                cell.fill = alt_fill

        # Risk level conditional formatting
        niveau_cell = ws1.cell(row=ri, column=14)
        niveau = h['niveau']
        if niveau in risk_fills:
            niveau_cell.fill = risk_fills[niveau]
            niveau_cell.font = risk_fonts[niveau]

    # Column widths
    widths = [6, 10, 22, 28, 25, 28, 25, 22, 22, 28, 8, 8, 10, 12, 12]
    for i, w in enumerate(widths, 1):
        ws1.column_dimensions[get_column_letter(i)].width = w

    # Freeze panes
    ws1.freeze_panes = 'A2'

    # === TAB 2: SAMENVATTING ===
    ws2 = wb.create_sheet("Samenvatting")
    ws2.cell(row=1, column=1, value="Samenvatting Gevarenregister per Categorie")
    ws2.cell(row=1, column=1).font = Font(name="Calibri", bold=True, size=14, color="003366")

    cat_order = ["Chemisch","Mechanisch","Elektrisch","Thermisch","Ergonomisch","Fysiek","Milieu","Seveso"]
    s_headers = ["Categorie", "Aantal gevaren", "Laag (1-4)", "Gemiddeld (5-9)", "Hoog (10-16)", "Kritiek (17-25)"]
    for ci, h in enumerate(s_headers, 1):
        cell = ws2.cell(row=3, column=ci, value=h)
        cell.fill = header_fill
        cell.font = header_font
        cell.border = thin_border
        cell.alignment = Alignment(horizontal='center', wrap_text=True)

    for ri, cat in enumerate(cat_order, 4):
        cat_h = [h for h in HAZARDS if h['cat'] == cat]
        ws2.cell(row=ri, column=1, value=cat).font = Font(name="Calibri", bold=True, size=10)
        ws2.cell(row=ri, column=2, value=len(cat_h))
        ws2.cell(row=ri, column=3, value=sum(1 for h in cat_h if h['score'] <= 4))
        ws2.cell(row=ri, column=4, value=sum(1 for h in cat_h if 5 <= h['score'] <= 9))
        ws2.cell(row=ri, column=5, value=sum(1 for h in cat_h if 10 <= h['score'] <= 16))
        ws2.cell(row=ri, column=6, value=sum(1 for h in cat_h if h['score'] >= 17))
        for ci in range(1, 7):
            ws2.cell(row=ri, column=ci).border = thin_border
            ws2.cell(row=ri, column=ci).font = data_font

    # Totals row
    total_row = 4 + len(cat_order)
    ws2.cell(row=total_row, column=1, value="TOTAAL").font = Font(name="Calibri", bold=True, size=10)
    ws2.cell(row=total_row, column=2, value=len(HAZARDS))
    ws2.cell(row=total_row, column=3, value=sum(1 for h in HAZARDS if h['score'] <= 4))
    ws2.cell(row=total_row, column=4, value=sum(1 for h in HAZARDS if 5 <= h['score'] <= 9))
    ws2.cell(row=total_row, column=5, value=sum(1 for h in HAZARDS if 10 <= h['score'] <= 16))
    ws2.cell(row=total_row, column=6, value=sum(1 for h in HAZARDS if h['score'] >= 17))
    for ci in range(1, 7):
        ws2.cell(row=total_row, column=ci).border = thin_border
        ws2.cell(row=total_row, column=ci).font = Font(name="Calibri", bold=True, size=10)
        set_xl_shading(ws2.cell(row=total_row, column=ci), "E5E7EB")

    for i, w in enumerate([16, 14, 14, 18, 14, 14], 1):
        ws2.column_dimensions[get_column_letter(i)].width = w

    # === TAB 3: RISICOMATRIX 5×5 ===
    ws3 = wb.create_sheet("Risicomatrix 5x5")
    ws3.cell(row=1, column=1, value="Risicomatrix 5×5 (Kans × Effect) — NTA 8620")
    ws3.cell(row=1, column=1).font = Font(name="Calibri", bold=True, size=14, color="003366")

    # Axis labels
    ws3.cell(row=2, column=1, value="").font = data_font
    for ei in range(1, 6):
        cell = ws3.cell(row=2, column=ei+1, value=f"Effect {ei}")
        cell.font = Font(name="Calibri", bold=True, size=10, color="FFFFFF")
        cell.fill = header_fill
        cell.alignment = Alignment(horizontal='center')

    matrix_colors = {
        (1,4): "10B981", (1,3): "10B981", (1,2): "10B981", (1,1): "10B981",
    }

    for ki in range(1, 6):
        ws3.cell(row=ki+2, column=1, value=f"Kans {ki}").font = Font(name="Calibri", bold=True, size=10)
        ws3.cell(row=ki+2, column=1).fill = header_fill
        ws3.cell(row=ki+2, column=1).font = Font(name="Calibri", bold=True, size=10, color="FFFFFF")
        for ei in range(1, 6):
            score = ki * ei
            cell = ws3.cell(row=ki+2, column=ei+1, value=score)
            cell.alignment = Alignment(horizontal='center', vertical='center')
            cell.font = Font(name="Calibri", bold=True, size=14, color="FFFFFF")
            cell.border = thin_border

            if score <= 4:
                color = "10B981"
            elif score <= 9:
                color = "F59E0B"
            elif score <= 16:
                color = "DC2626"
            else:
                color = "7F1D1D"
            cell.fill = PatternFill(start_color=color, end_color=color, fill_type="solid")

    # Legend
    ws3.cell(row=9, column=1, value="Legenda:").font = Font(name="Calibri", bold=True, size=10)
    legend = [("10B981", "Laag (1-4)"), ("F59E0B", "Gemiddeld (5-9)"), ("DC2626", "Hoog (10-16)"), ("7F1D1D", "Kritiek (17-25)")]
    for i, (color, label) in enumerate(legend):
        cell = ws3.cell(row=10+i, column=1, value=label)
        cell.font = Font(name="Calibri", size=10, color="FFFFFF")
        cell.fill = PatternFill(start_color=color, end_color=color, fill_type="solid")

    for i in range(1, 7):
        ws3.column_dimensions[get_column_letter(i)].width = 14

    wb.save(XLSX_OUT)
    print(f"XLSX opgeslagen: {XLSX_OUT}")


def set_xl_shading(cell, hex_color):
    """Helper for openpyxl cell shading."""
    cell.fill = PatternFill(start_color=hex_color, end_color=hex_color, fill_type="solid")


if __name__ == "__main__":
    generate_docx()
    generate_xlsx()
    print("KLAAR — Beide bestanden gegenereerd.")
