Spaces:
Sleeping
Sleeping
| """ | |
| Emit `design/atp_data.js` from `atp/seed.py` so the Agent-view React UI reads | |
| one canonical, referentially-consistent data blob. | |
| Output shape (see docs/ATP.md Β§3 + Β§7.1): | |
| window.ATP_DATA = { STANDARD, INFRA, LAYERS, AGENTS, CERTS, EVIDENCE, | |
| POLICIES, TIERS, RL, PLAYBOOKS, MARKETPLACE, VAULT, | |
| COMPOSE, EXPERT_REQUESTS, | |
| agentById, certById, evidenceById, certsForAgent, | |
| labelFor, matchExperts }; | |
| The JSON payload is serialised with `json.dumps`; the helper functions are | |
| appended as plain JS text inside the same IIFE. | |
| Run: | |
| python3 scripts/build_atp_data.py | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import sys | |
| from pathlib import Path | |
| PROJECT_ROOT = Path(__file__).parent.parent | |
| sys.path.insert(0, str(PROJECT_ROOT)) | |
| from atp.seed import build_seed # noqa: E402 | |
| OUT_PATH = PROJECT_ROOT / "design" / "atp_data.js" | |
| JS_TEMPLATE = """// Generated by scripts/build_atp_data.py β do not edit by hand. | |
| // | |
| // window.ATP_DATA β the ATP (Agent University) data contract. See docs/ATP.md | |
| // Β§3 + Β§7.1. Every certId / evidenceId / policyId / agentId referenced anywhere | |
| // in this blob resolves; awards <-> AGENTS.certIds are bidirectional. | |
| window.ATP_DATA = (() => {{ | |
| const DATA = {payload}; | |
| // ββ indices ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const _agents = {{}}; | |
| (DATA.AGENTS || []).forEach((a) => {{ _agents[a.id] = a; }}); | |
| const _certs = {{}}; | |
| (DATA.CERTS || []).forEach((c) => {{ _certs[c.id] = c; }}); | |
| const _ev = {{}}; | |
| (DATA.EVIDENCE || []).forEach((e) => {{ _ev[e.id] = e; }}); | |
| // ββ helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function agentById(id) {{ return _agents[id] || null; }} | |
| function certById(id) {{ return _certs[id] || null; }} | |
| function evidenceById(id) {{ return _ev[id] || null; }} | |
| function certsForAgent(agentId) {{ | |
| const a = _agents[agentId]; | |
| return a ? (a.certIds || []).map((id) => _certs[id]).filter(Boolean) : []; | |
| }} | |
| function labelFor(certId) {{ | |
| const c = _certs[certId]; | |
| return c ? c.label : null; | |
| }} | |
| // matchExperts({{course, concentrationId, badgeIds, packId}}) -> | |
| // {{ exact:[agentId], partial:[{{agentId, covered:[str], missing:[str]}}] }} | |
| // exact = agent whose skills cover all track competencies AND holds the badge | |
| // certs AND (packId ? bound to the pack's policies : true). Partial ranked by | |
| // coverage. Revoked agents are excluded from supply. | |
| function matchExperts(sel) {{ | |
| sel = sel || {{}}; | |
| const badgeIds = sel.badgeIds || []; | |
| const packId = sel.packId || null; | |
| let comps = []; | |
| (DATA.COMPOSE && DATA.COMPOSE.majors ? DATA.COMPOSE.majors : []).forEach((m) => {{ | |
| if (sel.course != null && String(m.course) !== String(sel.course)) return; | |
| (m.concentrations || []).forEach((cc) => {{ | |
| if (sel.concentrationId && cc.id !== sel.concentrationId) return; | |
| comps = comps.concat(cc.competencies || []); | |
| }}); | |
| }}); | |
| comps = Array.from(new Set(comps)); | |
| let packPolicies = []; | |
| if (packId && DATA.COMPOSE && DATA.COMPOSE.packs) {{ | |
| const pk = DATA.COMPOSE.packs.find((p) => p.id === packId); | |
| if (pk) packPolicies = pk.policyIds || []; | |
| }} | |
| const exact = []; | |
| const partial = []; | |
| (DATA.AGENTS || []).forEach((a) => {{ | |
| if (a.status === "revoked") return; | |
| const skillNames = (a.skills || []).map((s) => s.name); | |
| const certIds = a.certIds || []; | |
| const policyIds = a.policyIds || []; | |
| const covered = comps.filter((c) => skillNames.includes(c)); | |
| const missing = comps.filter((c) => !skillNames.includes(c)); | |
| const holdsBadges = badgeIds.every((b) => certIds.includes(b)); | |
| const boundPack = !packId || packPolicies.every((p) => policyIds.includes(p)); | |
| if (comps.length && missing.length === 0 && holdsBadges && boundPack) {{ | |
| exact.push(a.id); | |
| }} else if (covered.length || badgeIds.some((b) => certIds.includes(b))) {{ | |
| partial.push({{ agentId: a.id, covered: covered, missing: missing }}); | |
| }} | |
| }}); | |
| partial.sort((x, y) => y.covered.length - x.covered.length); | |
| return {{ exact: exact, partial: partial }}; | |
| }} | |
| return Object.assign(DATA, {{ | |
| agentById, certById, evidenceById, certsForAgent, labelFor, matchExperts, | |
| }}); | |
| }})(); | |
| """ | |
| def main() -> None: | |
| data = build_seed() | |
| payload = json.dumps(data, indent=2, ensure_ascii=False) | |
| out = JS_TEMPLATE.format(payload=payload) | |
| OUT_PATH.write_text(out, encoding="utf-8") | |
| size_kb = len(out.encode("utf-8")) / 1024 | |
| print(f"[Done] wrote {OUT_PATH.relative_to(PROJECT_ROOT)} ({size_kb:.1f} KB)") | |
| if size_kb > 300: | |
| print(f" WARNING: exceeds 300KB budget ({size_kb:.1f} KB)") | |
| if __name__ == "__main__": | |
| main() | |