brain-university-api / scripts /build_atp_data.py
jang0294's picture
Upload folder using huggingface_hub
4a8ceaa verified
Raw
History Blame Contribute Delete
5.1 kB
"""
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()