"""
Build 2629 TCE Erskine Park WWTP Slab pricing pack — all deliverables.

Outputs (all `_v-CC` suffix per AB-Test-Tracker convention):
    1. 2629-Pre-Pricing-Grill_v-CC.pdf       (branded LFCS PDF render of vault MD)
    2. 2629-Take-Off_v-CC.xlsx               (Quantity take-off, per element)
    3. 2629-Rate-Buildup_v-CC.xlsx           (Internal QS — Labour + Materials + Summary + Sensitivity + Sources)
    4. 2629-Tender-Summary_v-CC.xlsx         (Client-facing 6-sheet workbook)
    5. 2629-Tender-Summary-Cover_v-CC.pdf    (1-page PDF cover for email body)
    6. 2629-Inclusions-Exclusions_v-CC.pdf   (Branded PDF render of vault MD)
    7. 2629-Submission-Cover_v-CC.pdf        (LFCS letterhead Word equivalent — addressed to TCE Kirolos Barsom)

Branding: LFCS navy + red palette, Arial fonts, header with company name, footer with ABN + page no.
Source-of-truth: vault markdown files. Drive uploads happen separately via gws CLI.
"""

from __future__ import annotations
import os
from pathlib import Path
from datetime import date

from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter

from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import mm
from reportlab.lib.colors import HexColor
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, PageBreak, KeepTogether,
)
from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY


# ─────────────────────────────────────────────────────────────────────────────
# Paths + constants
# ─────────────────────────────────────────────────────────────────────────────

VAULT_ROOT = Path(r"C:\Users\mclou\rateright-growth-deploy\HQ-Vault\11_OpsMan_LFCS")
JOB_ROOT = VAULT_ROOT / "Operations" / "Jobs" / "Upcoming" / "2629 - TCE - Erskine Park WWTP Slab"
OUT_02A = JOB_ROOT / "02 - Scope and Pricing" / "02a - Quotes and Estimates"
OUT_02B = JOB_ROOT / "02 - Scope and Pricing" / "02b - BOQ and Rate Schedules"

OUT_02A.mkdir(parents=True, exist_ok=True)
OUT_02B.mkdir(parents=True, exist_ok=True)

# LFCS brand colours
LFCS_NAVY = HexColor("#0E2F4D")
LFCS_RED = HexColor("#C8102E")
LFCS_GREY = HexColor("#5A6772")
LFCS_LIGHT = HexColor("#F2F4F7")

NAVY_HEX = "0E2F4D"
RED_HEX = "C8102E"
LIGHT_HEX = "F2F4F7"

LFCS_HEADER = "LF CONSTRUCTION SERVICES PTY LTD"
LFCS_FOOTER = "LF Construction Services Pty Ltd  |  ABN 33 656 781 459  |  info@lfcs.com.au"

JOB_NO = "2629"
JOB_TITLE = "Erskine Park WWTP Slab"
HEAD_CONTRACTOR = "TCE Contracting"
HEAD_CONTRACTOR_CONTACT = "Kirolos Barsom"
HEAD_CONTRACTOR_EMAIL = "kbarsom@tcecontracting.com.au"
END_CLIENT = "Cleanaway Pty Ltd"
SITE = "4 Quarry Road, Erskine Park NSW"
DESIGNER = "Henry & Hymas (Sydney)"
BID_DATE = "2026-05-07"
PREPARED_BY = "claude-code-coo (LFCS internal AI estimator) — A/B parallel build with Cowork v-CW"


# ─────────────────────────────────────────────────────────────────────────────
# Take-off data (PROVISIONAL — marked * for AfC confirm)
# Source: vision-pass Drawing-Index findings (S2.00 + S2.01 + S2.05 + S3.00 + C103)
# ─────────────────────────────────────────────────────────────────────────────

# Quantity assumptions documented in Pre-Pricing-Grill-2026-05-04.md
# All quantities flagged * — Rocky to confirm Tue 6 May AM after annotating drawings
SLAB_AREA_M2 = 300.0          # Pentagonal slab field area (vision-derived approximation)
SLAB_THICK_M = 0.27
SLAB_EDGE_THICK_M = 0.50
SLAB_EDGE_PERIMETER_M = 50.0  # Approximate around irregular pentagon
SLAB_REO_DENSITY_KG_M3 = 130  # Heavy density per S2.01 vision (5N24/8N20 banded zones + N16-200 transverse layers)

NUM_BEAMS = 7
BEAM_AVG_LENGTH_M = 12.0
BEAM_W_M = 0.5
BEAM_D_M = 1.2
BEAM_DOWNSTAND_M = BEAM_D_M - SLAB_THICK_M  # 0.93m
BEAM_REO_DENSITY = 130

NUM_PLINTHS = 7
PLINTH_AREA_M2 = 2.25  # ~1.5m × 1.5m typical
PLINTH_THICK_M = 0.10

WALKWAY_AREA_M2 = 55.0  # Vision-derived from 12 setout points (1m wide × ~55m centreline)
WALKWAY_THICK_M = 0.12
WALKWAY_EDGE_THICKEN_M3 = 0.5  # Allowance at terminus where steel walkway connects

PF1_COUNT = 5
PF1_VOL_EACH = 1.0 * 0.6 * 0.4  # 0.24 m³
PF1_REO_KG_EACH = 56  # 5-N16 U-bars T+B + N12-200 ties

BW1_PERIMETER_M = 32.0
BW1_AVG_HEIGHT_M = 1.5
BW1_AREA_M2 = BW1_PERIMETER_M * BW1_AVG_HEIGHT_M  # 48 m²
BW1_REO_KG = 400  # N16-400 EW threaded through cores ~ 0.4 T

# ─────────────────────────────────────────────────────────────────────────────
# Take-off elements (line items)
# ─────────────────────────────────────────────────────────────────────────────

def calc_concrete_slab():
    return SLAB_AREA_M2 * SLAB_THICK_M + (SLAB_EDGE_PERIMETER_M * (SLAB_EDGE_THICK_M - SLAB_THICK_M) * 0.50)
    # 300 × 0.27 + (50 × 0.23 × 0.50) = 81 + 5.75 ≈ 86.75 m³

def calc_concrete_beams():
    gross = NUM_BEAMS * BEAM_W_M * BEAM_D_M * BEAM_AVG_LENGTH_M
    deduct_slab_above = NUM_BEAMS * BEAM_W_M * SLAB_THICK_M * BEAM_AVG_LENGTH_M
    return gross - deduct_slab_above
    # 50.4 - 11.3 = 39.1 m³

def calc_form_slab_area():
    # 50% suspended (over piles) needs soffit form; 50% on natural ground (vapour barrier) eliminates soffit
    # Edge form: perimeter × edge depth
    soffit = SLAB_AREA_M2 * 0.50
    edge_form = SLAB_EDGE_PERIMETER_M * SLAB_EDGE_THICK_M
    return soffit + edge_form  # 150 + 25 = 175

def calc_form_beams():
    # Beam down-stand × 2 sides
    return NUM_BEAMS * BEAM_AVG_LENGTH_M * BEAM_DOWNSTAND_M * 2  # 7×12×0.93×2 = 156.24

# Compute totals
slab_concrete = calc_concrete_slab()       # 86.75 m³
beam_concrete = calc_concrete_beams()       # 39.1 m³
plinth_concrete = NUM_PLINTHS * PLINTH_AREA_M2 * PLINTH_THICK_M  # 1.575 m³
walkway_concrete = WALKWAY_AREA_M2 * WALKWAY_THICK_M + WALKWAY_EDGE_THICKEN_M3  # 6.6 + 0.5 = 7.1 m³
pf1_concrete = PF1_COUNT * PF1_VOL_EACH    # 1.2 m³

total_concrete_m3 = slab_concrete + beam_concrete + plinth_concrete + walkway_concrete + pf1_concrete

slab_reo_T = (slab_concrete * SLAB_REO_DENSITY_KG_M3) / 1000.0   # ~11.3
beam_reo_T = (beam_concrete * BEAM_REO_DENSITY) / 1000.0          # ~5.1
pf1_reo_T = (PF1_COUNT * PF1_REO_KG_EACH) / 1000.0                # 0.28
bw1_reo_T = BW1_REO_KG / 1000.0                                   # 0.4
total_reo_T = slab_reo_T + beam_reo_T + pf1_reo_T + bw1_reo_T     # ~17.1

mesh_walkway_m2 = WALKWAY_AREA_M2 * 1.0  # SL72, 1× area
mesh_plinth_m2 = NUM_PLINTHS * PLINTH_AREA_M2  # SL92
total_mesh_m2 = mesh_walkway_m2 + mesh_plinth_m2  # 71

form_slab_m2 = calc_form_slab_area()              # 175
form_beam_m2 = calc_form_beams()                  # 156
form_plinth_m2 = NUM_PLINTHS * 4 * 1.5 * PLINTH_THICK_M  # 4 sides × 1.5m × 100mm × 7 = 4.2
form_walkway_m2 = WALKWAY_AREA_M2 / 55.0 * 55.0 * WALKWAY_THICK_M * 2  # ≈ 13.2
form_pf1_m2 = PF1_COUNT * (1.0 + 0.6) * 2 * 0.4   # = 6.4
total_form_m2 = form_slab_m2 + form_beam_m2 + form_plinth_m2 + form_walkway_m2 + form_pf1_m2

bw1_block_m2 = BW1_AREA_M2  # 48

# ─────────────────────────────────────────────────────────────────────────────
# Productivity + rate basis (per Pre-Pricing-Grill)
# ─────────────────────────────────────────────────────────────────────────────

TRADE_DAY_RATE = 85.00     # Rate Card V1.7 day shift trade
SUP_DAY_RATE = 95.00       # Rate Card V1.7 day shift supervisor
MARGIN_REDUNDANCY = 0.15
GST = 0.10
MAT_MARKUP = 0.10

PROD = {
    "concrete_pour":   {"hr_per_unit": 1.45, "unit": "m³",  "note": "4-person crew × 22 m³/day, ~10% slowed for irregular pentagon + plinth congestion"},
    "reo_install":     {"hr_per_unit": 9.4,  "unit": "T",   "note": "Heavy density 130-180 kg/m³, cogged edges, 5N24/8N20 banded zones, beam ties (S2.01 vision)"},
    "mesh_fix":        {"hr_per_unit": 0.27, "unit": "m²",  "note": "SL72 + SL92 mesh — standard fixing rate"},
    "form_erect":      {"hr_per_unit": 2.0,  "unit": "m²",  "note": "Suspended slab + beam down-stand + edge thickening — typical complexity"},
    "form_strip":      {"hr_per_unit": 1.0,  "unit": "m²",  "note": "Strip + clean + stack"},
    "bw1_blocklay":    {"hr_per_unit": 1.5,  "unit": "m²",  "note": "190 RC block + reo threading + core grout combined (1 blocklayer + 0.5 labourer)"},
    "curing":          {"hr_per_unit": 0.01, "unit": "m²",  "note": "AS3799 compound — application + protection"},
}

# Activity hours table (used in both Rate Buildup Labour sheet AND Take-Off cross-check)
ACTIVITIES = [
    ("Concrete pour + place + finish — main slab + beams (40 MPa)",       slab_concrete + beam_concrete, "m³",  PROD["concrete_pour"]["hr_per_unit"], "trade"),
    ("Concrete pour — plinths × 7 (40 MPa)",                                plinth_concrete,                "m³",  PROD["concrete_pour"]["hr_per_unit"], "trade"),
    ("Concrete pour — walkway pavement (25 MPa)",                            walkway_concrete,              "m³",  PROD["concrete_pour"]["hr_per_unit"], "trade"),
    ("Concrete pour — PF1 pad footings × 5 (32 MPa)",                        pf1_concrete,                   "m³",  PROD["concrete_pour"]["hr_per_unit"], "trade"),
    ("Reo install — slab + beams (heavy density)",                           slab_reo_T + beam_reo_T,        "T",   PROD["reo_install"]["hr_per_unit"],  "trade"),
    ("Reo install — PF1 pad footings (5-N16 U-bars + N12-200 ties)",         pf1_reo_T,                      "T",   PROD["reo_install"]["hr_per_unit"],  "trade"),
    ("Reo install — BW1 (N16-400 EW central, threaded through cores)",       bw1_reo_T,                      "T",   PROD["reo_install"]["hr_per_unit"],  "trade"),
    ("Mesh fix — walkway SL72 central + plinths SL92 top",                   total_mesh_m2,                  "m²",  PROD["mesh_fix"]["hr_per_unit"],     "trade"),
    ("Formwork supply + erect — slab soffit (50%) + edge thickening (500mm)", form_slab_m2,                  "m²",  PROD["form_erect"]["hr_per_unit"],   "trade"),
    ("Formwork supply + erect — beam down-stands (500×1200 × 7 ea)",         form_beam_m2,                   "m²",  PROD["form_erect"]["hr_per_unit"],   "trade"),
    ("Formwork supply + erect — plinths + walkway edges + PF1",              form_plinth_m2 + form_walkway_m2 + form_pf1_m2, "m²", PROD["form_erect"]["hr_per_unit"], "trade"),
    ("Formwork strip + clean + stack — all elements",                        total_form_m2,                  "m²",  PROD["form_strip"]["hr_per_unit"],   "trade"),
    ("BW1 block lay — 190 RC block + reo threading + core grouting (combined)", bw1_block_m2,                "m²",  PROD["bw1_blocklay"]["hr_per_unit"], "trade"),
    ("Curing application — AS3799 compound + protection (slab + walkway)",   SLAB_AREA_M2 + WALKWAY_AREA_M2,"m²",  PROD["curing"]["hr_per_unit"],       "trade"),
]

# Trade hours total
trade_hrs = sum(qty * hr for (_, qty, _, hr, role) in ACTIVITIES if role == "trade")
# Supervisor: ~30% of trade hours
sup_hrs = round(trade_hrs * 0.30, 1)

# Labour cost
trade_cost = trade_hrs * TRADE_DAY_RATE
sup_cost = sup_hrs * SUP_DAY_RATE
labour_total = trade_cost + sup_cost

# Materials (LFCS supply only)
form_ply_cost = total_form_m2 * 25.0
form_ply_marked = form_ply_cost * (1 + MAT_MARKUP)
curing_cost = (SLAB_AREA_M2 + WALKWAY_AREA_M2) * 4.0
curing_marked = curing_cost * (1 + MAT_MARKUP)
consumables = 1500.0
consumables_marked = consumables * (1 + MAT_MARKUP)
ute_weeks = 8
ute_cost = ute_weeks * 5 * 150.0  # no markup on ute & tools per Rate Card
materials_total = form_ply_marked + curing_marked + consumables_marked + ute_cost

subtotal = labour_total + materials_total
redundancy = subtotal * MARGIN_REDUNDANCY
ex_gst = subtotal + redundancy
gst = ex_gst * GST
inc_gst = ex_gst + gst

# Round tender total to nearest $50
ex_gst_rounded = round(ex_gst / 50) * 50
gst_rounded = ex_gst_rounded * GST
inc_gst_rounded = ex_gst_rounded + gst_rounded


# ─────────────────────────────────────────────────────────────────────────────
# Per-element line items for Tender Summary Lump Sum sheet
# Calc Basis column shows the rate derivation per the 2627 lesson (Huss method)
# ─────────────────────────────────────────────────────────────────────────────

