#!/usr/bin/env python3 """Generate src/data/baseline.ts from the parsed Candy barchart dump.""" import json, datetime, re, textwrap from collections import OrderedDict acts = json.load(open("/tmp/mega_acts.json")) by = {a["code"]: a for a in acts} # ---- ordering: roots in document order, children in document order ------- roots = [a["code"] for a in acts if a["parent"] is None] def walk(code, depth, path): a = by[code] a["level"] = depth a["path"] = path for c in a["children"]: walk(c, depth + 1, path + [code]) for r in roots: walk(r, 0, []) # ---- typo corrections (display only; originalName is preserved) ---------- FIX = [ ("Mega Infrastructure AI Intergration", "Mega Infrastructure AI Integration"), ("CCS Intergration", "CCS Integration"), ("Training & Cources", "Training & Courses"), ("Drivers license & PDP", "Drivers licence & PDP"), ("All sites income & Expences", "All sites income & Expenses"), ("All sites combined retetion", "All sites combined retention"), ("All sites combined Expences", "All sites combined Expenses"), ("All sites combined material expences", "All sites combined material expenses"), ("Combined supplier expences", "Combined supplier expenses"), ("Head office fixed expences", "Head office fixed expenses"), ("Expences per week", "Expenses per week"), ("Expences per month total to date", "Expenses per month total to date"), ("Opperator certificate", "Operator certificate"), ("Opperator", "Operator"), ("Expences", "Expenses"), ("VaT allocation", "VAT allocation"), ] for a in acts: a["originalName"] = a["name"] n = a["name"] for bad, good in FIX: if n == bad: n = good break if bad in n: n = n.replace(bad, good) a["displayName"] = n # ---- quality analysis ---------------------------------------------------- def wd(a, b): d, n = a, 0 while d <= b: if d.weekday() < 5: n += 1 d += datetime.timedelta(days=1) return n def dt(s): return datetime.date.fromisoformat(s) for a in acts: s, f = dt(a["start"]), dt(a["finish"]) a["spanDays"] = wd(s, f) a["durMismatch"] = (a["duration"] != a["spanDays"]) # stretched-window cluster: leaves sharing an identical window that is ~2x their declared duration from collections import defaultdict win = defaultdict(list) for a in acts: win[(a["start"], a["finish"])].append(a["code"]) STRETCHED = set() for k, v in win.items(): if len(v) < 2: continue for c in v: a = by[c] if a["durMismatch"] and a["duration"] > 0 and a["spanDays"] > a["duration"] * 1.5: STRETCHED.add(c) for a in acts: a["unconfirmed"] = "?" in a["name"] a["stretched"] = a["code"] in STRETCHED a["typoFixed"] = a["displayName"] != a["originalName"] a["workstream"] = a["path"][1] if len(a["path"]) >= 2 else None PROG = { "A000": "Mega Infrastructure AI Integration", "B110": "Mega Plant", } def q(s): return json.dumps(s).replace("\\", "\\\\") if False else json.dumps(s) out = [] out.append("// AUTO-GENERATED from 'Mega Infrastructure AI intergration system.pdf'") out.append("// (Candy 2.05p / P6 barchart export, printed 30 Sep 2026).") out.append("// 100 activities, 2 programmes, 06 Oct 2026 -> 31 Aug 2027.") out.append("// Hierarchy comes from PDF indentation -- NOT from the activity code.") out.append("// Regenerate with scripts/gen_baseline.py if the source PDF changes.") out.append("") out.append("import type { BaselineActivity } from '../types'") out.append("") out.append("export const SOURCE_PDF = 'Mega Infrastructure AI intergration system.pdf'") out.append("export const SOURCE_PRINTED = '30 September 2026 13:00'") out.append("") out.append("export const PROGRAMMES = [") for r in roots: a = by[r] out.append(" {") out.append(f" code: {q(a['code'])},") out.append(f" name: {q(PROG[a['code']])},") out.append(f" originalName: {q(a['originalName'])},") out.append(f" start: {q(a['start'])},") out.append(f" finish: {q(a['finish'])},") out.append(" },") out.append("] as const") out.append("") out.append("export const BASELINE: BaselineActivity[] = [") for a in acts: flags = [] if a["unconfirmed"]: flags.append("unconfirmed") if a["stretched"]: flags.append("stretched") if a["durMismatch"]: flags.append("duration-mismatch") if a["typoFixed"]: flags.append("name-corrected") out.append(" {") out.append(f" code: {q(a['code'])},") out.append(f" name: {q(a['displayName'])},") out.append(f" originalName: {q(a['originalName'])},") out.append(f" parent: {q(a['parent'])},") out.append(f" level: {a['level']},") out.append(f" plannedDuration: {a['duration']},") out.append(f" spanDays: {a['spanDays']},") out.append(f" start: {q(a['start'])},") out.append(f" finish: {q(a['finish'])},") out.append(f" flags: {json.dumps(flags)},") out.append(" },") out.append("]") out.append("") src = "\n".join(out) open("/tmp/baseline.ts", "w").write(src) print(f"generated {len(acts)} activities, {len(src)} bytes") print(f"flags: unconfirmed={sum(1 for a in acts if a['unconfirmed'])} " f"stretched={sum(1 for a in acts if a['stretched'])} " f"durMismatch={sum(1 for a in acts if a['durMismatch'])} " f"nameCorrected={sum(1 for a in acts if a['typoFixed'])}")