|
| 1 | +#!/usr/bin/env python3 |
| 2 | +""" |
| 3 | +ENT-AGI-GOV-MASTER-WP-035 — HTML Dashboard Renderer |
| 4 | +Generates: public/ent-agi-gov-master.html |
| 5 | +""" |
| 6 | + |
| 7 | +import json |
| 8 | +import html as htmllib |
| 9 | +from pathlib import Path |
| 10 | + |
| 11 | +HERE = Path(__file__).parent |
| 12 | +SRC = HERE / "data" / "ent-agi-gov-master.json" |
| 13 | +OUT = HERE / "public" / "ent-agi-gov-master.html" |
| 14 | + |
| 15 | +MODULE_ORDER = [ |
| 16 | + "M1_pillars", |
| 17 | + "M2_regulatory", |
| 18 | + "M3_architectures", |
| 19 | + "M4_safety", |
| 20 | + "M5_civilizational", |
| 21 | + "M6_financialMrm", |
| 22 | + "M7_kafkaGac", |
| 23 | + "M8_roadmap", |
| 24 | +] |
| 25 | + |
| 26 | + |
| 27 | +def esc(v): |
| 28 | + if v is None: |
| 29 | + return "" |
| 30 | + if isinstance(v, bool): |
| 31 | + return "true" if v else "false" |
| 32 | + return htmllib.escape(str(v)) |
| 33 | + |
| 34 | + |
| 35 | +def kv_table(d): |
| 36 | + rows = "".join( |
| 37 | + f"<tr><td class='k'>{esc(k)}</td><td class='v'>{render_value(v)}</td></tr>" |
| 38 | + for k, v in d.items() |
| 39 | + ) |
| 40 | + return f"<table class='kv'>{rows}</table>" |
| 41 | + |
| 42 | + |
| 43 | +def render_value(v): |
| 44 | + if isinstance(v, dict): |
| 45 | + return kv_table(v) |
| 46 | + if isinstance(v, list): |
| 47 | + if not v: |
| 48 | + return "<em>—</em>" |
| 49 | + if all(isinstance(x, (str, int, float, bool)) for x in v): |
| 50 | + return "<ul>" + "".join(f"<li>{esc(x)}</li>" for x in v) + "</ul>" |
| 51 | + if all(isinstance(x, dict) for x in v): |
| 52 | + keys = [] |
| 53 | + for d in v: |
| 54 | + for k in d.keys(): |
| 55 | + if k not in keys: |
| 56 | + keys.append(k) |
| 57 | + head = "".join(f"<th>{esc(k)}</th>" for k in keys) |
| 58 | + body = "" |
| 59 | + for d in v: |
| 60 | + body += "<tr>" + "".join( |
| 61 | + f"<td>{render_value(d.get(k, ''))}</td>" for k in keys |
| 62 | + ) + "</tr>" |
| 63 | + return ( |
| 64 | + f"<table class='grid'><thead><tr>{head}</tr></thead>" |
| 65 | + f"<tbody>{body}</tbody></table>" |
| 66 | + ) |
| 67 | + return "<ul>" + "".join(f"<li>{render_value(x)}</li>" for x in v) + "</ul>" |
| 68 | + return esc(v) |
| 69 | + |
| 70 | + |
| 71 | +def render_section(sec): |
| 72 | + sid = sec.get("id", "") |
| 73 | + title = sec.get("title", "") |
| 74 | + html = [f"<div class='section' id='{esc(sid)}'>"] |
| 75 | + html.append(f"<h3>{esc(sid)} · {esc(title)}</h3>") |
| 76 | + for key, val in sec.items(): |
| 77 | + if key in ("id", "title"): |
| 78 | + continue |
| 79 | + html.append( |
| 80 | + f"<div class='sub'><h4>{esc(key)}</h4>{render_value(val)}</div>" |
| 81 | + ) |
| 82 | + html.append("</div>") |
| 83 | + return "\n".join(html) |
| 84 | + |
| 85 | + |
| 86 | +def render_module(mod): |
| 87 | + mid = mod.get("id", "") |
| 88 | + title = mod.get("title", "") |
| 89 | + summary = mod.get("summary", "") |
| 90 | + sections = mod.get("sections", []) or [] |
| 91 | + html = [f"<section class='module' id='{esc(mid)}'>"] |
| 92 | + html.append(f"<h2>{esc(mid)} · {esc(title)}</h2>") |
| 93 | + if summary: |
| 94 | + html.append(f"<p class='summary'>{esc(summary)}</p>") |
| 95 | + for sec in sections: |
| 96 | + html.append(render_section(sec)) |
| 97 | + html.append("</section>") |
| 98 | + return "\n".join(html) |
| 99 | + |
| 100 | + |
| 101 | +def main(): |
| 102 | + data = json.loads(SRC.read_text(encoding="utf-8")) |
| 103 | + meta = data["meta"] |
| 104 | + exec_sum = data["executiveSummary"] |
| 105 | + |
| 106 | + modules = [data[k] for k in MODULE_ORDER if k in data] |
| 107 | + |
| 108 | + toc_items = "".join( |
| 109 | + f"<li><a href='#{esc(m['id'])}'>{esc(m['id'])} · {esc(m['title'].split('—')[-1].strip()[:46])}</a></li>" |
| 110 | + for m in modules |
| 111 | + ) |
| 112 | + toc_items += ( |
| 113 | + "<li><a href='#schemas'>Schemas</a></li>" |
| 114 | + "<li><a href='#code-examples'>Code Examples</a></li>" |
| 115 | + "<li><a href='#case-studies'>Case Studies</a></li>" |
| 116 | + "<li><a href='#regulatory-matrix'>Regulatory Alignment</a></li>" |
| 117 | + "<li><a href='#api'>API Endpoints</a></li>" |
| 118 | + ) |
| 119 | + |
| 120 | + modules_html = "\n".join(render_module(m) for m in modules) |
| 121 | + |
| 122 | + schemas_html = "" |
| 123 | + for name, sch in data.get("schemas", {}).items(): |
| 124 | + schemas_html += ( |
| 125 | + f"<details><summary>{esc(name)}</summary>" |
| 126 | + f"<pre><code>{esc(json.dumps(sch, indent=2))}</code></pre></details>" |
| 127 | + ) |
| 128 | + |
| 129 | + code_html = "" |
| 130 | + for name, code in data.get("codeExamples", {}).items(): |
| 131 | + code_html += ( |
| 132 | + f"<details><summary>{esc(name)}</summary>" |
| 133 | + f"<pre><code>{esc(code)}</code></pre></details>" |
| 134 | + ) |
| 135 | + |
| 136 | + cs_html = "" |
| 137 | + for cs in data.get("caseStudies", []): |
| 138 | + outcomes = cs.get("outcomes", {}) |
| 139 | + outcomes_html = ( |
| 140 | + kv_table(outcomes) if isinstance(outcomes, dict) |
| 141 | + else render_value(outcomes) |
| 142 | + ) |
| 143 | + cs_html += ( |
| 144 | + f"<div class='case'><h3>{esc(cs.get('id',''))} · {esc(cs.get('title',''))}</h3>" |
| 145 | + f"<p><strong>Sector:</strong> {esc(cs.get('sector',''))}</p>" |
| 146 | + f"<p>{esc(cs.get('summary',''))}</p>" |
| 147 | + f"<div class='sub'><h4>Outcomes</h4>{outcomes_html}</div>" |
| 148 | + "</div>" |
| 149 | + ) |
| 150 | + |
| 151 | + reg = meta.get("regulatoryAlignment", []) |
| 152 | + if isinstance(reg, list): |
| 153 | + reg_html = "<ul>" + "".join(f"<li>{esc(r)}</li>" for r in reg) + "</ul>" |
| 154 | + else: |
| 155 | + reg_html = esc(reg) |
| 156 | + |
| 157 | + audience = meta.get("audience", []) |
| 158 | + audience_html = ( |
| 159 | + "<ul>" + "".join(f"<li>{esc(a)}</li>" for a in audience) + "</ul>" |
| 160 | + if isinstance(audience, list) else esc(audience) |
| 161 | + ) |
| 162 | + |
| 163 | + horizon = meta.get("horizonMilestones", {}) |
| 164 | + horizon_html = kv_table(horizon) if isinstance(horizon, dict) else esc(horizon) |
| 165 | + |
| 166 | + inv = meta.get("deliverableInventory", {}) |
| 167 | + inv_html = kv_table(inv) if isinstance(inv, dict) else esc(inv) |
| 168 | + |
| 169 | + api = data.get("apiEndpoints", {"prefix": "/api/ent-agi-gov-master", "routes": []}) |
| 170 | + api_items = "".join( |
| 171 | + f"<li><code>{esc(api['prefix'])}{esc(r)}</code></li>" |
| 172 | + for r in api.get("routes", []) |
| 173 | + ) |
| 174 | + |
| 175 | + n_modules = len(modules) |
| 176 | + total_sections = sum(len(m.get("sections", []) or []) for m in modules) |
| 177 | + n_schemas = len(data.get("schemas", {})) |
| 178 | + n_code = len(data.get("codeExamples", {})) |
| 179 | + n_cs = len(data.get("caseStudies", [])) |
| 180 | + n_routes = len(api.get("routes", [])) |
| 181 | + |
| 182 | + page = f"""<!doctype html> |
| 183 | +<html lang="en"> |
| 184 | +<head> |
| 185 | +<meta charset="utf-8" /> |
| 186 | +<meta name="viewport" content="width=device-width,initial-scale=1" /> |
| 187 | +<title>{esc(meta.get('docRef',''))} — {esc(meta.get('title',''))}</title> |
| 188 | +<meta name="description" content="{esc(meta.get('subtitle',''))}" /> |
| 189 | +<style> |
| 190 | + :root {{ |
| 191 | + --bg:#070b1a; --panel:#0f1734; --panel2:#121d40; --fg:#eaf0fb; --muted:#8aa0c2; |
| 192 | + --accent:#7cc6ff; --accent2:#b693ff; --accent3:#ff9ec7; --line:#1d2a52; |
| 193 | + --ok:#58f0a7; --warn:#ffcb6b; --crit:#ff7a7a; |
| 194 | + }} |
| 195 | + * {{ box-sizing:border-box; }} |
| 196 | + body {{ margin:0; background:var(--bg); color:var(--fg); |
| 197 | + font:14px/1.55 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Inter,sans-serif; }} |
| 198 | + header.hero {{ padding:34px 38px; |
| 199 | + background:linear-gradient(135deg,#1a2566,#0a1235 55%,#070b1a); |
| 200 | + border-bottom:1px solid var(--line); }} |
| 201 | + header.hero .doc-ref {{ color:var(--accent3); font-size:12px; letter-spacing:1px; |
| 202 | + text-transform:uppercase; margin-bottom:6px; }} |
| 203 | + header.hero h1 {{ margin:0 0 10px; font-size:25px; letter-spacing:.2px; }} |
| 204 | + header.hero .subtitle {{ color:var(--muted); max-width:1100px; }} |
| 205 | + header.hero .badges {{ margin-top:16px; display:flex; gap:8px; flex-wrap:wrap; }} |
| 206 | + header.hero .badge {{ background:#19255a; color:var(--accent); padding:5px 12px; |
| 207 | + border-radius:999px; font-size:12px; border:1px solid var(--line); }} |
| 208 | + header.hero .badge.warn {{ color:var(--warn); border-color:#5a3f1a; background:#2a1f0d; }} |
| 209 | + header.hero .badge.ok {{ color:var(--ok); border-color:#1a4a31; background:#0c2a1c; }} |
| 210 | + header.hero .kpis {{ margin-top:18px; display:flex; gap:18px; flex-wrap:wrap; }} |
| 211 | + header.hero .kpi {{ background:#0d1532; border:1px solid var(--line); border-radius:10px; |
| 212 | + padding:10px 14px; min-width:130px; }} |
| 213 | + header.hero .kpi .v {{ font-size:20px; color:var(--accent2); font-weight:600; }} |
| 214 | + header.hero .kpi .l {{ font-size:11px; color:var(--muted); text-transform:uppercase; |
| 215 | + letter-spacing:.6px; }} |
| 216 | + nav.toc {{ position:sticky; top:0; background:#070b1aee; backdrop-filter:blur(8px); |
| 217 | + border-bottom:1px solid var(--line); padding:10px 20px; z-index:5; }} |
| 218 | + nav.toc ul {{ list-style:none; margin:0; padding:0; display:flex; flex-wrap:wrap; gap:6px; }} |
| 219 | + nav.toc li a {{ color:var(--accent); text-decoration:none; padding:4px 10px; |
| 220 | + border:1px solid var(--line); border-radius:6px; font-size:12px; }} |
| 221 | + nav.toc li a:hover {{ background:#141e44; }} |
| 222 | + main {{ padding:24px 36px 80px; max-width:1340px; }} |
| 223 | + section.module {{ background:var(--panel); border:1px solid var(--line); |
| 224 | + border-radius:12px; padding:22px 24px; margin:20px 0; }} |
| 225 | + section.module h2 {{ margin:0 0 8px; color:var(--accent2); font-size:18px; }} |
| 226 | + section.module .summary {{ color:var(--muted); margin:0 0 16px; }} |
| 227 | + .section {{ border-top:1px dashed var(--line); padding-top:14px; margin-top:14px; }} |
| 228 | + .section h3 {{ margin:0 0 8px; font-size:15px; color:var(--accent); }} |
| 229 | + .section .sub {{ margin:10px 0; }} |
| 230 | + .section .sub h4 {{ margin:0 0 6px; font-size:12px; color:var(--muted); |
| 231 | + text-transform:uppercase; letter-spacing:.5px; }} |
| 232 | + table.kv, table.grid {{ width:100%; border-collapse:collapse; font-size:13px; |
| 233 | + background:#0d1532; border:1px solid var(--line); |
| 234 | + border-radius:6px; overflow:hidden; }} |
| 235 | + table.kv td, table.grid td, table.grid th {{ padding:6px 10px; |
| 236 | + border-bottom:1px solid var(--line); |
| 237 | + vertical-align:top; text-align:left; }} |
| 238 | + table.kv td.k {{ color:var(--muted); width:30%; white-space:nowrap; }} |
| 239 | + table.grid th {{ background:#162248; color:var(--accent); font-weight:600; }} |
| 240 | + ul {{ margin:6px 0 6px 20px; padding:0; }} |
| 241 | + code {{ background:#0d1532; padding:1px 5px; border-radius:4px; color:var(--accent); }} |
| 242 | + pre {{ background:#0d1532; padding:12px; border-radius:6px; overflow:auto; |
| 243 | + border:1px solid var(--line); font-size:12px; }} |
| 244 | + details {{ background:#0d1532; border:1px solid var(--line); border-radius:6px; |
| 245 | + padding:8px 12px; margin:8px 0; }} |
| 246 | + details summary {{ cursor:pointer; color:var(--accent); }} |
| 247 | + .case {{ border-top:1px dashed var(--line); padding-top:14px; margin-top:14px; }} |
| 248 | + .case h3 {{ margin:0 0 8px; color:var(--accent3); font-size:15px; }} |
| 249 | + footer {{ color:var(--muted); border-top:1px solid var(--line); |
| 250 | + padding:16px 36px; font-size:12px; }} |
| 251 | +</style> |
| 252 | +</head> |
| 253 | +<body> |
| 254 | +<header class="hero"> |
| 255 | + <div class="doc-ref">{esc(meta.get('docRef',''))} · {esc(meta.get('classification',''))}</div> |
| 256 | + <h1>{esc(meta.get('title',''))}</h1> |
| 257 | + <p class="subtitle">{esc(meta.get('subtitle',''))}</p> |
| 258 | + <div class="badges"> |
| 259 | + <span class="badge">Version {esc(meta.get('version',''))}</span> |
| 260 | + <span class="badge">Date {esc(meta.get('date',''))}</span> |
| 261 | + <span class="badge">Horizon {esc(meta.get('horizon',''))}</span> |
| 262 | + <span class="badge warn">EU AI Act</span> |
| 263 | + <span class="badge warn">SR 11-7 Tier 1</span> |
| 264 | + <span class="badge">NIST AI RMF 1.0</span> |
| 265 | + <span class="badge">ISO/IEC 42001</span> |
| 266 | + <span class="badge ok">Basel III/IV · ICAAP</span> |
| 267 | + <span class="badge ok">FCRA / ECOA</span> |
| 268 | + </div> |
| 269 | + <div class="kpis"> |
| 270 | + <div class="kpi"><div class="v">{n_modules}</div><div class="l">Modules</div></div> |
| 271 | + <div class="kpi"><div class="v">{total_sections}</div><div class="l">Sections</div></div> |
| 272 | + <div class="kpi"><div class="v">7</div><div class="l">Pillars (G1-G7)</div></div> |
| 273 | + <div class="kpi"><div class="v">16</div><div class="l">Regulatory Axes</div></div> |
| 274 | + <div class="kpi"><div class="v">9</div><div class="l">Reference Architectures</div></div> |
| 275 | + <div class="kpi"><div class="v">8</div><div class="l">Safety Protocols</div></div> |
| 276 | + <div class="kpi"><div class="v">{n_schemas}</div><div class="l">Schemas</div></div> |
| 277 | + <div class="kpi"><div class="v">{n_code}</div><div class="l">Code Examples</div></div> |
| 278 | + <div class="kpi"><div class="v">{n_cs}</div><div class="l">Case Studies</div></div> |
| 279 | + <div class="kpi"><div class="v">{n_routes}</div><div class="l">API Routes</div></div> |
| 280 | + </div> |
| 281 | +</header> |
| 282 | +<nav class="toc"><ul>{toc_items}</ul></nav> |
| 283 | +<main> |
| 284 | + <section class="module" id="exec"> |
| 285 | + <h2>Executive Summary</h2> |
| 286 | + {kv_table(exec_sum)} |
| 287 | + </section> |
| 288 | +
|
| 289 | + <section class="module" id="meta"> |
| 290 | + <h2>Document Metadata</h2> |
| 291 | + {kv_table({k: v for k, v in meta.items() |
| 292 | + if k not in ('audience', 'regulatoryAlignment', |
| 293 | + 'horizonMilestones', 'deliverableInventory')})} |
| 294 | + <div class="section" id="audience"> |
| 295 | + <h3>Audience</h3> |
| 296 | + {audience_html} |
| 297 | + </div> |
| 298 | + <div class="section" id="horizon"> |
| 299 | + <h3>Horizon Milestones (2026-2030)</h3> |
| 300 | + {horizon_html} |
| 301 | + </div> |
| 302 | + <div class="section" id="inventory"> |
| 303 | + <h3>Deliverable Inventory</h3> |
| 304 | + {inv_html} |
| 305 | + </div> |
| 306 | + </section> |
| 307 | +
|
| 308 | + {modules_html} |
| 309 | +
|
| 310 | + <section class="module" id="regulatory-matrix"> |
| 311 | + <h2>Regulatory Alignment (Headline)</h2> |
| 312 | + <p class="summary">Master crosswalk lives in <code>M2 — Regulatory Alignment Matrix</code>; the headline list of 16 axes:</p> |
| 313 | + {reg_html} |
| 314 | + </section> |
| 315 | +
|
| 316 | + <section class="module" id="schemas"> |
| 317 | + <h2>JSON Schemas</h2> |
| 318 | + <p class="summary">{n_schemas} schemas covering governance artefacts, compute registry, model risk records, fairness reports, policy decisions, treaty disclosures.</p> |
| 319 | + {schemas_html} |
| 320 | + </section> |
| 321 | +
|
| 322 | + <section class="module" id="code-examples"> |
| 323 | + <h2>Code Examples</h2> |
| 324 | + <p class="summary">{n_code} reference implementations: OPA/Rego policies, Terraform GaC modules, Merkle WORM audit, CI/CD pipeline, governance sidecar, fairness gate, kinetic kill-switch, regulator report templates.</p> |
| 325 | + {code_html} |
| 326 | + </section> |
| 327 | +
|
| 328 | + <section class="module" id="case-studies"> |
| 329 | + <h2>Case Studies</h2> |
| 330 | + <p class="summary">{n_cs} reference deployments across G-SIFI, Fortune 500, Global 2000, asset management, frontier AI lab, and sovereign-cloud government tiers.</p> |
| 331 | + {cs_html} |
| 332 | + </section> |
| 333 | +
|
| 334 | + <section class="module" id="api"> |
| 335 | + <h2>API Endpoints</h2> |
| 336 | + <p class="summary">Prefix: <code>{esc(api.get('prefix',''))}</code> · Total planned: {n_routes}</p> |
| 337 | + <ul>{api_items}</ul> |
| 338 | + </section> |
| 339 | +</main> |
| 340 | +<footer> |
| 341 | + © {esc(meta.get('docRef',''))} v{esc(meta.get('version',''))} · |
| 342 | + {esc(meta.get('date',''))} · {esc(meta.get('classification',''))} · |
| 343 | + Owner: {esc(meta.get('owner',''))} |
| 344 | +</footer> |
| 345 | +</body> |
| 346 | +</html> |
| 347 | +""" |
| 348 | + OUT.parent.mkdir(parents=True, exist_ok=True) |
| 349 | + OUT.write_text(page, encoding="utf-8") |
| 350 | + size_kb = OUT.stat().st_size // 1024 |
| 351 | + print(f"Wrote {OUT} ({size_kb} KB)") |
| 352 | + print( |
| 353 | + f"Modules: {n_modules} | Sections: {total_sections} | " |
| 354 | + f"Schemas: {n_schemas} | Code: {n_code} | Cases: {n_cs} | " |
| 355 | + f"Routes: {n_routes}" |
| 356 | + ) |
| 357 | + |
| 358 | + |
| 359 | +if __name__ == "__main__": |
| 360 | + main() |
0 commit comments