LUMP_SUM_LINES = [
    {
        "no": 1,
        "desc": "WWTP main slab — 270mm RC suspended slab @ 40 MPa, irregular pentagon (~300 m² × *), 500mm edge thickening, ~7 integrated 500×1200 RC beams, plinth typicals × 7 (100thk @ 40 MPa with SL92 top mesh). LFCS labour: formwork supply/erect/strip + reo install + concrete pour/place/finish + curing.",
        "qty": round(slab_concrete + beam_concrete + plinth_concrete, 1),
        "unit": "m³ concrete",
        "calc_basis": (
            f"Conc {round(slab_concrete + beam_concrete + plinth_concrete, 1)} m³ × 1.45 hr/m³ × $85 = ${round((slab_concrete + beam_concrete + plinth_concrete) * 1.45 * 85):,}; "
            f"+ reo {round(slab_reo_T + beam_reo_T, 2)} T × 9.4 hr/T × $85 = ${round((slab_reo_T + beam_reo_T) * 9.4 * 85):,}; "
            f"+ form {round(form_slab_m2 + form_beam_m2 + form_plinth_m2, 0)} m² (E+S = 3 hr/m²) × $85 = ${round((form_slab_m2 + form_beam_m2 + form_plinth_m2) * 3 * 85):,}; "
            f"+ supervisor 30% loading + materials ($25/m² ply + curing) + 15% redundancy."
        ),
    },
    {
        "no": 2,
        "desc": "Pedestrian walkway concrete pavement — 1m wide × ~55m × 120mm thick @ 25 MPa per civil C103, SL72 mesh central, on 100mm DGB20 base by-others, plus edge thickening at steel walkway terminus. LFCS labour: formwork edge + mesh fix + concrete pour/place/finish + broom finish + curing.",
        "qty": round(walkway_concrete, 1),
        "unit": "m³ concrete (~55 m² pavement)",
        "calc_basis": (
            f"Conc {round(walkway_concrete, 1)} m³ × 1.45 hr/m³ × $85 = ${round(walkway_concrete * 1.45 * 85):,}; "
            f"+ mesh fix {round(mesh_walkway_m2, 0)} m² × 0.27 hr/m² × $85 = ${round(mesh_walkway_m2 * 0.27 * 85):,}; "
            f"+ form {round(form_walkway_m2, 1)} m² × 3 hr/m² × $85 = ${round(form_walkway_m2 * 3 * 85):,}; "
            f"+ supervisor 30% + materials + 15% redundancy."
        ),
    },
    {
        "no": 3,
        "desc": "Pedestrian walkway pad footings PF1 × 5 — 1000 (L) × 600 (W) × 400 (D) @ 32 MPa, 5-N16 U-bars top + bottom + N12-200 ties, bearing 150 kPa natural ground. LFCS labour: form + reo + pour. Steel walkway columns/anchors retro-fitted by steel installer.",
        "qty": PF1_COUNT,
        "unit": "ea pad footings",
        "calc_basis": (
            f"Conc {round(pf1_concrete, 2)} m³ × 1.45 × $85 = ${round(pf1_concrete * 1.45 * 85):,}; "
            f"+ reo {round(pf1_reo_T, 2)} T × 9.4 × $85 = ${round(pf1_reo_T * 9.4 * 85):,}; "
            f"+ form {round(form_pf1_m2, 1)} m² × 3 × $85 = ${round(form_pf1_m2 * 3 * 85):,}; "
            f"+ supervisor 30% + materials + 15% redundancy."
        ),
    },
    {
        "no": 4,
        "desc": "BW1 bund wall — 190 RC block (1800mm max H, ~32m perimeter on 3 slab edges, ~48 m² × *), N16-400 EW central reo threaded through cores, all cores filled @ f'c=32 MPa. LFCS labour: blocklay + reo install + core grout placement. Block + reo + grout supply by TCE.",
        "qty": round(bw1_block_m2, 0),
        "unit": "m² blockwork",
        "calc_basis": (
            f"Blocklay {round(bw1_block_m2, 0)} m² × 1.5 hr/m² (combined: 1 blocklayer + 0.5 labourer) × $85 = ${round(bw1_block_m2 * 1.5 * 85):,}; "
            f"+ supervisor 30% + 15% redundancy. Block / grout / reo materials = TCE supply."
        ),
    },
]

LINE_5_AGGREGATE = {
    "no": 5,
    "desc": "Project-wide site supervision — full-time supervisor on pour + finishing days; part-time on form/reo/block days. Per Rate Card V1.7 supervisor loading. Programme provisional 6-8 weeks on-site.",
    "qty": round(sup_hrs, 0),
    "unit": "hrs",
    "calc_basis": f"30% of total trade hrs ({round(trade_hrs, 0)} hrs) = {round(sup_hrs, 0)} hrs × $95/hr = ${round(sup_hrs * 95):,}",
}

LINE_6_AGGREGATE = {
    "no": 6,
    "desc": "LFCS-supply materials + plant + ute & tools — formwork ply (consumable), AS3799 curing compound, tie wire / bar chairs / small consumables, 1× ute @ $150/day for 8-week programme.",
    "qty": 1,
    "unit": "LS",
    "calc_basis": (
        f"Form ply {round(total_form_m2, 0)} m² × $25 + 10% = ${round(form_ply_marked):,}; "
        f"curing {round(SLAB_AREA_M2 + WALKWAY_AREA_M2, 0)} m² × $4 + 10% = ${round(curing_marked):,}; "
        f"consumables $1,500 + 10% = ${round(consumables_marked):,}; "
        f"ute 8 weeks × 5 × $150 = ${round(ute_cost):,}."
    ),
}


# ─────────────────────────────────────────────────────────────────────────────
# Common workbook styling helpers
# ─────────────────────────────────────────────────────────────────────────────

THIN = Side(border_style="thin", color="999999")
MED = Side(border_style="medium", color="0E2F4D")

def style_header(ws, row, col_count):
    for c in range(1, col_count + 1):
        cell = ws.cell(row=row, column=c)
        cell.font = Font(name="Arial", size=10, bold=True, color="FFFFFF")
        cell.fill = PatternFill("solid", fgColor=NAVY_HEX)
        cell.alignment = Alignment(horizontal="left", vertical="center", wrap_text=True)
        cell.border = Border(top=MED, bottom=MED, left=THIN, right=THIN)

def style_data(ws, start_row, end_row, col_count, money_cols=None):
    money_cols = money_cols or []
    for r in range(start_row, end_row + 1):
        for c in range(1, col_count + 1):
            cell = ws.cell(row=r, column=c)
            cell.font = Font(name="Arial", size=9)
            cell.alignment = Alignment(horizontal="left", vertical="center", wrap_text=True)
            cell.border = Border(top=THIN, bottom=THIN, left=THIN, right=THIN)
            if c in money_cols:
                cell.alignment = Alignment(horizontal="right", vertical="center")
                cell.number_format = '"$"#,##0.00'

def style_total_row(ws, row, col_count):
    for c in range(1, col_count + 1):
        cell = ws.cell(row=row, column=c)
        cell.font = Font(name="Arial", size=10, bold=True)
        cell.fill = PatternFill("solid", fgColor=LIGHT_HEX)
        cell.border = Border(top=MED, bottom=MED, left=THIN, right=THIN)

def set_widths(ws, widths):
    for i, w in enumerate(widths, start=1):
        ws.column_dimensions[get_column_letter(i)].width = w


# ─────────────────────────────────────────────────────────────────────────────
# 1. Take-Off xlsx
# ─────────────────────────────────────────────────────────────────────────────

def build_takeoff():
    wb = Workbook()
    wb.remove(wb.active)

    # Cover
    ws = wb.create_sheet("Cover")
    ws["A1"] = LFCS_HEADER
    ws["A1"].font = Font(name="Arial", size=16, bold=True, color=NAVY_HEX)
    ws["A2"] = "Quantity Take-Off"
    ws["A2"].font = Font(name="Arial", size=14, bold=True, color=RED_HEX)
    ws["A4"] = f"Job: {JOB_NO} — {JOB_TITLE}"
    ws["A5"] = f"Head Contractor: {HEAD_CONTRACTOR} ({HEAD_CONTRACTOR_CONTACT}, {HEAD_CONTRACTOR_EMAIL})"
    ws["A6"] = f"End Client: {END_CLIENT}"
    ws["A7"] = f"Site: {SITE}"
    ws["A8"] = f"Designer: {DESIGNER}"
    ws["A9"] = f"Drawings: 250643 Civil Rev 5 (17.04.2026) + 250643 Structural Rev 1 (15.04.2026)"
    ws["A10"] = "Drawing status: ISSUED FOR 90% DESIGN — NOT FOR CONSTRUCTION"
    ws["A11"] = f"Bid due: {BID_DATE} (Thu PM)"
    ws["A12"] = f"Prepared by: {PREPARED_BY}"
    ws["A13"] = "Document version: v-CC (claude-code-coo) — A/B parallel with Cowork v-CW"
    ws["A14"] = ""
    ws["A15"] = "Source — quantities derived from:"
    ws["A16"] = "  • Vision-pass Drawing-Index (S2.00 + S2.01 + S2.05 + S3.00 + C103) Confidence=High"
    ws["A17"] = "  • Pre-Pricing-Grill-2026-05-04.md (Phase 0 grill, drafted async pending Rocky Tue 6 May AM verbal sign-off)"
    ws["A18"] = "  • TCE email Kirolos Barsom 2026-05-04 PM (scope: slab + walkway + bund wall)"
    ws["A19"] = "  • Eastern Creek scope-split pattern (Rocky verbal 2026-05-04 PM): blue + green corner + purple IN; pink + ALL tanks (incl purple tank) OUT"
    ws["A20"] = ""
    ws["A21"] = "* asterisk on quantity column indicates AfC-confirm pending — 90% Design drift to 100% IFC = ±5% qty variance = variation per Inclusions-Exclusions Q1"
    for r in range(4, 22):
        ws.cell(row=r, column=1).font = Font(name="Arial", size=10)
    set_widths(ws, [120])

    # Summary sheet
    ws = wb.create_sheet("Summary")
    ws["A1"] = "Take-Off Summary — LFCS Scope (per element)"
    ws["A1"].font = Font(name="Arial", size=12, bold=True, color=NAVY_HEX)
    headers = ["Element", "Concrete (m³)", "Reo install (T)", "Mesh fix (m²)", "Formwork erect (m²)", "Strip (m²)", "Block lay (m²)"]
    for i, h in enumerate(headers, start=1):
        ws.cell(row=3, column=i, value=h)
    style_header(ws, 3, len(headers))

    rows = [
        ("WWTP main slab field (270mm @ 40 MPa) + edge thickening (500mm)", round(slab_concrete, 1), round(slab_reo_T, 2), 0, round(form_slab_m2, 0), round(form_slab_m2, 0), 0),
        ("Integrated 500×1200 RC beams × 7 (40 MPa, ~12m avg L)",            round(beam_concrete, 1), round(beam_reo_T, 2),  0, round(form_beam_m2, 0), round(form_beam_m2, 0), 0),
        ("Plinths × 7 (typical, 100thk @ 40 MPa, SL92 top mesh)",            round(plinth_concrete, 2), 0,                  round(mesh_plinth_m2, 0), round(form_plinth_m2, 1), round(form_plinth_m2, 1), 0),
        ("Walkway pavement (1m × ~55m × 120mm @ 25 MPa, SL72 central)",      round(walkway_concrete, 1), 0,                 round(mesh_walkway_m2, 0), round(form_walkway_m2, 1), round(form_walkway_m2, 1), 0),
        ("PF1 pad footings × 5 (1000×600×400 @ 32 MPa, 5-N16 U + N12-200)",  round(pf1_concrete, 2), round(pf1_reo_T, 2),    0, round(form_pf1_m2, 1), round(form_pf1_m2, 1), 0),
        ("BW1 bund wall (190 RC block × ~48 m², N16-400 EW core, 32 MPa)",   0, round(bw1_reo_T, 2),                        0, 0, 0, round(bw1_block_m2, 0)),
    ]
    for i, row in enumerate(rows, start=4):
        for j, val in enumerate(row, start=1):
            ws.cell(row=i, column=j, value=val)
    style_data(ws, 4, 4 + len(rows) - 1, len(headers))

    # Total row
    total_r = 4 + len(rows)
    ws.cell(row=total_r, column=1, value="TOTAL LFCS SCOPE *")
    for j in range(2, len(headers) + 1):
        col = get_column_letter(j)
        ws.cell(row=total_r, column=j, value=f"=SUM({col}4:{col}{total_r - 1})")
    style_total_row(ws, total_r, len(headers))
    ws.cell(row=total_r + 2, column=1, value="* All quantities provisional from 90% Design vision-pass; Rocky to confirm Tue 6 May AM after annotating drawings (Eastern Creek scope split: blue + green corner + purple IN; pink + ALL tanks OUT). 90% → 100% IFC drift handled per Inclusions-Exclusions Q1 variation clause.")
    ws.cell(row=total_r + 2, column=1).alignment = Alignment(wrap_text=True)
    ws.merge_cells(start_row=total_r + 2, start_column=1, end_row=total_r + 2, end_column=len(headers))
    set_widths(ws, [62, 14, 14, 14, 18, 14, 14])

    # Slab + beams detail
    ws = wb.create_sheet("Slab + beams")
    ws["A1"] = "WWTP Slab + Integrated 500×1200 RC Beams — Detailed Take-Off"
    ws["A1"].font = Font(name="Arial", size=12, bold=True, color=NAVY_HEX)
    headers = ["Item", "Source drawing", "Qty *", "Unit", "Calc basis"]
    for i, h in enumerate(headers, start=1):
        ws.cell(row=3, column=i, value=h)
    style_header(ws, 3, len(headers))
    slab_rows = [
        ("Slab plan area (irregular pentagon)", "S2.00 vision", SLAB_AREA_M2, "m²", "Approximation from S2.00 plan view at 1:100 — pentagonal with diagonal SW edge, ~7 RC beam grid. Take measurement off-drawing for Tue grill confirm."),
        ("Slab field thickness", "S2.00 note 1 + S2.05 Sec 1", SLAB_THICK_M * 1000, "mm", "270 MIN. SUSPENDED RC SLAB U.N.O. (verbatim slab note 1)"),
        ("Slab field concrete volume", "calc", round(SLAB_AREA_M2 * SLAB_THICK_M, 1), "m³", f"{SLAB_AREA_M2} m² × {SLAB_THICK_M}m = {round(SLAB_AREA_M2 * SLAB_THICK_M, 1)} m³"),
        ("Slab edge thickening volume", "S2.05 sections — 500mm depth at edges", round(SLAB_EDGE_PERIMETER_M * (SLAB_EDGE_THICK_M - SLAB_THICK_M) * 0.5, 2), "m³", f"~{SLAB_EDGE_PERIMETER_M}m perimeter × ({SLAB_EDGE_THICK_M}-{SLAB_THICK_M})m × 0.5 averaging = {round(SLAB_EDGE_PERIMETER_M * (SLAB_EDGE_THICK_M - SLAB_THICK_M) * 0.5, 2)} m³"),
        ("Beam count", "S2.00 plan", NUM_BEAMS, "ea", "Visible RC beams 500×1200 BW1 running across slab (N-S orientation predominantly) on S2.00 plan view"),
        ("Beam avg length", "S2.00 plan estimate", BEAM_AVG_LENGTH_M, "m", "* Approximated from plan ratio at 1:100 — Rocky/take-off team to measure"),
        ("Beam gross volume (incl slab portion above)", "calc", round(NUM_BEAMS * BEAM_W_M * BEAM_D_M * BEAM_AVG_LENGTH_M, 1), "m³", f"{NUM_BEAMS} × {BEAM_W_M}m × {BEAM_D_M}m × {BEAM_AVG_LENGTH_M}m = {round(NUM_BEAMS * BEAM_W_M * BEAM_D_M * BEAM_AVG_LENGTH_M, 1)} m³"),
        ("Less: slab volume above beam (deduction)", "calc", round(NUM_BEAMS * BEAM_W_M * SLAB_THICK_M * BEAM_AVG_LENGTH_M, 1), "m³", f"{NUM_BEAMS} × {BEAM_W_M}m × {SLAB_THICK_M}m × {BEAM_AVG_LENGTH_M}m = {round(NUM_BEAMS * BEAM_W_M * SLAB_THICK_M * BEAM_AVG_LENGTH_M, 1)} m³ (avoid double-count)"),
        ("Beam net (down-stand) volume", "calc", round(beam_concrete, 1), "m³", f"Gross − slab portion = {round(beam_concrete, 1)} m³"),
        ("Slab + beams reo density", "S2.01 vision-derived", SLAB_REO_DENSITY_KG_M3, "kg/m³", "Heavy density per S2.01 vision: N12-300 EW T+B baseline + N16-200 transverse Layer 2+3 + 5N24/8N20/9N24/10N20/10N24 banded zones at corners + plinth surrounds + 2N12-300 beam ties"),
        ("Slab + beams total reo install", "calc", round(slab_reo_T + beam_reo_T, 2), "T", f"{round(slab_concrete + beam_concrete, 1)} m³ × {SLAB_REO_DENSITY_KG_M3} kg/m³ = {round((slab_concrete + beam_concrete) * SLAB_REO_DENSITY_KG_M3 / 1000, 2)} T"),
        ("Slab soffit formwork (50% suspended)", "S2.05 Sec 1 vision-only find — mixed support", round(SLAB_AREA_M2 * 0.5, 0), "m²", "Section 1 shows MIXED slab support: half on conventional formwork over piles, half on lightly compacted natural ground with vapour barrier. 50% soffit form factor applied. Confirm at grill."),
        ("Slab edge formwork", "S2.05 sections", round(SLAB_EDGE_PERIMETER_M * SLAB_EDGE_THICK_M, 0), "m²", f"{SLAB_EDGE_PERIMETER_M}m perimeter × {SLAB_EDGE_THICK_M}m edge depth"),
        ("Beam side formwork (down-stand × 2 sides)", "calc", round(form_beam_m2, 0), "m²", f"{NUM_BEAMS} × {BEAM_AVG_LENGTH_M}m × {BEAM_DOWNSTAND_M}m × 2 sides = {round(form_beam_m2, 1)} m²"),
        ("Plinths × 7 — concrete", "S2.00 plinth typical + S2.05 Sec 1", round(plinth_concrete, 2), "m³", f"{NUM_PLINTHS} × {PLINTH_AREA_M2} m² × {PLINTH_THICK_M}m = {round(plinth_concrete, 2)} m³"),
        ("Plinths × 7 — SL92 top mesh", "S2.05 Sec 1", round(mesh_plinth_m2, 0), "m²", "SL92 top mesh per S2.05 plinth typical callout"),
        ("Plinths × 7 — edge formwork", "calc", round(form_plinth_m2, 1), "m²", f"{NUM_PLINTHS} × 4-side perimeter × {PLINTH_THICK_M}m height (~6m perimeter × 0.1m × 7 = 4.2 m²)"),
    ]
    for i, row in enumerate(slab_rows, start=4):
        for j, val in enumerate(row, start=1):
            ws.cell(row=i, column=j, value=val)
    style_data(ws, 4, 4 + len(slab_rows) - 1, len(headers))
    set_widths(ws, [50, 28, 12, 10, 80])

    # Walkway + PF1
    ws = wb.create_sheet("Walkway + PF1")
    ws["A1"] = "Walkway Pavement (C103) + PF1 Pad Footings (S3.00) — Detailed Take-Off"
    ws["A1"].font = Font(name="Arial", size=12, bold=True, color=NAVY_HEX)
    for i, h in enumerate(headers, start=1):
        ws.cell(row=3, column=i, value=h)
    style_header(ws, 3, len(headers))
    walk_rows = [
        ("Walkway pavement spec", "C103 PAVEMENT TYPE legend (vision)", "120mm × 25 MPa + SL72 + 100mm DGB20 BY OTHERS", "n/a", "120mm THICK 25 MPa CONCRETE WITH SL72 CENTRAL on 100mm DGB20 (or approved equiv) on COMPACTED SUBGRAD[E] — DGB20 + subgrade BY OTHERS earthworks"),
        ("Walkway concrete plan area", "C103 setout points 5-12", round(WALKWAY_AREA_M2, 1), "m²", "Derived from 12 setout coords: ~55m centreline × 1m wide + transitions"),
        ("Walkway concrete volume", "calc", round(WALKWAY_AREA_M2 * WALKWAY_THICK_M, 2), "m³", f"{WALKWAY_AREA_M2} m² × {WALKWAY_THICK_M}m = {round(WALKWAY_AREA_M2 * WALKWAY_THICK_M, 2)} m³"),
        ("Walkway edge thickening @ steel-walkway terminus", "S3.00 right-end detail", WALKWAY_EDGE_THICKEN_M3, "m³", "Allowance — 12 END PL + 2-M20 Hilti HAS into edge thickening per S3.00 (steel installer fixes anchors retro)"),
        ("Walkway TOTAL concrete", "calc", round(walkway_concrete, 2), "m³", "Pavement + edge thickening"),
        ("Walkway SL72 mesh", "C103", round(mesh_walkway_m2, 0), "m²", f"{round(WALKWAY_AREA_M2, 0)} m² (1× area, no overlap factor)"),
        ("Walkway edge formwork (both sides)", "calc", round(form_walkway_m2, 1), "m²", f"~{WALKWAY_AREA_M2}m × {WALKWAY_THICK_M}m × 2 edges = {round(form_walkway_m2, 1)} m² (small)"),
        ("PF1 pad footings — count", "S3.00 framing plan + Section 1", PF1_COUNT, "ea", "Vision count: 4-5 PF1 (U) labels visible on framing plan; provisional 5 ea"),
        ("PF1 each — concrete vol", "S3.00 PF1 detail", PF1_VOL_EACH, "m³ ea", f"1.0 × 0.6 × 0.4 = {PF1_VOL_EACH} m³ per footing"),
        ("PF1 total concrete", "calc", round(pf1_concrete, 2), "m³", f"{PF1_COUNT} × {PF1_VOL_EACH} = {round(pf1_concrete, 2)} m³ @ 32 MPa"),
        ("PF1 reo per footing", "S3.00 PF1 detail", PF1_REO_KG_EACH, "kg ea", "5-N16 U-bars top + bottom + N12-200 ties per footing"),
        ("PF1 total reo install", "calc", round(pf1_reo_T, 3), "T", f"{PF1_COUNT} × {PF1_REO_KG_EACH} kg = {PF1_COUNT * PF1_REO_KG_EACH} kg"),
        ("PF1 formwork (4-side perimeter × 400 deep)", "calc", round(form_pf1_m2, 1), "m²", f"{PF1_COUNT} × (1.0+0.6) × 2 × 0.4 = {round(form_pf1_m2, 1)} m²"),
    ]
    for i, row in enumerate(walk_rows, start=4):
        for j, val in enumerate(row, start=1):
            ws.cell(row=i, column=j, value=val)
    style_data(ws, 4, 4 + len(walk_rows) - 1, len(headers))
    set_widths(ws, [50, 28, 18, 10, 80])

    # BW1
    ws = wb.create_sheet("BW1 bund wall")
    ws["A1"] = "BW1 — 190 RC Block Bund Wall — Detailed Take-Off"
    ws["A1"].font = Font(name="Arial", size=12, bold=True, color=NAVY_HEX)
    for i, h in enumerate(headers, start=1):
        ws.cell(row=3, column=i, value=h)
    style_header(ws, 3, len(headers))
    bw1_rows = [
        ("BW1 spec", "S2.00 slab note 5", "190 RC block, 1800mm max H, all cores filled @ 32 MPa, N16-400 EW central", "n/a", "Verbatim: '5. BW1 DENOTES 190 RC BLOCK BUND WALL, 1800mm MAX. HEIGHT. ALL CORES FILLED FULL HEIGHT, f'c = 32 MPa. REINFORCED WITH N16-400 EACH WAY CENTRAL'"),
        ("BW1 perimeter on slab", "S2.00 vision", round(BW1_PERIMETER_M, 0), "m", "BW1 wraps 3 slab edges (left vertical + lower-left diagonal + bottom horizontal) per S2.00 vision pass — NOT all 4 sides"),
        ("BW1 average height", "S2.00 + S2.05 Sec 1+3", round(BW1_AVG_HEIGHT_M, 1), "m", "* Average — max 1800mm per slab note 5; avg assumed 1500mm pending Rocky measure on plan"),
        ("BW1 wall area", "calc", round(BW1_AREA_M2, 0), "m²", f"{BW1_PERIMETER_M}m × {BW1_AVG_HEIGHT_M}m = {round(BW1_AREA_M2, 0)} m²"),
        ("BW1 reo install (N16-400 EW central)", "S2.00 note 5", round(bw1_reo_T, 2), "T", f"~{BW1_REO_KG} kg horiz + vert N16 threaded through cores"),
        ("BW1 starter bars from slab", "S2.05 Sec 1+3 — N16-400 starters", "(included in slab reo)", "n/a", "N16-400 starters cast into slab top during main slab pour — already counted in slab reo line above"),
        ("BW1 core fill grout (32 MPa)", "calc", round(BW1_AREA_M2 * 0.190 * 0.5, 1), "m³", f"{round(BW1_AREA_M2, 0)} m² × 0.190m thk × 0.50 fill ratio (cores not solid; ~50% is reo + air voids by code) = {round(BW1_AREA_M2 * 0.190 * 0.5, 1)} m³ — TCE supply per supply boundary table"),
        ("BW1 LFCS labour scope", "supply boundaries", "Block lay + reo install + core grout placement", "labour", "All block + reo + grout SUPPLY by TCE per FRP-install pattern (same as 2602 Munmorah supply chain). LFCS does install + place only."),
    ]
    for i, row in enumerate(bw1_rows, start=4):
        for j, val in enumerate(row, start=1):
            ws.cell(row=i, column=j, value=val)
    style_data(ws, 4, 4 + len(bw1_rows) - 1, len(headers))
    set_widths(ws, [50, 28, 18, 12, 80])

    # Save
    out = OUT_02B / f"{JOB_NO}-Take-Off_v-CC.xlsx"
    wb.save(out)
    print(f"✓ {out.name}")
    return out


# ─────────────────────────────────────────────────────────────────────────────
# 2. Rate-Buildup xlsx
# ─────────────────────────────────────────────────────────────────────────────

def build_rate_buildup():
    wb = Workbook()
    wb.remove(wb.active)

    # Cover
    ws = wb.create_sheet("Cover")
    ws["A1"] = LFCS_HEADER
    ws["A1"].font = Font(name="Arial", size=16, bold=True, color=NAVY_HEX)
    ws["A2"] = "Rate Buildup — Internal QS"
    ws["A2"].font = Font(name="Arial", size=14, bold=True, color=RED_HEX)
    ws["A4"] = f"Job: {JOB_NO} — {JOB_TITLE}"
    ws["A5"] = f"Bid Date: {BID_DATE}  |  Pricing posture: DEFENSIBLE-FOR-VARIATIONS"
    ws["A6"] = "Rate basis: LFCS Rate Card V1.7 (15-Apr-2026) day shift"
    ws["A7"] = "  • Trade: $85.00/hr  |  Supervisor: $95.00/hr  |  Materials cost + 10% markup  |  Ute & tools $150/day"
    ws["A8"] = "  • 8-hr min call-out  |  15% redundancy on subtotal  |  30-day rate validity"
    ws["A9"] = "  • Variation rate basis = SAME as submission (Defensible-for-variations posture per concept note)"
    ws["A10"] = ""
    ws["A11"] = "Workbook structure (5 sheets): Cover · Labour · Materials · Summary · Sensitivity · Sources & Notes"
    ws["A12"] = f"Prepared by: {PREPARED_BY}"
    set_widths(ws, [120])

    # Labour
    ws = wb.create_sheet("Labour")
    ws["A1"] = "Labour — All Activities (Trade + Supervisor)"
    ws["A1"].font = Font(name="Arial", size=12, bold=True, color=NAVY_HEX)
    headers = ["#", "Activity", "Qty", "Unit", "Hr/unit", "Hours", "Role", "Rate $/hr", "Subtotal $", "Notes / source"]
    for i, h in enumerate(headers, start=1):
        ws.cell(row=3, column=i, value=h)
    style_header(ws, 3, len(headers))

    activity_rows = []
    for i, (desc, qty, unit, hr_per, role) in enumerate(ACTIVITIES, start=1):
        hours = round(qty * hr_per, 1)
        rate = TRADE_DAY_RATE if role == "trade" else SUP_DAY_RATE
        subtotal = round(hours * rate, 2)
        # Pick prod note from PROD dict by matching keyword
        note = ""
        for key, p in PROD.items():
            kw = key.replace("_", " ")
            if any(k in desc.lower() for k in [kw, kw.split()[0]]):
                note = p["note"]
                break
        activity_rows.append([i, desc, round(qty, 2), unit, hr_per, hours, role, rate, subtotal, note])

    # Add supervisor aggregate row
    activity_rows.append([
        len(activity_rows) + 1,
        "Site supervision — full time on pour/finish days, part-time on form/reo/block (30% of trade hours)",
        sup_hrs, "hrs", 1.0, sup_hrs, "supervisor", SUP_DAY_RATE,
        round(sup_hrs * SUP_DAY_RATE, 2),
        "Rate Card V1.7 supervisor loading. Applied across ~6-8 week programme."
    ])

    for i, row in enumerate(activity_rows, start=4):
        for j, val in enumerate(row, start=1):
            ws.cell(row=i, column=j, value=val)
    style_data(ws, 4, 4 + len(activity_rows) - 1, len(headers), money_cols=[8, 9])

    total_r = 4 + len(activity_rows)
    ws.cell(row=total_r, column=1, value="")
    ws.cell(row=total_r, column=2, value="LABOUR SUBTOTAL")
    ws.cell(row=total_r, column=6, value=f"=SUM(F4:F{total_r - 1})")
    ws.cell(row=total_r, column=9, value=f"=SUM(I4:I{total_r - 1})")
    ws.cell(row=total_r, column=9).number_format = '"$"#,##0.00'
    style_total_row(ws, total_r, len(headers))
    set_widths(ws, [4, 60, 10, 8, 10, 10, 12, 12, 14, 60])

    # Materials
    ws = wb.create_sheet("Materials")
    ws["A1"] = "Materials + Plant — LFCS Supply Only (FRP-install pattern: TCE supplies major)"
    ws["A1"].font = Font(name="Arial", size=12, bold=True, color=NAVY_HEX)
    headers = ["#", "Item", "Qty", "Unit", "Cost rate $", "Cost subtotal $", "Markup %", "Subtotal w/ markup $", "Notes"]
    for i, h in enumerate(headers, start=1):
        ws.cell(row=3, column=i, value=h)
    style_header(ws, 3, len(headers))
    mat_rows = [
        [1, "Formwork ply + props consumable wear", round(total_form_m2, 0), "m²", 25.00, round(total_form_m2 * 25, 2), int(MAT_MARKUP * 100), round(form_ply_marked, 2), "Consumable wear allowance — assumes ~70% recovery on plywood; props/acrows recovered fully"],
        [2, "AS3799 curing compound", round(SLAB_AREA_M2 + WALKWAY_AREA_M2, 0), "m² coverage", 4.00, round((SLAB_AREA_M2 + WALKWAY_AREA_M2) * 4, 2), int(MAT_MARKUP * 100), round(curing_marked, 2), "Spray-applied curing compound per S1.01 C-series notes"],
        [3, "Tie wire + bar chairs + small consumables", 1, "LS", 1500.00, 1500.00, int(MAT_MARKUP * 100), round(consumables_marked, 2), "Bar chairs plastic-tipped at A1/A2 (full plastic at external faces) per S1.01 R-series"],
        [4, "Ute & tools", ute_weeks * 5, "days", 150.00, ute_cost, 0, ute_cost, "1× ute, 8-week programme, 5 day-week. No markup per Rate Card V1.7."],
    ]
    for i, row in enumerate(mat_rows, start=4):
        for j, val in enumerate(row, start=1):
            ws.cell(row=i, column=j, value=val)
    style_data(ws, 4, 4 + len(mat_rows) - 1, len(headers), money_cols=[5, 6, 8])

    total_r = 4 + len(mat_rows)
    ws.cell(row=total_r, column=2, value="MATERIALS SUBTOTAL (incl. markup)")
    ws.cell(row=total_r, column=8, value=f"=SUM(H4:H{total_r - 1})")
    ws.cell(row=total_r, column=8).number_format = '"$"#,##0.00'
    style_total_row(ws, total_r, len(headers))
    set_widths(ws, [4, 50, 10, 12, 14, 16, 10, 18, 60])

    # Summary
    ws = wb.create_sheet("Summary")
    ws["A1"] = "Summary — Internal QS Total"
    ws["A1"].font = Font(name="Arial", size=12, bold=True, color=NAVY_HEX)
    summary_rows = [
        ("Labour subtotal (trade + supervisor)", round(labour_total, 2)),
        ("Materials + plant subtotal (incl. 10% markup on supply items)", round(materials_total, 2)),
        ("External (concrete pump excluded — TCE / by-others; no Engineer Cert beyond Hold Points)", 0.0),
        ("Subtotal pre-redundancy", round(subtotal, 2)),
        ("15% redundancy on subtotal", round(redundancy, 2)),
        ("SUBTOTAL ex GST", round(ex_gst, 2)),
        ("ROUNDED TENDER ex GST (to nearest $50)", float(ex_gst_rounded)),
        ("GST 10%", round(gst_rounded, 2)),
        ("GRAND TOTAL inc GST", round(inc_gst_rounded, 2)),
    ]
    for i, (label, val) in enumerate(summary_rows, start=3):
        ws.cell(row=i, column=1, value=label)
        ws.cell(row=i, column=2, value=val)
        ws.cell(row=i, column=2).number_format = '"$"#,##0.00'
        ws.cell(row=i, column=1).font = Font(name="Arial", size=10)
        if "ROUNDED" in label or "GRAND" in label:
            ws.cell(row=i, column=1).font = Font(name="Arial", size=11, bold=True, color=NAVY_HEX)
            ws.cell(row=i, column=2).font = Font(name="Arial", size=11, bold=True, color=RED_HEX)
    set_widths(ws, [70, 18])

    # Sensitivity
    ws = wb.create_sheet("Sensitivity")
    ws["A1"] = "Sensitivity — Quantified ±$ swings"
    ws["A1"].font = Font(name="Arial", size=12, bold=True, color=NAVY_HEX)
    headers = ["#", "Risk / Driver", "Direction", "Swing $", "Notes"]
    for i, h in enumerate(headers, start=1):
        ws.cell(row=3, column=i, value=h)
    style_header(ws, 3, len(headers))
    sens = [
        [1, "Slab plan area variance from 90% Design to 100% IFC (±5% of 300 m²)", "+/-", round((300 * 0.05 * SLAB_THICK_M) * 1.45 * 85 * 1.30, 0), "Vision-derived slab area is provisional. ±15 m² × 270mm × pour rate × supervisor loading"],
        [2, "BW1 blocklayer internal vs external subbie", "+", 12000, "Subbie quote typically $250/m²; internal LFCS rate ~$130/m². 48 m² × delta ≈ $6K-12K"],
        [3, "Reo density 130 → 180 kg/m³ (heavy banded zones underestimated)", "+", round(((125 * 50) / 1000) * 9.4 * 85 * 1.15, 0), "Vision pass identified heavy density but not full count. 50 kg/m³ × 125 m³ = 6.25 T extra fixing"],
        [4, "Mixed slab support split (suspended vs ground-supported)", "-", round((300 * 0.10) * 2 * 85 * 1.15, 0), "If ground-supported portion > 50%, soffit form labour drops. ±10% swing on ~150 m² = ~$5K"],
        [5, "P1 pile extension above NGL conventionally formed (vision-only S2.05 find)", "+", 8500, "If LFCS scope (not by-others) — extra formwork + labour for 13-15 piles"],
        [6, "AS3610 formwork class onerous (1 or 2 vs assumed 3 industrial)", "+", 14000, "Higher class = more form-face prep + sealing + tie-bar pattern + form releases"],
        [7, "Concrete pump LFCS-supplied vs by-others", "+", 6500, "Pump hire 5-6 pours × $1100/day"],
        [8, "TCE concrete + reo supply timing slip / standby", "+", 5000, "Standby billed at dayworks rate per Inclusions-Exclusions Q15"],
        [9, "Walkway DGB20 sub-base by-others slip / sequencing", "+", 3500, "Walkway pour delayed → standby + remob"],
        [10, "Existing wall demolition by-others sequencing in DJ zone", "+", 4000, "DJ zone delayed → split slab pour into 2 stages"],
        [11, "Surface finish more onerous than steel-trowel (e.g. burnished / sealed)", "+", 6000, "Steel-trowel + burnish + apply sealer = +1-2 days × 4-person crew"],
        [12, "Bid award delay → mob commencement past Q4 2026 → rate revalidation", "+", "TBC", "30-day rate validity per Inclusions-Exclusions Q6 — revalidate via VO"],
    ]
    for i, row in enumerate(sens, start=4):
        for j, val in enumerate(row, start=1):
            ws.cell(row=i, column=j, value=val)
    style_data(ws, 4, 4 + len(sens) - 1, len(headers), money_cols=[4])
    set_widths(ws, [4, 60, 10, 14, 70])

    # Sources & Notes
    ws = wb.create_sheet("Sources & Notes")
    ws["A1"] = "Sources & Notes — Every Input Rate Traceable"
    ws["A1"].font = Font(name="Arial", size=12, bold=True, color=NAVY_HEX)
    headers = ["#", "Topic", "Value", "Source"]
    for i, h in enumerate(headers, start=1):
        ws.cell(row=3, column=i, value=h)
    style_header(ws, 3, len(headers))
    sources = [
        [1, "Trade day shift rate", "$85.00/hr", "LFCS Rate Card V1.7 (15-Apr-2026) — Drive 1g6eQ32f_xNO3IiLh4k0IiQSgWT2-Qdxt"],
        [2, "Supervisor day shift rate", "$95.00/hr", "Rate Card V1.7 supervisor loading (+$10 over trade)"],
        [3, "Materials markup", "+10%", "Rate Card V1.7"],
        [4, "Ute & tools", "$150/day, no markup", "Rate Card V1.7"],
        [5, "Min call-out", "8 hrs/working day", "Rate Card V1.7"],
        [6, "Redundancy", "15% on subtotal", "LFCS standard per pricing-checklist Phase 6"],
        [7, "Concrete pour productivity", "1.45 hr/m³ (4-person crew × 22 m³/day)", "FRP Rates Development.xlsx — adjusted -10% for irregular pentagon + plinth congestion"],
        [8, "Reo install productivity (heavy density)", "9.4 hr/T", "FRP Rates Development.xlsx — adjusted for cogged edges + banded zones + beam ties (S2.01 vision)"],
        [9, "Mesh fix productivity", "0.27 hr/m²", "Industry standard slab-on-grade mesh"],
        [10, "Formwork erect productivity", "2 hr/m²", "FRP Rates Development.xlsx 'Suspended slab formwork' — typical complexity"],
        [11, "Formwork strip productivity", "1 hr/m²", "FRP Rates Development.xlsx"],
        [12, "BW1 blocklay productivity", "1.5 hr/m² combined", "Industry standard 190 RC block: 1 hr/blocklayer + 0.5 hr/labourer per m²"],
        [13, "Slab area assumption", "300 m² (provisional pentagon estimate)", "S2.00 vision pass — Rocky to confirm Tue 6 May AM after annotating drawings"],
        [14, "Slab reo density", "130 kg/m³ heavy", "S2.01 vision: N12-300 EW T+B + N16-200 transverse Layer 2+3 + 5N24/8N20 banded zones"],
        [15, "Walkway concrete spec", "120mm × 25 MPa + SL72 central + 100mm DGB20 by-others", "C103 PAVEMENT TYPE legend (vision-only find — text-pass missed)"],
        [16, "Walkway area", "55 m² (1m × ~55m)", "C103 setout points 5-12 — 12 corners derived"],
        [17, "BW1 perimeter", "32m wraps 3 slab edges", "S2.00 vision (NOT 4 sides — text-pass implied closed perimeter)"],
        [18, "BW1 height", "1500mm avg, 1800 max", "S2.00 slab note 5 + S2.05 Sec 1+3 vision"],
        [19, "PF1 count", "5 ea", "S3.00 framing plan vision count (4-5 visible)"],
        [20, "Mixed slab support split", "50% suspended-on-formwork, 50% on-natural-ground-with-vapour-barrier", "S2.05 Section 1 vision-only find (text-pass missed) — pricing impact: ground portion eliminates soffit form"],
        [21, "Drawing status", "ISSUED FOR 90% DESIGN — NOT IFC", "All 23 drawings stamped 90% Design — drift to 100% IFC = variation per Inclusions-Exclusions Q1"],
        [22, "Pre-Pricing-Grill", "Drafted 2026-05-04 PM", "02b/Pre-Pricing-Grill-2026-05-04.md — Rocky Tue 6 May AM verbal sign-off pending"],
        [23, "Defensible-for-variations posture", "Locked", "lfcs-standards/concepts/Defensible-for-variations-posture.md — variation rate basis = submission rate basis"],
        [24, "Source — drawings", "250643 Civil Rev 5 (17.04.2026) + 250643 Structural Rev 1 (15.04.2026)", "Henry & Hymas Sydney"],
        [25, "Source — TCE email scope", "concrete slab + walkway + bund wall (BW1 IN); commence mid-Aug 2026", "Kirolos Barsom 2026-05-04 3:02pm to info@lfcs.com.au"],
    ]
    for i, row in enumerate(sources, start=4):
        for j, val in enumerate(row, start=1):
            ws.cell(row=i, column=j, value=val)
    style_data(ws, 4, 4 + len(sources) - 1, len(headers))
    set_widths(ws, [4, 38, 30, 80])

    out = OUT_02B / f"{JOB_NO}-Rate-Buildup_v-CC.xlsx"
    wb.save(out)
    print(f"✓ {out.name}")
    return out


# ─────────────────────────────────────────────────────────────────────────────
# 3. Tender Summary xlsx (client-facing)
# ─────────────────────────────────────────────────────────────────────────────

def build_tender_summary():
    wb = Workbook()
    wb.remove(wb.active)

    # Cover
    ws = wb.create_sheet("Cover")
    ws["A1"] = LFCS_HEADER
    ws["A1"].font = Font(name="Arial", size=18, bold=True, color=NAVY_HEX)
    ws["A2"] = "TENDER SUBMISSION — LUMP SUM"
    ws["A2"].font = Font(name="Arial", size=14, bold=True, color=RED_HEX)
    ws["A4"] = f"Project: Wastewater Treatment Plant Upgrade — {SITE}"
    ws["A5"] = f"End Client: {END_CLIENT}"
    ws["A6"] = f"Designer: {DESIGNER}"
    ws["A7"] = f"To: {HEAD_CONTRACTOR} ({HEAD_CONTRACTOR_CONTACT}, {HEAD_CONTRACTOR_EMAIL})"
    ws["A8"] = f"LFCS Job Reference: {JOB_NO}"
    ws["A9"] = f"Drawings: 250643 Civil Rev 5 (17.04.2026) + 250643 Structural Rev 1 (15.04.2026) — ISSUED FOR 90% DESIGN"
    ws["A10"] = ""
    ws["A11"] = "PRICING POSTURE: Defensible-for-variations — submission rate = future variation rate (Rate Card V1.7)"
    ws["A11"].font = Font(name="Arial", size=10, bold=True, color=NAVY_HEX)
    ws["A12"] = "PROGRAMME: Site commence mid-Aug 2026  |  RATE VALIDITY: 30 days from submission"
    ws["A13"] = ""
    ws["A14"] = "Workbook structure (6 sheets, per LFCS pricing-checklist Phase 5):"
    ws["A15"] = "  1. Cover (this sheet)"
    ws["A16"] = "  2. Lump Sum (Option A — primary)"
    ws["A17"] = "  3. Schedule of Rates (Option B — variation rate basis)"
    ws["A18"] = "  4. Exclusions (Block A by-others + Block B defensive)"
    ws["A19"] = "  5. Terms (validity, pre-conditions, payment, signature)"
    ws["A20"] = "  6. Dimensions & Assumptions (every assumption traceable to source — '*' = AfC confirm)"
    ws["A21"] = ""
    ws["A22"] = f"Bid date: {BID_DATE} (Thu PM)"
    ws["A23"] = f"Prepared by: {PREPARED_BY}"
    ws["A24"] = "Document version: v-CC (claude-code-coo) — A/B parallel build with Cowork v-CW per AB-Test-Tracker"
    ws["A26"] = f"GRAND TOTAL inc GST: ${inc_gst_rounded:,.2f}"
    ws["A26"].font = Font(name="Arial", size=14, bold=True, color=RED_HEX)
    ws["A27"] = f"  ex GST: ${ex_gst_rounded:,.2f}  |  GST 10%: ${gst_rounded:,.2f}"
    ws["A27"].font = Font(name="Arial", size=11, bold=True, color=NAVY_HEX)
    set_widths(ws, [120])

    # Lump Sum
    ws = wb.create_sheet("Lump Sum")
    ws["A1"] = "Option A — Lump Sum (Primary)"
    ws["A1"].font = Font(name="Arial", size=12, bold=True, color=NAVY_HEX)
    ws["A2"] = "All quantities provisional from 90% Design — '*' indicates AfC-confirm pending. ±5% qty variance from 100% IFC = variation per Q1 of Exclusions sheet."
    ws["A2"].font = Font(name="Arial", size=9, italic=True, color=LFCS_GREY.hexval()[2:])
    ws.merge_cells("A2:G2")

    headers = ["#", "Description", "Qty *", "Unit", "Rate $", "Total $", "Calc Basis (rate derivation)"]
    for i, h in enumerate(headers, start=1):
        ws.cell(row=4, column=i, value=h)
    style_header(ws, 4, len(headers))

    # Build line items with computed totals.
    # Distribute redundancy proportionally across line totals so client sees integrated rate
    # (Calc Basis still references base $ + 15% redundancy explicitly so QS can cell-check)

    # Compute per-line base costs
    def labour_for(activity_keys):
        """Sum trade hours × $85 + supervisor 30% loading × $95 + materials proportion + 15% redundancy."""
        trade_h = sum(qty * hr for (desc, qty, _, hr, role) in ACTIVITIES if role == "trade" and any(k in desc.lower() for k in activity_keys))
        return trade_h

    # Element 1: WWTP main slab + integrated beams + plinths
    e1_keys = ["main slab", "plinths", "beams", "concrete pour + place + finish — main slab"]
    e1_trade_hrs = (
        (slab_concrete + beam_concrete) * 1.45 +     # main slab + beams pour
        plinth_concrete * 1.45 +                      # plinths pour
        (slab_reo_T + beam_reo_T) * 9.4 +             # slab + beams reo
        mesh_plinth_m2 * 0.27 +                       # plinth mesh
        form_slab_m2 * 2.0 +                          # slab form erect
        form_beam_m2 * 2.0 +                          # beam form erect
        form_plinth_m2 * 2.0 +                        # plinth form erect
        (form_slab_m2 + form_beam_m2 + form_plinth_m2) * 1.0 +  # form strip
        SLAB_AREA_M2 * 0.01                           # curing
    )
    e1_sup_hrs = e1_trade_hrs * 0.30
    e1_labour = e1_trade_hrs * TRADE_DAY_RATE + e1_sup_hrs * SUP_DAY_RATE
    e1_form_ply = (form_slab_m2 + form_beam_m2 + form_plinth_m2) * 25 * (1 + MAT_MARKUP)
    e1_curing = SLAB_AREA_M2 * 4 * (1 + MAT_MARKUP)
    e1_consum_share = 0.5 * consumables_marked + 0.5 * ute_cost  # half of consumables + half ute to main slab element
    e1_subtotal = e1_labour + e1_form_ply + e1_curing + e1_consum_share
    e1_total = round(e1_subtotal * (1 + MARGIN_REDUNDANCY), 0)

    # Element 2: Walkway pavement
    e2_trade_hrs = (
        walkway_concrete * 1.45 +
        mesh_walkway_m2 * 0.27 +
        form_walkway_m2 * 2.0 +
        form_walkway_m2 * 1.0 +
        WALKWAY_AREA_M2 * 0.01
    )
    e2_sup_hrs = e2_trade_hrs * 0.30
    e2_labour = e2_trade_hrs * TRADE_DAY_RATE + e2_sup_hrs * SUP_DAY_RATE
    e2_form_ply = form_walkway_m2 * 25 * (1 + MAT_MARKUP)
    e2_curing = WALKWAY_AREA_M2 * 4 * (1 + MAT_MARKUP)
    e2_subtotal = e2_labour + e2_form_ply + e2_curing
    e2_total = round(e2_subtotal * (1 + MARGIN_REDUNDANCY), 0)

    # Element 3: PF1 pad footings
    e3_trade_hrs = (
        pf1_concrete * 1.45 +
        pf1_reo_T * 9.4 +
        form_pf1_m2 * 2.0 +
        form_pf1_m2 * 1.0
    )
    e3_sup_hrs = e3_trade_hrs * 0.30
    e3_labour = e3_trade_hrs * TRADE_DAY_RATE + e3_sup_hrs * SUP_DAY_RATE
    e3_form_ply = form_pf1_m2 * 25 * (1 + MAT_MARKUP)
    e3_subtotal = e3_labour + e3_form_ply
    e3_total = round(e3_subtotal * (1 + MARGIN_REDUNDANCY), 0)

    # Element 4: BW1 bund wall
    e4_trade_hrs = (
        BW1_AREA_M2 * 1.5 +    # blocklay combined
        bw1_reo_T * 9.4        # reo threading
    )
    e4_sup_hrs = e4_trade_hrs * 0.30
    e4_labour = e4_trade_hrs * TRADE_DAY_RATE + e4_sup_hrs * SUP_DAY_RATE
    e4_consum_share = 0.5 * consumables_marked + 0.5 * ute_cost
    e4_subtotal = e4_labour + e4_consum_share
    e4_total = round(e4_subtotal * (1 + MARGIN_REDUNDANCY), 0)

    # Adjust to match overall ex_gst_rounded — add small balancer or scale
    raw_sum = e1_total + e2_total + e3_total + e4_total
    scale = ex_gst_rounded / raw_sum if raw_sum > 0 else 1.0
    e1_total = round(e1_total * scale, 0)
    e2_total = round(e2_total * scale, 0)
    e3_total = round(e3_total * scale, 0)
    e4_total = round(e4_total * scale, 0)
    # Final balancer to round to exact ex_gst_rounded
    diff = ex_gst_rounded - (e1_total + e2_total + e3_total + e4_total)
    e1_total += diff  # absorb residual rounding into largest line

    line_items = [
        [1, LUMP_SUM_LINES[0]["desc"], LUMP_SUM_LINES[0]["qty"], LUMP_SUM_LINES[0]["unit"], "—", e1_total, LUMP_SUM_LINES[0]["calc_basis"]],
        [2, LUMP_SUM_LINES[1]["desc"], LUMP_SUM_LINES[1]["qty"], LUMP_SUM_LINES[1]["unit"], "—", e2_total, LUMP_SUM_LINES[1]["calc_basis"]],
        [3, LUMP_SUM_LINES[2]["desc"], LUMP_SUM_LINES[2]["qty"], LUMP_SUM_LINES[2]["unit"], "—", e3_total, LUMP_SUM_LINES[2]["calc_basis"]],
        [4, LUMP_SUM_LINES[3]["desc"], LUMP_SUM_LINES[3]["qty"], LUMP_SUM_LINES[3]["unit"], "—", e4_total, LUMP_SUM_LINES[3]["calc_basis"]],
    ]
    for i, row in enumerate(line_items, start=5):
        for j, val in enumerate(row, start=1):
            ws.cell(row=i, column=j, value=val)
    style_data(ws, 5, 5 + len(line_items) - 1, len(headers), money_cols=[6])

    # Totals
    total_r = 5 + len(line_items)
    ws.cell(row=total_r, column=2, value="SUBTOTAL ex GST")
    ws.cell(row=total_r, column=6, value=f"=SUM(F5:F{total_r - 1})")
    ws.cell(row=total_r, column=6).number_format = '"$"#,##0.00'
    style_total_row(ws, total_r, len(headers))

    ws.cell(row=total_r + 1, column=2, value="GST 10%")
    ws.cell(row=total_r + 1, column=6, value=f"=F{total_r}*0.10")
    ws.cell(row=total_r + 1, column=6).number_format = '"$"#,##0.00'
    style_data(ws, total_r + 1, total_r + 1, len(headers), money_cols=[6])

    ws.cell(row=total_r + 2, column=2, value="GRAND TOTAL inc GST")
    ws.cell(row=total_r + 2, column=6, value=f"=F{total_r}*1.10")
    ws.cell(row=total_r + 2, column=6).number_format = '"$"#,##0.00'
    style_total_row(ws, total_r + 2, len(headers))
    ws.cell(row=total_r + 2, column=2).font = Font(name="Arial", size=11, bold=True, color="FFFFFF")
    ws.cell(row=total_r + 2, column=2).fill = PatternFill("solid", fgColor=RED_HEX)
    ws.cell(row=total_r + 2, column=6).font = Font(name="Arial", size=11, bold=True, color="FFFFFF")
    ws.cell(row=total_r + 2, column=6).fill = PatternFill("solid", fgColor=RED_HEX)

    set_widths(ws, [4, 70, 14, 14, 12, 16, 60])

    # Schedule of Rates (Option B)
    ws = wb.create_sheet("SoR")
    ws["A1"] = "Option B — Schedule of Rates (LFCS Rate Card V1.7 — Variation Rate Basis)"
    ws["A1"].font = Font(name="Arial", size=12, bold=True, color=NAVY_HEX)
    ws["A2"] = "Variations + standby + cancellations bill against this same rate basis as Lump Sum (Defensible-for-variations posture)."
    ws["A2"].font = Font(name="Arial", size=9, italic=True)
    ws.merge_cells("A2:E2")

    headers = ["Tier", "Hours into shift", "Rate $/hr trade", "Rate $/hr supervisor", "Notes"]
    for i, h in enumerate(headers, start=1):
        ws.cell(row=4, column=i, value=h)
    style_header(ws, 4, len(headers))
    sor_rows = [
        ["Day shift (Mon-Fri ordinary)", "First 8 hrs (ordinary time)", 85.00, 95.00, "PRIMARY TIER — applied across this submission. 8 hr min call-out per working day."],
        ["Day shift OT", "Hrs 9-10 (time-and-a-quarter)", 105.00, 115.00, "Day-shift OT — applies if work extends beyond 8 hrs"],
        ["Day shift double", "After 10 hrs (double time)", 120.00, 130.00, "Beyond 10 hrs — applies if extended day shifts requested"],
        ["Night shift (Sun-Thu 19:00-05:00)", "First 2 hrs", 105.00, 115.00, "NOT APPLIED — 2629 day shift only"],
        ["Night shift", "After 2 hrs", 140.00, 150.00, "NOT APPLIED — 2629 day shift only"],
        ["Saturday", "First 2 hrs / after 2 hrs", "$105 / $120", "$115 / $130", "NOT APPLIED — weekend only on agreed VOs"],
        ["Sunday + Public Holiday", "All hrs", 140.00, 150.00, "NOT APPLIED"],
        ["Materials", "Cost basis", "+10% markup", "+10% markup", "Applies to LFCS-supplied materials only (formwork ply + curing + consumables)"],
        ["Ute & tools", "Per day per vehicle", 150.00, "n/a", "No markup. Per Rate Card V1.7"],
        ["Min call-out", "Per working day", "8 hrs", "8 hrs", "Adverse weather + by-others standby + cancellation = 8 hrs minimum"],
        ["Midnight crossing loading", "Per person per shift", "+8 hrs at day rate", "+8 hrs at day rate", "NOT APPLIED — 2629 day shift only"],
    ]
    for i, row in enumerate(sor_rows, start=5):
        for j, val in enumerate(row, start=1):
            ws.cell(row=i, column=j, value=val)
    style_data(ws, 5, 5 + len(sor_rows) - 1, len(headers))
    set_widths(ws, [38, 28, 18, 22, 60])

    # Exclusions
    ws = wb.create_sheet("Exclusions")
    ws["A1"] = "Exclusions — Block A (mirror by-others scope) + Block B (defensive)"
    ws["A1"].font = Font(name="Arial", size=12, bold=True, color=NAVY_HEX)
    ws["A2"] = "BLOCK A — By-others scope (mirror of TCE / Cleanaway responsibility per Drawing-Index supply boundary table):"
    ws["A2"].font = Font(name="Arial", size=11, bold=True, color=NAVY_HEX)
    a_rows = [
        "1. Concrete supply (40 MPa slab, 32 MPa walkway/PF1/BW1-corefill, 25 MPa walkway pavement) — by TCE",
        "2. Reinforcement supply (all N-grade deformed bars to AS4671 + SL72 + SL92 mesh) — by TCE",
        "3. Block + core-fill grout supply for BW1 — by TCE",
        "4. 750 dia bored piles P1 (32 MPa, 8m socket into siltstone, 10-N24 vertical + 400 cogs + N12-300 ties) — by-others piling subby",
        "5. Stormwater sump 1000×1000×1200 (S2.00 top-left + per civil drawings) — by-others civil contractor",
        "6. Existing wall demolition (S2.00 east edge + S2.05 Section 2) — by-others demo subby",
        "7. Existing slab DJ tie-in: drilling + 20mm Danley dowel install + Hilti HIT-HY 200 chemical anchor injection (S2.00 note 4) — by-others",
        "8. Sludge dewatering unit access platform steel framing (PSC1 100×6 SHS + PSC2 150 UC 23 + PSB1 200 UB 22 + PSB2 200 PFC + Webforge A325MSG grating + 16mm cross-bracing + handrail) — by-others steel installer",
        "9. Hilti chemical anchor install for sludge platform (4-M20 Hilti HAS HDG per PSC1/PSC2 base @ 170mm embed with HIT-HY 200) — by-others steel installer (LFCS pours clean slab; steel installer drills + chemsets retro)",
        "10. Pedestrian walkway steel framing (SB1/SB2 250 PFC + SC1 100×6 SHS + grating + handrail + brackets + welds + cross-bracing + cleat plates) — by-others steel installer",
        "11. Hilti chemical anchor install for walkway columns (4-M20 at typical PF1 + 2-M20 at leftmost column + 2-M20 at right-end into pavement edge thickening) — by-others steel installer",
        "12. Walkway DGB20 100mm sub-base + compacted subgrade (per C103 PAVEMENT TYPE legend) — by-others earthworks",
        "13. Existing services relocations (EXIE electricity + EXIW water + EXIC comms + EXIF fence — per C103 red-dashed) — by-others services contractor",
        "14. All Mechanical work — piping, equipment, MTO, isometric, tie-ins (250643_SK_M101-M106) — by-others",
        "15. All Electrical work — MCC, single-line, hazardous area classification, earthing, cable retic, L&SP (250643_SK_E100-E203) — by-others",
        "16. All Process / P&ID (250643_SK_C900-C953) — by-others",
        "17. Earthworks: bulk excavation + compaction + sub-base preparation — by-others",
        "18. Site security, hoarding, fencing, traffic management — by head contractor / Cleanaway",
    ]
    r = 4
    for it in a_rows:
        ws.cell(row=r, column=1, value=it).alignment = Alignment(wrap_text=True)
        r += 1
    ws.cell(row=r, column=1, value="").font = Font(size=4)
    r += 1
    ws.cell(row=r, column=1, value="BLOCK B — LFCS defensive exclusions:")
    ws.cell(row=r, column=1).font = Font(name="Arial", size=11, bold=True, color=NAVY_HEX)
    r += 1
    b_rows = [
        "B1. Cranage — not allowed for; if required, priced separately or supplied by others",
        "B2. Concrete pumping — by-others (TCE) OR variation if LFCS provides",
        "B3. Out-of-hours work, weekend work, night shift — DAY SHIFT ONLY (Mon-Fri ordinary). Variation if requested.",
        "B4. Wet-weather standby + downtime — claimable on dayworks rate per Rate Card V1.7 (8-hr min call-out)",
        "B5. Rock or unforeseen ground conditions at PF1 base / under formwork to NGL — rates assume normal soil per Core G Eotech 10.12.25",
        "B6. Confined space entry — only if priced as named items (none identified in scope)",
        "B7. Hazardous area certification / intrinsically-safe equipment — by-others",
        "B8. Biohazard PPE beyond standard PPE — by-others if site requires (assess at induction)",
        "B9. Asbestos discovery + management — variation if encountered",
        "B10. NCRs, defects, rework due to design changes between 90% Design and 100% IFC — variation",
        "B11. Delay damages caused by pile sequencing, demo sequencing, by-others trades — variation per Rate Card V1.7 dayworks rate",
        "B12. AS3610 formwork class higher than Class 3 industrial assumed — variation",
        "B13. Slab / walkway surface finish more onerous than steel-trowel + broom assumed — variation",
        "B14. Concrete supplier off TCE Register / NATA mix lead-time — flag if applicable; variation if LFCS holds program",
        "B15. Pre-conditions to commencement not met by award — see Terms sheet",
        "B16. Cancellation premium — 8-hr min call-out per working day at Rate Card V1.7 day shift rate",
        "B17. Tax + duty changes during the 30-day rate validity period",
        "B18. Quantity variance ±5% between 90% Design take-off and 100% IFC — re-priced at submission rate (Defensible-for-variations clause)",
        "B19. Demolition spoil + waste disposal — by-others",
        "B20. Site shed / amenities / first-aid — by head contractor",
        "B21. Survey + setting out + invert level supply — by head contractor",
        "B22. Engineer Cert beyond standard Hold Point sign-offs — additional engineering fees by head contractor",
        "B23. Sub-PQP requested in format different to LFCS standard — additional admin labour as variation",
        "B24. Variations from mid-Aug 2026 commencement date (if award delayed past 60 days) — rate revalidation per Q6 of Inclusions-Exclusions",
        "B25. Cleanaway (end client) site-specific induction premiums beyond standard contractor induction",
    ]
    for it in b_rows:
        ws.cell(row=r, column=1, value=it).alignment = Alignment(wrap_text=True)
        r += 1
    set_widths(ws, [120])

    # Terms
    ws = wb.create_sheet("Terms")
    ws["A1"] = "Terms — Validity, Pre-conditions, Payment, Signature"
    ws["A1"].font = Font(name="Arial", size=12, bold=True, color=NAVY_HEX)
    terms = [
        ["1. Validity", "30 days from submission date (2026-05-07). Materials cost movements (concrete + reo) may trigger rate revalidation past validity."],
        ["2. Programme", "Mid-Aug 2026 commencement per TCE email 2026-05-04. ~14 weeks lead from award to mob. Programme duration provisional 6-8 weeks on-site."],
        ["3. Hours of work", "Day shift only (Mon-Fri ordinary). Night/weekend = variation at Rate Card V1.7 night/Saturday/Sunday tier rates."],
        ["4. Payment basis", "Lump Sum primary (Option A). Schedule of Rates basis (Option B) governs variations + standby + cancellations."],
        ["5. Variation rate basis", "Variations bill against LFCS Rate Card V1.7 day shift rates as included in Option B SoR sheet — SAME basis as submission (Defensible-for-variations posture)."],
        ["6. Pre-conditions to commencement", "(a) TCE concrete + reo + block + grout supply chain confirmed; (b) Pile work + DJ install Hold Point released by piling/demo subbies; (c) Walkway sub-base + subgrade by-others completed + signed off; (d) Existing services relocations + fence removals signed off; (e) Eastern Creek scope-split annotation by Rocky locked; (f) AfC drawings issued for IFC drift assessment OR variation rate basis accepted in writing."],
        ["7. Insurances", "LFCS holds Public Liability $20M, Workers Comp, and Plant insurance. Certificates available on request."],
        ["8. Security of Payment", "Standard NSW SoP Act 1999 progress claim cycle assumed."],
        ["9. Retention", "No retention included in Lump Sum unless TCE confirms standard 5%/2.5% release cycle applies. If applicable, gross-up by retention % is variation."],
        ["10. Daywork docket discipline", "Variations + standby billed via signed daywork dockets — daily docket signed by TCE site supervisor + Cleanaway site rep at end of each shift. Unsigned dockets = unpaid per LFCS standard practice."],
        ["11. Drawings revision currency", "Submission based on 90% Design (Civil Rev 5 17.04.2026; Structural Rev 1 15.04.2026). Drift to 100% IFC = variation per Inclusions-Exclusions Q1 + Q7."],
        ["12. Signatory", "Signed for and on behalf of LF Construction Services Pty Ltd by Rocky McLoughlin (PM/Super) — see Submission Cover letter for signature."],
        ["13. Acceptance", "TCE's purchase order / written acceptance of this submission constitutes acceptance of all terms herein."],
    ]
    for i, (a, b) in enumerate(terms, start=3):
        ws.cell(row=i, column=1, value=a).font = Font(name="Arial", size=10, bold=True)
        ws.cell(row=i, column=2, value=b).alignment = Alignment(wrap_text=True, vertical="top")
        ws.cell(row=i, column=2).font = Font(name="Arial", size=10)
    set_widths(ws, [22, 120])

    # Dimensions & Assumptions
    ws = wb.create_sheet("Dimensions & Assumptions")
    ws["A1"] = "Dimensions & Assumptions — Every assumption traceable, * = AfC-confirm pending"
    ws["A1"].font = Font(name="Arial", size=12, bold=True, color=NAVY_HEX)
    headers = ["#", "Parameter", "Value / Assumption", "Source / Note", "AfC * "]
    for i, h in enumerate(headers, start=1):
        ws.cell(row=3, column=i, value=h)
    style_header(ws, 3, len(headers))
    dim_rows = [
        [1, "Slab plan area (irregular pentagon)", f"{SLAB_AREA_M2} m²", "S2.00 vision-pass approximation at 1:100 ratio — Rocky/take-off team to measure off plan", "*"],
        [2, "Slab field thickness", f"{int(SLAB_THICK_M*1000)}mm", "Verbatim S2.00 slab note 1: 'SLAB TO BE 270mm MIN. SUSPENDED RC SLAB U.N.O.'", ""],
        [3, "Slab edge thickening depth", f"{int(SLAB_EDGE_THICK_M*1000)}mm", "S2.05 Sec 1 + Sec 2 + Sec 3 vision", ""],
        [4, "Slab perimeter (for edge form)", f"~{int(SLAB_EDGE_PERIMETER_M)}m", "Approximation from S2.00 plan", "*"],
        [5, "Concrete grade — slab + plinths", "40 MPa", "Verbatim S2.00 slab note 2 + S2.05 plinth typical callout", ""],
        [6, "Concrete grade — walkway pavement", "25 MPa", "C103 PAVEMENT TYPE legend (vision-only find)", ""],
        [7, "Concrete grade — PF1 pad footings + BW1 core fill + piles", "32 MPa", "S3.00 PF1 detail + S2.00 slab note 3 + S2.00 slab note 5", ""],
        [8, "Reo intensity slab + beams", f"{SLAB_REO_DENSITY_KG_M3} kg/m³ heavy", "S2.01 vision: N12-300 EW T+B + N16-200 transverse Layer 2+3 + 5N24/8N20/9N24/10N20/10N24 banded zones + 2N12-300 beam ties. Final tonnage requires plan take-off.", "*"],
        [9, "Beam count", f"{NUM_BEAMS} ea", "S2.00 plan vision count", ""],
        [10, "Beam cross-section", "500 W × 1200 D", "S2.00 plan label '500 x 1200 RC BEAM BW1' + S2.05 sections", ""],
        [11, "Beam avg length", f"~{BEAM_AVG_LENGTH_M}m", "Approximation from S2.00 plan ratio", "*"],
        [12, "Plinth count", f"{NUM_PLINTHS} ea typical", "S2.00 plan dashed-rectangle count + 'PLINTH, TYPICAL' callout", "*"],
        [13, "Plinth size each", "~1.5 × 1.5 × 0.1m", "S2.05 Sec 1 plinth typical detail", "*"],
        [14, "Plinth top mesh", "SL92", "S2.05 Sec 1 plinth typical detail callout", ""],
        [15, "Walkway concrete spec", "120mm × 25 MPa with SL72 mesh central, on 100mm DGB20 base by-others, on compacted subgrade by-others", "C103 PAVEMENT TYPE legend (verbatim)", ""],
        [16, "Walkway plan area", f"~{int(WALKWAY_AREA_M2)} m² (1m × ~55m)", "Derived from C103 setout points 5-12 (vision pass)", "*"],
        [17, "PF1 spec", "1000 MIN (L) × 600 (W) × 400 (D), 32 MPa, 5-N16 'U' bars T+B, N12-200 ties, on natural ground 150 kPa bearing", "S3.00 PF1 PAD FOOTING DETAIL", ""],
        [18, "PF1 count", f"{PF1_COUNT} ea", "S3.00 framing plan vision count (4-5 visible)", "*"],
        [19, "BW1 spec", "190 RC block, 1800mm max H, all cores filled @ 32 MPa, N16-400 EW central reo", "Verbatim S2.00 slab note 5", ""],
        [20, "BW1 perimeter", f"~{int(BW1_PERIMETER_M)}m wraps 3 slab edges (left + diagonal + bottom)", "S2.00 vision (NOT 4-side closed perimeter)", "*"],
        [21, "BW1 average height", f"{BW1_AVG_HEIGHT_M}m (max 1800mm per note 5)", "S2.00 + S2.05 Sec 1+3 vision", "*"],
        [22, "Slab support condition", "Mixed: 50% suspended-on-formwork over piles + 50% on-natural-ground with vapour barrier (assumed split)", "S2.05 Section 1 vision-only find (text-pass missed) — Rocky to confirm split at grill", "*"],
        [23, "Pile work P1", "By-others piling subby (32 MPa, 750 dia, 8m socket, 10-N24 + N12-300 ties) — LFCS commences after pile tops cleaned + Hold Point", "S2.00 slab note 3", ""],
        [24, "Pile extension above NGL", "Provisional by-others (S2.05 Sec 1 vision-only: 'P1 to be conventionally formed and extended above NGL' — may be LFCS)", "S2.05 Section 1 callout", "*"],
        [25, "DJ existing slab tie-in", "By-others — drill + grout 20mm Danley square dowel × 400 LG (HDG) at 400 max ctrs, 200 embed with Hilti HIT-HY 200, dowel master sleeve, 10mm Jointex", "Verbatim S2.00 slab note 4", ""],
        [26, "Hilti anchor install (steel platforms + walkway)", "By-others steel installer — LFCS pours clean PF1 + slab; steel installer drills + chemsets retro (typical method)", "S2.00 platform plan + S3.00 detail", ""],
        [27, "Existing wall to be demolished", "By-others demo subby — partial extent on east edge of slab (per S2.00 callout + S2.05 Sec 2)", "S2.00 east edge callout 'EXTENT OF EXISTING WALL TO BE DEMOLISHED'", ""],
        [28, "Existing block retaining wall (west)", "Preserved (NOT in demo scope) — S2.05 Sec 1 + Sec 3 show preserved", "S2.05 Sec 1+3 vision", ""],
        [29, "Reo grade", "N-grade deformed to AS4671 + SL72/SL92 fabric to AS4671", "S1.01 R-series (text-pass)", ""],
        [30, "Concrete cover", "B1-External (1-50km coast) typical; B2-External (up to 1km) marine where applicable", "S1.01 C12 cover table (text-pass)", ""],
        [31, "Formwork class", "AS3610 Class 3 (industrial finish) assumed — qualified in submission", "Drawing notes don't specify project spec; assume Class 3 per Inclusions-Exclusions Q12", "*"],
        [32, "Slab finish", "Steel-trowel slab + broom finish walkway assumed — qualified in submission", "Drawings don't explicitly call out — assumed UNO per Inclusions-Exclusions Q13", "*"],
        [33, "Curing", "Within 2 hrs of pour, min 7 days, AS3799 curing compound", "S1.01 C-series + R-series (text-pass)", ""],
        [34, "Lap lengths", "N12 = 400 (500), N16 = ~625, N24 = 1250, N28 = 1500, N32 = 1750, N36 = 2050", "S1.01 R10 lap table (text-pass)", ""],
        [35, "Standards referenced", "AS3600 / AS3610 / AS4671 / AS3700 / AS5131 / AS4100 / AS1379 / AS3799 / AS2159 / AS1170", "S1.01 (text-pass)", ""],
        [36, "Geotech bearings", "Strip footings 150 kPa (PF1) / Bored piles 700 kPa (P1) — per Core G Eotech report 10.12.25", "S2.00 slab note 3 + S3.00 PF1 detail", ""],
        [37, "Eastern Creek scope-split pattern", "LFCS = blue + green corner + purple sections IN; pink + ALL tanks (incl purple tank) OUT", "Rocky verbal 2026-05-04 PM — annotation pending Tue 6 May AM", "*"],
        [38, "Drawing status", "ISSUED FOR 90% DESIGN — NOT FOR CONSTRUCTION", "All 23 drawings stamped 90% Design (Civil Rev 5 / Structural Rev 1)", ""],
    ]
    for i, row in enumerate(dim_rows, start=4):
        for j, val in enumerate(row, start=1):
            ws.cell(row=i, column=j, value=val)
    style_data(ws, 4, 4 + len(dim_rows) - 1, len(headers))
    set_widths(ws, [4, 38, 36, 75, 6])

    out = OUT_02B / f"{JOB_NO}-Tender-Summary_v-CC.xlsx"
    wb.save(out)
    print(f"✓ {out.name}")
    return out


# ─────────────────────────────────────────────────────────────────────────────
# 4. Branded PDF helpers
# ─────────────────────────────────────────────────────────────────────────────

def lfcs_header_footer(canvas, doc, doc_title, doc_subtitle=""):
    """Draw LFCS header bar + footer on every page."""
    canvas.saveState()
    # Header bar
    canvas.setFillColor(LFCS_NAVY)
    canvas.rect(0, A4[1] - 22 * mm, A4[0], 22 * mm, fill=True, stroke=False)
    canvas.setFillColor(LFCS_RED)
    canvas.rect(0, A4[1] - 24 * mm, A4[0], 2 * mm, fill=True, stroke=False)
    canvas.setFillColor(HexColor("#FFFFFF"))
    canvas.setFont("Helvetica-Bold", 14)
    canvas.drawString(15 * mm, A4[1] - 12 * mm, LFCS_HEADER)
    canvas.setFont("Helvetica", 9)
    canvas.drawString(15 * mm, A4[1] - 18 * mm, doc_title)
    if doc_subtitle:
        canvas.drawRightString(A4[0] - 15 * mm, A4[1] - 18 * mm, doc_subtitle)
    # Footer bar
    canvas.setFillColor(LFCS_NAVY)
    canvas.rect(0, 0, A4[0], 12 * mm, fill=True, stroke=False)
    canvas.setFillColor(HexColor("#FFFFFF"))
    canvas.setFont("Helvetica", 8)
    canvas.drawString(15 * mm, 4.5 * mm, LFCS_FOOTER)
    canvas.drawRightString(A4[0] - 15 * mm, 4.5 * mm, f"Page {doc.page}")
    canvas.restoreState()


def make_styles():
    styles = getSampleStyleSheet()
    styles.add(ParagraphStyle(name="LfcsTitle", fontName="Helvetica-Bold", fontSize=18, textColor=LFCS_NAVY, spaceAfter=6, alignment=TA_LEFT))
    styles.add(ParagraphStyle(name="LfcsH1", fontName="Helvetica-Bold", fontSize=13, textColor=LFCS_NAVY, spaceBefore=10, spaceAfter=4, alignment=TA_LEFT))
    styles.add(ParagraphStyle(name="LfcsH2", fontName="Helvetica-Bold", fontSize=11, textColor=LFCS_RED, spaceBefore=8, spaceAfter=3, alignment=TA_LEFT))
    styles.add(ParagraphStyle(name="LfcsBody", fontName="Helvetica", fontSize=10, textColor=HexColor("#222222"), leading=13, spaceAfter=4, alignment=TA_JUSTIFY))
    styles.add(ParagraphStyle(name="LfcsBullet", fontName="Helvetica", fontSize=10, textColor=HexColor("#222222"), leading=13, leftIndent=12, spaceAfter=2))
    styles.add(ParagraphStyle(name="LfcsRight", fontName="Helvetica-Bold", fontSize=11, textColor=LFCS_RED, alignment=TA_RIGHT))
    styles.add(ParagraphStyle(name="LfcsSmall", fontName="Helvetica", fontSize=8, textColor=LFCS_GREY, alignment=TA_LEFT))
    return styles


# ─────────────────────────────────────────────────────────────────────────────
# 5. Pre-Pricing-Grill PDF (branded render of vault MD)
# ─────────────────────────────────────────────────────────────────────────────

def build_grill_pdf():
    out = OUT_02B / f"{JOB_NO}-Pre-Pricing-Grill_v-CC.pdf"
    doc = SimpleDocTemplate(
        str(out), pagesize=A4,
        leftMargin=15 * mm, rightMargin=15 * mm,
        topMargin=30 * mm, bottomMargin=18 * mm,
    )
    s = make_styles()
    story = []
    title = f"Pre-Pricing-Grill — {JOB_NO} {JOB_TITLE}"
    subtitle = f"Bid {BID_DATE}"

    story.append(Paragraph(f"Pre-Pricing-Grill (Phase 0) — {JOB_NO} {JOB_TITLE}", s["LfcsTitle"]))
    story.append(Paragraph(f"<b>Job:</b> {JOB_NO} — {JOB_TITLE}<br/><b>Head Contractor:</b> {HEAD_CONTRACTOR} ({HEAD_CONTRACTOR_CONTACT})<br/><b>End Client:</b> {END_CLIENT}<br/><b>Bid Due:</b> {BID_DATE} (Thu PM)<br/><b>Status:</b> Drafted async pending Rocky Tue 6 May AM verbal sign-off", s["LfcsBody"]))
    story.append(Spacer(1, 6))
    story.append(Paragraph("This Phase 0 grill (per <i>pricing-checklist.md</i>) captures every assumption that drives a rate before pricing finalised. No pricing finalised until Rocky verbal-confirms each Q. Rocky's call wins over RFQ literal reading.", s["LfcsBody"]))

    story.append(Paragraph("Q1 — Supply boundaries", s["LfcsH1"]))
    q1 = [
        ["Item", "LFCS supply", "TCE / by-others"],
        ["Concrete (40/32/25 MPa per element to AS1379)", "—", "✓ TCE"],
        ["Reinforcement (N-grade to AS4671 + SL72/92 mesh)", "—", "✓ TCE"],
        ["Bund wall blocks (190 RC block to AS3700)", "—", "✓ TCE"],
        ["Bund wall core-fill grout (32 MPa)", "—", "✓ TCE"],
        ["Vapour barrier (under suspended-on-ground portion)", "—", "✓ Earthworks"],
        ["Formwork ply + props + bracing", "✓ LFCS", "—"],
        ["Curing compound (AS3799)", "✓ LFCS", "—"],
        ["Tie wire + bar chairs + small consumables", "✓ LFCS", "—"],
    ]
    t = Table(q1, colWidths=[100 * mm, 35 * mm, 35 * mm])
    t.setStyle(TableStyle([
        ("BACKGROUND", (0, 0), (-1, 0), LFCS_NAVY),
        ("TEXTCOLOR", (0, 0), (-1, 0), HexColor("#FFFFFF")),
        ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
        ("FONTSIZE", (0, 0), (-1, -1), 9),
        ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
        ("GRID", (0, 0), (-1, -1), 0.5, HexColor("#999999")),
        ("ROWBACKGROUNDS", (0, 1), (-1, -1), [HexColor("#FFFFFF"), HexColor("#F2F4F7")]),
    ]))
    story.append(t)
    story.append(Paragraph("<b>Default applied:</b> TCE supplies all major materials; LFCS supplies formwork + curing + small consumables (FRP-install pattern, same supply chain as 2602 Munmorah).", s["LfcsBody"]))

    story.append(Paragraph("Q2 — Site management + plant boundaries", s["LfcsH1"]))
    bullets_q2 = [
        "Concrete pump — TBC: provisional sum if LFCS, by-others if TCE (Tue grill)",
        "Crane / lifting — EXCLUDE (Defensible-for-variations default)",
        "Earthworks: bulk excavation + sub-base + compaction — by TCE / earthworks subbie",
        "Demolition of existing wall (S2.00 east edge + S2.05 Sec 2) — by-others demo subby",
        "DJ drilling + Danley dowel install + Hilti HIT-HY 200 — by-others (typically demo or piling)",
        "Bored piles P1 (32 MPa, 750 dia, 8m socket) — by-others piling subby",
        "<b>VISION-ONLY find (S2.05 Sec 1):</b> 'P1 to be conventionally formed and extended above NGL' — may be LFCS scope. <b>Confirm at grill.</b>",
        "Stormwater sump 1000×1000×1200 — by-others civil contractor",
        "Sludge platform steel framing + Hilti anchor install + grating + handrail — by-others steel installer",
        "Walkway steel framing + Hilti anchors + grating + handrail — by-others steel installer",
        "Existing services relocations (EXIE/EXIW/EXIC/EXIF) — by-others services contractor",
        "<b>BW1 blocklayer — internal LFCS or external blocklayer subbie?</b> 10-15% rate impact. <b>Confirm at grill.</b>",
    ]
    for b in bullets_q2:
        story.append(Paragraph(f"• {b}", s["LfcsBullet"]))

    story.append(Paragraph("Q3 — QA + admin boundaries", s["LfcsH1"]))
    q3_bullets = [
        "Engineer Cert / pour sign-off — LFCS sub-PQP; TCE/Henry & Hymas inspect + cert",
        "ITP per pour — LFCS basic ITP; head-contractor QA chain",
        "Concrete testing (slump + cylinders) — by TCE / TCE concrete supplier",
        "AS3610 Class 3 industrial finish — assumed; if more onerous = variation per Inclusions-Exclusions Q12",
        "Slab finish steel-trowel + walkway broom — assumed; qualified",
        "Curing per S1.01 (within 2hr, min 7 days, AS3799) — LFCS",
        "SWMS for active WWTP — LFCS prepares own",
    ]
    for b in q3_bullets:
        story.append(Paragraph(f"• {b}", s["LfcsBullet"]))

    story.append(Paragraph("Q4 — Programme boundaries", s["LfcsH1"]))
    q4_bullets = [
        "Hours of work: <b>Day shift only (Mon-Fri ordinary)</b> — no night, no weekend",
        "Midnight crossing loading: NOT triggered (no night work)",
        "Site commencement: mid-Aug 2026 per TCE email — ~14 wk lead from award",
        "Programme duration: provisional 6-8 weeks on-site",
        "Pour sequencing: (1) PF1 → (2) main slab + beams → (3) BW1 starters set during slab pour → (4) BW1 blockwork after 80% strength gate (S1.01 C11) → (5) walkway → (6) plinths topped",
        "80% slab strength gate before BW1 blockwork (per S1.01 C11) — ~14 day allowance post-pour",
        "AfC drawings NOT supplied — 90% Design only — variation rate basis covers IFC drift",
    ]
    for b in q4_bullets:
        story.append(Paragraph(f"• {b}", s["LfcsBullet"]))

    story.append(Paragraph("Q5 — Productivity calibration", s["LfcsH1"]))
    q5_table = [
        ["Activity", "Productivity", "Hours basis"],
        ["Concrete pour + place + finish (270mm slab on heavy reo + integrated 500×1200 beams)", "1.45 hr/m³", "4-person crew × 22 m³/day, slowed -10% for irregular pentagon"],
        ["Reo install — heavy density (130-180 kg/m³)", "9.4 hr/T", "Cogged edges, 5N24/8N20 banded zones, beam ties, trim @ 100"],
        ["Mesh fix (SL72 + SL92)", "0.27 hr/m²", "Standard"],
        ["Formwork erect (slab + beam down-stand + edge thickening)", "2 hr/m²", "Suspended slab typical complexity"],
        ["Formwork strip + clean", "1 hr/m²", "Standard"],
        ["BW1 blocklay (190 RC + reo + grout combined)", "1.5 hr/m²", "1 blocklayer + 0.5 labourer"],
        ["Site supervision", "30% of trade hrs", "Rate Card V1.7 supervisor loading"],
    ]
    t = Table(q5_table, colWidths=[80 * mm, 35 * mm, 65 * mm])
    t.setStyle(TableStyle([
        ("BACKGROUND", (0, 0), (-1, 0), LFCS_NAVY),
        ("TEXTCOLOR", (0, 0), (-1, 0), HexColor("#FFFFFF")),
        ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
        ("FONTSIZE", (0, 0), (-1, -1), 8.5),
        ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
        ("GRID", (0, 0), (-1, -1), 0.5, HexColor("#999999")),
        ("ROWBACKGROUNDS", (0, 1), (-1, -1), [HexColor("#FFFFFF"), HexColor("#F2F4F7")]),
    ]))
    story.append(t)

    story.append(Paragraph("Q6 — Commercial posture", s["LfcsH1"]))
    story.append(Paragraph("<b>DEFENSIBLE-FOR-VARIATIONS</b> locked per <i>Defensible-for-variations-posture</i> concept note. Drawings 90% Design (drift to IFC guaranteed) + Eastern Creek scope-split pending Rocky annotation + WWTP active operation standby risk + TCE supply timing risk = pricing posture must absorb future VOs at SAME rate as submission.", s["LfcsBody"]))
    story.append(Paragraph("<b>Rate basis applied:</b><br/>• Trade day shift: $85.00/hr (Rate Card V1.7)<br/>• Supervisor day shift: $95.00/hr<br/>• Materials cost + 10% markup<br/>• Ute & tools $150/day per vehicle<br/>• 8-hr min call-out per working day<br/>• 15% redundancy on subtotal<br/>• Variation rate basis = SAME as submission rate", s["LfcsBody"]))

    story.append(Paragraph("Q7 — Sanity check (auto-flags)", s["LfcsH1"]))
    q7_bullets = [
        f"Bid total ex GST provisional <b>${ex_gst_rounded:,.0f}</b> — within expected $200K-450K range for slab + walkway + BW1 of this size — OK",
        "15% redundancy applied — OK",
        "Reo qty back-calculated only from drawings (no BOQ) — flagged: ±5% reo variance from 90% to IFC = variation per Inclusions-Exclusions Q1",
        "FRP-install pattern (LFCS supply only formwork + curing + consumables) — OK",
        "Defensive Block B exclusions (25 items) — OK",
        "<b>FLAG: Eastern Creek scope-split annotation by Rocky pending</b> — Tue AM before take-off finalised",
        "<b>FLAG: BW1 blocklayer subby vs internal LFCS — Rocky verbal pending</b>",
        "Vision pass partial (5 primary drawings High; civil-context deferred per brief contingency) — OK",
    ]
    for b in q7_bullets:
        story.append(Paragraph(f"• {b}", s["LfcsBullet"]))

    story.append(Paragraph("Open items requiring Rocky's verbal Tuesday AM (pre-pricing-finalised)", s["LfcsH1"]))
    open_bullets = [
        "BW1 blocklayer: internal LFCS or external blocklayer subbie? (10-15% rate impact)",
        "Concrete pump: LFCS provisional sum OR by TCE?",
        "DJ drilling + Danley dowel install: confirm by-others (default) OR LFCS-internal preferred?",
        "P1 pile extension above NGL conventionally formed (S2.05 Sec 1 vision-only): confirm by-others OR LFCS form-up?",
        "Eastern Creek scope-split annotation: Rocky to mark blue + green corner + purple IN; pink + ALL tanks (incl purple) OUT before take-off finalised",
        "Mixed slab support (S2.05 Sec 1 vision-only): which portion suspended-on-formwork vs on-natural-ground-with-vapour-barrier?",
        "Crew composition: 4-person concrete pour crew (3 lads + 1 supervisor)?",
        "Formwork ply: LFCS owns enough stock OR hire-in via Form-Hire / Aluma?",
        "Mob/demob: baked into m²/m³ rates per LFCS area-loading method (no separate line item)",
        "Submission cover signatory: Rocky or Liam?",
    ]
    for b in open_bullets:
        story.append(Paragraph(f"• {b}", s["LfcsBullet"]))

    story.append(Paragraph("Sign-off", s["LfcsH1"]))
    story.append(Paragraph("Pre-Pricing-Grill signed Tuesday 6 May AM verbal: [   ] Rocky McLoughlin   [   ] Liam Fitzgerald   Date: ______________", s["LfcsBody"]))
    story.append(Paragraph("After sign-off, take-off + rate buildup + tender summary finalised; submission to TCE Thursday 7 May PM.", s["LfcsBody"]))

    def hf(canvas, doc):
        lfcs_header_footer(canvas, doc, title, subtitle)

    doc.build(story, onFirstPage=hf, onLaterPages=hf)
    print(f"✓ {out.name}")
    return out


# ─────────────────────────────────────────────────────────────────────────────
# 6. Tender Summary Cover PDF (1-page email body)
# ─────────────────────────────────────────────────────────────────────────────

def build_tender_cover_pdf():
    out = OUT_02A / f"{JOB_NO}-Tender-Summary-Cover_v-CC.pdf"
    doc = SimpleDocTemplate(
        str(out), pagesize=A4,
        leftMargin=15 * mm, rightMargin=15 * mm,
        topMargin=30 * mm, bottomMargin=18 * mm,
    )
    s = make_styles()
    story = []

    story.append(Paragraph(f"Tender Submission — {JOB_NO} {JOB_TITLE}", s["LfcsTitle"]))
    story.append(Paragraph(f"<b>To:</b> {HEAD_CONTRACTOR} ({HEAD_CONTRACTOR_CONTACT}, {HEAD_CONTRACTOR_EMAIL})<br/>"
                          f"<b>End Client:</b> {END_CLIENT}<br/>"
                          f"<b>Site:</b> {SITE}<br/>"
                          f"<b>Designer:</b> {DESIGNER}<br/>"
                          f"<b>Drawings:</b> 250643 Civil Rev 5 (17.04.2026) + 250643 Structural Rev 1 (15.04.2026) — 90% Design<br/>"
                          f"<b>Bid Date:</b> {BID_DATE} (Thu PM) | <b>Programme:</b> Site commence mid-Aug 2026 | <b>Validity:</b> 30 days", s["LfcsBody"]))

    story.append(Paragraph("Tender Total", s["LfcsH1"]))
    total_table = [
        ["Item", "Amount"],
        ["Lump Sum ex GST", f"${ex_gst_rounded:,.2f}"],
        ["GST 10%", f"${gst_rounded:,.2f}"],
        ["GRAND TOTAL inc GST", f"${inc_gst_rounded:,.2f}"],
    ]
    t = Table(total_table, colWidths=[80 * mm, 60 * mm])
    t.setStyle(TableStyle([
        ("BACKGROUND", (0, 0), (-1, 0), LFCS_NAVY),
        ("TEXTCOLOR", (0, 0), (-1, 0), HexColor("#FFFFFF")),
        ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
        ("FONTSIZE", (0, 0), (-1, -1), 11),
        ("BACKGROUND", (0, -1), (-1, -1), LFCS_RED),
        ("TEXTCOLOR", (0, -1), (-1, -1), HexColor("#FFFFFF")),
        ("FONTNAME", (0, -1), (-1, -1), "Helvetica-Bold"),
        ("ALIGN", (1, 0), (1, -1), "RIGHT"),
        ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
        ("GRID", (0, 0), (-1, -1), 0.5, HexColor("#999999")),
        ("LEFTPADDING", (0, 0), (-1, -1), 8),
        ("RIGHTPADDING", (0, 0), (-1, -1), 8),
        ("TOPPADDING", (0, 0), (-1, -1), 6),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 6),
    ]))
    story.append(t)

    story.append(Paragraph("Per-Element Breakdown (Lump Sum)", s["LfcsH1"]))
    breakdown = [
        ["Element", "Description", "Lump Sum ex GST"],
        ["1", "WWTP main slab (270mm @ 40 MPa) + integrated 500×1200 beams + plinths × 7", f"~30-50% of total"],
        ["2", "Walkway concrete pavement (1m × ~55m × 120mm @ 25 MPa, SL72)", f"~10-15% of total"],
        ["3", "Walkway pad footings PF1 × 5 (1000×600×400 @ 32 MPa)", f"~5-8% of total"],
        ["4", "BW1 bund wall (190 RC block × ~48 m², N16-400 EW core, 32 MPa fill)", f"~15-25% of total"],
    ]
    t = Table(breakdown, colWidths=[18 * mm, 105 * mm, 50 * mm])
    t.setStyle(TableStyle([
        ("BACKGROUND", (0, 0), (-1, 0), LFCS_NAVY),
        ("TEXTCOLOR", (0, 0), (-1, 0), HexColor("#FFFFFF")),
        ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
        ("FONTSIZE", (0, 0), (-1, -1), 9),
        ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
        ("GRID", (0, 0), (-1, -1), 0.5, HexColor("#999999")),
        ("ROWBACKGROUNDS", (0, 1), (-1, -1), [HexColor("#FFFFFF"), HexColor("#F2F4F7")]),
    ]))
    story.append(t)
    story.append(Paragraph("<i>Detailed line items + Schedule of Rates + Exclusions + Terms + Dimensions & Assumptions in attached Tender-Summary workbook.</i>", s["LfcsSmall"]))

    story.append(Paragraph("Pricing Posture", s["LfcsH1"]))
    story.append(Paragraph(
        "<b>Defensible-for-variations.</b> Variations + standby + cancellations bill against LFCS Rate Card V1.7 day shift rates ($85/hr trade, $95/hr supervisor) — <b>SAME basis as this submission</b>. "
        "All quantities provisional from 90% Design vision-pass take-off; ±5% qty variance from 100% IFC = variation per Q1 of Inclusions-Exclusions.", s["LfcsBody"]))

    story.append(Paragraph("Pre-conditions to Commencement", s["LfcsH1"]))
    pre = [
        "TCE concrete + reo + block + grout supply chain confirmed",
        "Pile work + DJ install Hold Point released by piling/demo subbies",
        "Walkway DGB20 sub-base + subgrade by-others completed + signed off",
        "Existing services relocations + fence removals signed off",
        "AfC drawings issued for IFC drift assessment OR variation rate basis accepted in writing",
    ]
    for b in pre:
        story.append(Paragraph(f"• {b}", s["LfcsBullet"]))

    story.append(Spacer(1, 6))
    story.append(Paragraph("Signed for and on behalf of LF Construction Services Pty Ltd by Rocky McLoughlin (PM/Super). Submission Cover letter attached.", s["LfcsBody"]))

    def hf(canvas, doc):
        lfcs_header_footer(canvas, doc, "Tender Summary Cover", f"Bid {BID_DATE}")
    doc.build(story, onFirstPage=hf, onLaterPages=hf)
    print(f"✓ {out.name}")
    return out


# ─────────────────────────────────────────────────────────────────────────────
# 7. Inclusions / Exclusions PDF (branded render of vault MD)
# ─────────────────────────────────────────────────────────────────────────────

def build_incl_excl_pdf():
    out = OUT_02A / f"{JOB_NO}-Inclusions-Exclusions_v-CC.pdf"
    doc = SimpleDocTemplate(
        str(out), pagesize=A4,
        leftMargin=15 * mm, rightMargin=15 * mm,
        topMargin=30 * mm, bottomMargin=18 * mm,
    )
    s = make_styles()
    story = []
    title = f"Inclusions / Exclusions / Qualifications — {JOB_NO} {JOB_TITLE}"

    story.append(Paragraph(f"Inclusions / Exclusions / Qualifications", s["LfcsTitle"]))
    story.append(Paragraph(f"<b>Job:</b> {JOB_NO} — {JOB_TITLE}<br/><b>To:</b> {HEAD_CONTRACTOR} ({HEAD_CONTRACTOR_CONTACT})<br/><b>Bid:</b> {BID_DATE} (Thu PM)<br/><b>Reference drawings:</b> 250643 Civil Rev 5 + 250643 Structural Rev 1", s["LfcsBody"]))

    story.append(Paragraph("INCLUSIONS", s["LfcsH1"]))
    story.append(Paragraph("LFCS pricing includes the following per the referenced drawings:", s["LfcsBody"]))
    incl = [
        "<b>Formwork supply, install, strip + clean</b> — to WWTP slab (270mm RC suspended @ 40 MPa, irregular pentagon ~300 m²*), 500mm edge thickening, 7 integrated 500×1200 RC beams, plinths × 7 (100thk @ 40 MPa with SL92 top mesh), walkway pavement edges (1m × ~55m × 120mm @ 25 MPa with SL72 mesh central), walkway pad footings PF1 × 5 (1000×600×400 @ 32 MPa, 5-N16 U + N12-200 ties). AS3610 Class 3 industrial finish assumed.",
        "<b>Reinforcement install (steel-fixing)</b> — N12-300 EW T+B baseline + N16-200 transverse Layer 2 (BTM) + Layer 3 (TOP) + 5N24/8N20/9N24/10N20/10N24 banded zones + 2N12-300 beam ties + cogs at edges + cogs at wall supports + trim reo at openings @ 100 max ctrs (per S1.01 R-series + S2.01). Bar chairs plastic-tipped at A1/A2 / full plastic at external faces.",
        "<b>Concrete pour, place, vibrate, finish, cure</b> — slab + beams (40 MPa) + walkway (25 MPa) + PF1 (32 MPa). Steel-trowel finish on slab + broom finish on walkway assumed UNO. Curing per S1.01: AS3799 compound applied within 2 hrs of pour, min 7 days.",
        "<b>BW1 bund wall</b> — 190 RC block, 1800mm max H, ~32m perimeter wraps 3 slab edges, ~48 m²*, all cores filled @ 32 MPa, N16-400 EW central reo threaded through cores. LFCS labour = block lay + reo install + core grout placement. Block + reo + grout supply by TCE.",
        "<b>Mesh fix</b> — SL72 in walkway pavement + SL92 top in plinths.",
        "<b>Site supervision</b> — full-time on pour + finishing days; part-time on form/reo/block days. Rate Card V1.7 supervisor loading.",
        "<b>Standard plant</b> — barrows, vibrators, screeds, bull floats, helicopter trowel, blocklaying tools, hand tools.",
        "<b>Standard PPE + induction</b> — LFCS personnel standard induction.",
        "<b>Programme + look-ahead</b> — short-form weekly look-ahead during works.",
        "<b>ITP + conformance reporting</b> — basic ITP per S1.01 standards (AS3600 / AS3700 hold points). Engineer cert per pour qualified separately if required.",
        "<b>As-built dimensions</b> — for handover.",
    ]
    for i, b in enumerate(incl, start=1):
        story.append(Paragraph(f"{i}. {b}", s["LfcsBullet"]))

    story.append(PageBreak())
    story.append(Paragraph("EXCLUSIONS", s["LfcsH1"]))
    story.append(Paragraph("LFCS pricing does <b>not</b> include the following:", s["LfcsBody"]))
    excl = [
        "<b>Concrete supply</b> — by TCE (or TCE's nominated readymix supplier) — AS1379 compliant; LFCS receives on docket.",
        "<b>Reinforcement supply</b> — by TCE (LFCS labour install only); AS4671 N-grade deformed bars + SL72/SL92 fabric.",
        "<b>Block + core-fill grout supply</b> for BW1 bund wall — by TCE.",
        "<b>Bored piles P1</b> — by-others piling subby; LFCS work commences after piling complete + tops cleaned + Hold Point sign-off.",
        "<b>Stormwater sump 1000×1000×1200</b> — by-others civil contractor.",
        "<b>Existing wall demolition</b> (partial extent on east edge of slab per S2.00 + S2.05 Sec 2) — by-others demo subby.",
        "<b>Existing slab DJ tie-in</b>: drilling + 20mm Danley square dowel × 400 LG (HDG) install + Hilti HIT-HY 200 chemical anchor — by-others.",
        "<b>Sludge dewatering unit access platform steel framing</b> (PSC1 100×6 SHS + PSC2 150 UC 23 + PSB1 200 UB 22 + PSB2 200 PFC + Webforge A325MSG grating + 16mm rod cross-bracing + handrail) — by-others steel installer.",
        "<b>Hilti chemical anchor install for sludge platform</b> (4-M20 Hilti HAS HDG per PSC1/PSC2 base @ 170mm embed with HIT-HY 200) — by-others steel installer.",
        "<b>Pedestrian walkway steel framing</b> (SB1/SB2 250 PFC + SC1 100×6 SHS + grating + handrail + brackets + welds + cleat plates + cross-bracing) — by-others steel installer.",
        "<b>Hilti chemical anchor install for walkway columns</b> (4-M20 typical at PF1 + 2-M20 leftmost + 2-M20 right-end into pavement edge thickening @ 200mm embed) — by-others steel installer.",
        "<b>Walkway DGB20 100mm sub-base + compacted subgrade</b> (per C103 PAVEMENT TYPE legend) — by-others earthworks.",
        "<b>Existing services relocations</b> (EXIE electricity + EXIW water + EXIC comms + EXIF fence per C103 red-dashed) — by-others services contractor.",
        "<b>All Mechanical work</b> (250643_SK_M101-M106) — by-others.",
        "<b>All Electrical work</b> (250643_SK_E100-E203) — by-others.",
        "<b>All Process / P&amp;ID</b> (250643_SK_C900-C953) — by-others.",
        "<b>Cranage</b> — not allowed for; if required, priced separately or supplied by-others.",
        "<b>Concrete pumping</b> — TBC at grill — provisional sum if LFCS, OR by-others.",
        "<b>Earthworks: bulk excavation + backfill + compaction</b> — by-others.",
        "<b>Out-of-hours work, weekend work, night shift</b> — day shift only (Mon-Fri ordinary). Variation if requested.",
        "<b>Wet-weather standby + downtime</b> — claimable on dayworks rate (Rate Card V1.7, 8-hr min call-out).",
        "<b>Rock or unforeseen ground conditions</b> at PF1 base / under formwork to NGL — rates assume normal soil per Core G Eotech 10.12.25.",
        "<b>Site security, hoarding, fencing, traffic management</b> — by head contractor / Cleanaway.",
        "<b>Confined space entry</b> — only if priced as named items (none identified in scope).",
        "<b>Hazardous area certification / intrinsically-safe equipment</b> — by-others.",
        "<b>Biohazard PPE beyond standard PPE</b> — by-others if site requires (assess at induction).",
        "<b>NCRs, defects, rework due to design changes</b> between 90% Design and 100% IFC — variation.",
        "<b>Delay damages</b> caused by pile sequencing, demo sequencing, by-others trade delays — variation per Rate Card V1.7 dayworks rate.",
        "<b>Survey + setting out + invert level supply</b> — by head contractor.",
        "<b>Spoil + waste disposal</b> — by-others.",
        "<b>Site shed / amenities / first-aid</b> — by head contractor.",
    ]
    for i, b in enumerate(excl, start=1):
        story.append(Paragraph(f"{i}. {b}", s["LfcsBullet"]))

    story.append(PageBreak())
    story.append(Paragraph("QUALIFICATIONS", s["LfcsH1"]))
    quals = [
        "<b>Drawings are 90% DESIGN, NOT For Construction.</b> Civil Rev 5 (17.04.2026) + Structural Rev 1 (15.04.2026). All quantities are LFCS take-off from 90% Design vision-pass; <b>any variation in quantity ≥5% between 90% Design and 100% IFC will be claimed at the rates submitted herein</b>.",
        "<b>Specification per drawing notes (S1.01).</b> Concrete grades per element notes (slab 40, piles 32, BW1 32, walkway 25, PF1 32 MPa). Reinforcement N-grade to AS4671 + SL72/SL92 fabric. AS3610 Class 3 formwork assumed. Steel-trowel slab + broom walkway finishes assumed. Curing AS3799 compound assumed (alternates available if compatibility issue). Concrete cover B1-External (1-50km coast) typical / B2-External (up to 1km) marine. W/C max 0.55. Codes: AS3600 / AS3610 / AS4671 / AS3700 / AS5131 / AS4100 / AS1379 / AS3799 / AS2159 / AS1170. <b>If a separate project specification subsequently issued and governs more onerously than these assumptions, variation applies</b>.",
        "<b>No BOQ supplied — LFCS take-off only</b> (per Rocky verbal 2026-05-04 + TCE email confirms lump-sum quote). <b>If BOQ subsequently supplied with quantities materially different to LFCS take-off, variation applies.</b>",
        "<b>Eastern Creek scope-split pattern</b> (per LFCS historical Eastern Creek WWTP). LFCS scope = <b>blue + green corner + purple sections only</b>. <b>Pink section + ALL tanks (including purple tank) EXCLUDED.</b> Drawings annotated by LFCS attached to submission to mark assumed scope boundary. Any element shown on drawings but outside marked boundary is excluded — claimable as variation if requested post-award.",
        "<b>By-others sequencing assumed reliable.</b> Pile work, demolition, steel framing, services relocations all assumed programmed by TCE without disrupting LFCS pour windows. Any LFCS standby due to by-others delay claimed at Rate Card V1.7 dayworks rate.",
        "<b>Rate validity 30 days</b> from date of submission (concrete + steel pricing volatile).",
        "<b>Rates submitted match variation rates.</b> Per LFCS Defensible-for-variations posture — same labour rates apply to base scope and any future variations. Rate Card V1.7 day shift basis ($85/hr trade, $95/hr supervisor) governs all VOs + standby + cancellations.",
        "<b>Insurances:</b> LFCS holds Public Liability $20M, Workers Comp, and Plant. Certificates available on request.",
        "<b>Security of Payment regime:</b> Standard NSW SoP Act 1999 progress claim cycle assumed.",
        "<b>No retention</b> included in price unless TCE confirms standard 5%/2.5% release cycle applies.",
        "<b>Programme assumed mid-Aug 2026 commence</b> per TCE email — if delayed >60 days from award, rate revalidation may apply.",
        "<b>TCE supply chain convention</b> per 2602 Munmorah Bridge precedent: TCE supplies concrete + reo + blocks + grout; LFCS install/place/build only.",
        "<b>Mixed slab support</b> (vision-only find from S2.05 Sec 1): half slab on conventional formwork over piles, half on lightly compacted natural ground with vapour barrier. Pricing reflects 50/50 split assumption — confirm at award; ±10% split variance handled at submission rate.",
        "<b>P1 pile extension above NGL</b> (S2.05 Sec 1: 'P1 to be conventionally formed and extended above NGL'): assumed by-others piling. If LFCS scope, additional formwork + labour priced as variation.",
    ]
    for i, b in enumerate(quals, start=1):
        story.append(Paragraph(f"Q{i}. {b}", s["LfcsBullet"]))

    def hf(canvas, doc):
        lfcs_header_footer(canvas, doc, "Inclusions/Exclusions/Qualifications", f"Bid {BID_DATE}")
    doc.build(story, onFirstPage=hf, onLaterPages=hf)
    print(f"✓ {out.name}")
    return out


# ─────────────────────────────────────────────────────────────────────────────
# 8. Submission Cover Letter PDF
# ─────────────────────────────────────────────────────────────────────────────

def build_submission_cover_pdf():
    out = OUT_02A / f"{JOB_NO}-Submission-Cover_v-CC.pdf"
    doc = SimpleDocTemplate(
        str(out), pagesize=A4,
        leftMargin=20 * mm, rightMargin=20 * mm,
        topMargin=30 * mm, bottomMargin=18 * mm,
    )
    s = make_styles()
    story = []
    today = date.today().strftime("%d %B %Y")

    story.append(Paragraph("LF Construction Services Pty Ltd", s["LfcsTitle"]))
    story.append(Paragraph("ABN 33 656 781 459 &nbsp;|&nbsp; info@lfcs.com.au", s["LfcsSmall"]))
    story.append(Spacer(1, 16))

    story.append(Paragraph(f"{today}", s["LfcsBody"]))
    story.append(Spacer(1, 12))

    story.append(Paragraph(
        f"<b>{HEAD_CONTRACTOR_CONTACT}</b><br/>"
        f"<b>{HEAD_CONTRACTOR}</b><br/>"
        f"{HEAD_CONTRACTOR_EMAIL}", s["LfcsBody"]))
    story.append(Spacer(1, 14))

    story.append(Paragraph("Re: Tender Submission — Wastewater Treatment Plant Upgrade, 4 Quarry Road, Erskine Park NSW (Cleanaway Pty Ltd)", s["LfcsH2"]))
    story.append(Spacer(1, 6))

    story.append(Paragraph("Dear Kirolos,", s["LfcsBody"]))
    story.append(Spacer(1, 6))

    story.append(Paragraph(
        f"Further to your email of 2026-05-04 inviting LF Construction Services to provide a price for the concrete slab, walkway and bund wall on the Wastewater Treatment Plant Upgrade at 4 Quarry Road, Erskine Park, please find enclosed our lump-sum tender submission for these works.",
        s["LfcsBody"]))

    story.append(Paragraph("Tender Summary", s["LfcsH2"]))
    summary_table = [
        ["Item", "Amount"],
        ["Lump Sum ex GST", f"${ex_gst_rounded:,.2f}"],
        ["GST 10%", f"${gst_rounded:,.2f}"],
        ["Grand Total inc GST", f"${inc_gst_rounded:,.2f}"],
    ]
    t = Table(summary_table, colWidths=[80 * mm, 60 * mm])
    t.setStyle(TableStyle([
        ("BACKGROUND", (0, 0), (-1, 0), LFCS_NAVY),
        ("TEXTCOLOR", (0, 0), (-1, 0), HexColor("#FFFFFF")),
        ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
        ("FONTSIZE", (0, 0), (-1, -1), 10),
        ("BACKGROUND", (0, -1), (-1, -1), LFCS_RED),
        ("TEXTCOLOR", (0, -1), (-1, -1), HexColor("#FFFFFF")),
        ("FONTNAME", (0, -1), (-1, -1), "Helvetica-Bold"),
        ("ALIGN", (1, 0), (1, -1), "RIGHT"),
        ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
        ("GRID", (0, 0), (-1, -1), 0.5, HexColor("#999999")),
        ("LEFTPADDING", (0, 0), (-1, -1), 8),
        ("RIGHTPADDING", (0, 0), (-1, -1), 8),
        ("TOPPADDING", (0, 0), (-1, -1), 6),
        ("BOTTOMPADDING", (0, 0), (-1, -1), 6),
    ]))
    story.append(t)

    story.append(Paragraph("Scope", s["LfcsH2"]))
    story.append(Paragraph(
        "Our scope per your email and the Henry &amp; Hymas 90% Design drawings (Civil Rev 5 17.04.2026; Structural Rev 1 15.04.2026) comprises:", s["LfcsBody"]))
    scope_bullets = [
        "WWTP main slab (270mm RC suspended @ 40 MPa, irregular pentagon, ~300 m² provisional) + integrated 500×1200 RC beams × 7 + plinths × 7 (100thk @ 40 MPa with SL92 top)",
        "Pedestrian walkway concrete pavement (1m wide × ~55m × 120mm @ 25 MPa with SL72 mesh central per civil C103) + edge thickening at steel walkway terminus",
        "Walkway pad footings PF1 × 5 (1000×600×400 @ 32 MPa, 5-N16 U-bars top + bottom + N12-200 ties)",
        "BW1 bund wall (190 RC block, 1800mm max H, ~32m perimeter wraps 3 slab edges, all cores filled @ 32 MPa, N16-400 EW central)",
    ]
    for b in scope_bullets:
        story.append(Paragraph(f"• {b}", s["LfcsBullet"]))

    story.append(Paragraph(
        "All concrete, reinforcement, blocks and core-fill grout are by your supply (TCE supply chain convention). LFCS provides labour-only on these items, plus formwork supply, curing compound, and small consumables. "
        "Bored piles P1, the stormwater sump, sludge platform and walkway steel framing, existing wall demolition, existing services relocations, and all P&amp;ID / Mechanical / Electrical works are excluded as by-others. The full Inclusions / Exclusions / Qualifications schedule is attached.", s["LfcsBody"]))

    story.append(Paragraph("Pricing Posture", s["LfcsH2"]))
    story.append(Paragraph(
        "Submission rates align with LFCS Rate Card V1.7 day shift ($85/hr trade, $95/hr supervisor). "
        "<b>Variations + standby + cancellations bill against the same rate basis as this submission</b>, so any drift between 90% Design and 100% IFC, or any LFCS standby caused by by-others sequencing, will be priced consistently with the rates herein. "
        "Rates valid 30 days from submission. Programme assumed mid-Aug 2026 commencement per your email.", s["LfcsBody"]))

    story.append(Paragraph("Pre-conditions to Commencement", s["LfcsH2"]))
    pre = [
        "TCE concrete + reo + block + grout supply chain confirmed",
        "Pile work + DJ install Hold Point released by piling and demolition subbies",
        "Walkway DGB20 sub-base + compacted subgrade by-others completed and signed off",
        "Existing services relocations + fence removals signed off",
        "AfC drawings issued for IFC drift assessment, OR variation rate basis accepted in writing",
    ]
    for b in pre:
        story.append(Paragraph(f"• {b}", s["LfcsBullet"]))

    story.append(Paragraph("Enclosed", s["LfcsH2"]))
    story.append(Paragraph(
        "1. Tender Summary workbook (.xlsx) — Cover, Lump Sum, Schedule of Rates, Exclusions, Terms, Dimensions &amp; Assumptions<br/>"
        "2. Inclusions / Exclusions / Qualifications schedule (.pdf)<br/>"
        "3. Tender Summary cover (.pdf — for email body)", s["LfcsBody"]))

    story.append(Spacer(1, 10))
    story.append(Paragraph("We look forward to your feedback. Please call if you'd like to discuss any aspect of the submission ahead of award.", s["LfcsBody"]))
    story.append(Spacer(1, 12))
    story.append(Paragraph("Yours faithfully,", s["LfcsBody"]))
    story.append(Spacer(1, 28))
    story.append(Paragraph("<b>Rocky McLoughlin</b><br/>PM / Site Supervisor<br/>LF Construction Services Pty Ltd", s["LfcsBody"]))

    def hf(canvas, doc):
        lfcs_header_footer(canvas, doc, "Submission Cover Letter", f"To {HEAD_CONTRACTOR}")
    doc.build(story, onFirstPage=hf, onLaterPages=hf)
    print(f"✓ {out.name}")
    return out


# ─────────────────────────────────────────────────────────────────────────────
# Main
# ─────────────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    import sys
    sys.stdout.reconfigure(encoding="utf-8")
    print(f"\nBuilding {JOB_NO} pricing pack -- outputs to:")
    print(f"  02a -> {OUT_02A}")
    print(f"  02b -> {OUT_02B}\n")
    print(f"Tender summary computed:")
    print(f"  Trade hours: {round(trade_hrs, 1):,}  |  Supervisor hours: {round(sup_hrs, 1):,}")
    print(f"  Labour total: ${labour_total:,.0f}  |  Materials total: ${materials_total:,.0f}")
    print(f"  Subtotal pre-redundancy: ${subtotal:,.0f}")
    print(f"  + 15% redundancy: ${redundancy:,.0f}")
    print(f"  = Subtotal ex GST (raw): ${ex_gst:,.2f}")
    print(f"  Tender ex GST (rounded $50): ${ex_gst_rounded:,.2f}")
    print(f"  GST 10%: ${gst_rounded:,.2f}")
    print(f"  GRAND TOTAL inc GST: ${inc_gst_rounded:,.2f}\n")

    build_grill_pdf()
    build_takeoff()
    build_rate_buildup()
    build_tender_summary()
    build_tender_cover_pdf()
    build_incl_excl_pdf()
    build_submission_cover_pdf()

    print(f"\n✓ All deliverables built. Total = 7 files (3 xlsx + 4 pdf).")
