diff --git a/scripts/bootstrap_entities.py b/scripts/bootstrap_entities.py new file mode 100644 index 0000000..5bdd961 --- /dev/null +++ b/scripts/bootstrap_entities.py @@ -0,0 +1,188 @@ +"""Bootstrap engineering entities from existing project knowledge. + +One-shot script that seeds the entity/relationship graph from what +AtoCore already knows via memories, project state, and vault docs. +Safe to re-run — uses name+project dedup. + +Usage: + + python3 scripts/bootstrap_entities.py --base-url http://localhost:8100 +""" + +from __future__ import annotations + +import argparse +import json +import os +import urllib.request + +DEFAULT_BASE_URL = os.environ.get("ATOCORE_BASE_URL", "http://dalidou:8100") + + +def post(base_url, path, body): + data = json.dumps(body).encode("utf-8") + req = urllib.request.Request( + f"{base_url}{path}", method="POST", + headers={"Content-Type": "application/json"}, data=data, + ) + try: + with urllib.request.urlopen(req, timeout=10) as resp: + return json.loads(resp.read().decode("utf-8")) + except Exception as e: + return {"error": str(e)} + + +def entity(base_url, etype, name, project="", desc="", props=None): + result = post(base_url, "/entities", { + "entity_type": etype, "name": name, "project": project, + "description": desc, "properties": props or {}, + }) + eid = result.get("id", "") + status = "+" if eid else "skip" + print(f" {status} [{etype}] {name}") + return eid + + +def rel(base_url, src, tgt, rtype): + if not src or not tgt: + return + result = post(base_url, "/relationships", { + "source_entity_id": src, "target_entity_id": tgt, + "relationship_type": rtype, + }) + print(f" -> {rtype}") + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--base-url", default=DEFAULT_BASE_URL) + args = parser.parse_args() + b = args.base_url + + print("=== P04 GigaBIT M1 ===") + p04 = entity(b, "project", "GigaBIT M1", "p04-gigabit", + "1.2m primary mirror for stratospheric balloon telescope") + + p04_m1 = entity(b, "system", "M1 Mirror Assembly", "p04-gigabit", + "Primary mirror blank + support system + reference frame") + rel(b, p04, p04_m1, "contains") + + p04_vs = entity(b, "subsystem", "Vertical Support", "p04-gigabit", + "18-point whiffletree axial support from below") + p04_ls = entity(b, "subsystem", "Lateral Support", "p04-gigabit", + "Circumferential constraint system with GF-PTFE pads") + p04_rf = entity(b, "subsystem", "Reference Frame", "p04-gigabit", + "Structural mounting interface between mirror and OTA") + p04_blank = entity(b, "component", "M1 Blank", "p04-gigabit", + "1.2m Zerodur aspheric blank from Schott", + {"material": "Zerodur", "diameter_m": 1.2, "focal_ratio": "F/1.2"}) + rel(b, p04_m1, p04_vs, "contains") + rel(b, p04_m1, p04_ls, "contains") + rel(b, p04_m1, p04_rf, "contains") + rel(b, p04_m1, p04_blank, "contains") + + p04_zerodur = entity(b, "material", "Zerodur", "p04-gigabit", + "Glass-ceramic with near-zero CTE for mirror blanks") + p04_ptfe = entity(b, "material", "GF-PTFE", "p04-gigabit", + "Glass-filled PTFE for thermal stability on lateral pads") + rel(b, p04_blank, p04_zerodur, "uses_material") + rel(b, p04_ls, p04_ptfe, "uses_material") + + p04_optb = entity(b, "decision", "Option B Conical Back", "p04-gigabit", + "Selected mirror architecture: conical-back lightweighting") + rel(b, p04_optb, p04_blank, "affected_by_decision") + + p04_wfe = entity(b, "requirement", "WFE < 15nm RMS filtered", "p04-gigabit", + "Filtered mechanical wavefront error below 15 nm across 20-60 deg elevation") + p04_mass = entity(b, "requirement", "Mass < 103.5 kg", "p04-gigabit", + "Total mirror assembly mass constraint") + rel(b, p04_m1, p04_wfe, "constrained_by") + rel(b, p04_m1, p04_mass, "constrained_by") + + print("\n=== P05 Interferometer ===") + p05 = entity(b, "project", "Interferometer System", "p05-interferometer", + "Metrology system for GigaBIT M1 figuring") + + p05_rig = entity(b, "system", "Test Rig", "p05-interferometer", + "Folded-beam interferometric test setup for M1 measurement") + rel(b, p05, p05_rig, "contains") + + p05_ifm = entity(b, "component", "Interferometer", "p05-interferometer", + "Fixed horizontal Twyman-Green dynamic interferometer") + p05_fold = entity(b, "component", "Fold Mirror", "p05-interferometer", + "45-degree beam redirect, <= lambda/20 surface quality") + p05_cgh = entity(b, "component", "CGH Null Corrector", "p05-interferometer", + "6-inch transmission CGH for F/1.2 asphere null test", + {"diameter": "6 inch", "substrate": "fused silica", "error_budget_nm": 5.5}) + p05_tilt = entity(b, "subsystem", "Tilting Platform", "p05-interferometer", + "Mirror tilting platform, co-tilts with interferometer") + rel(b, p05_rig, p05_ifm, "contains") + rel(b, p05_rig, p05_fold, "contains") + rel(b, p05_rig, p05_cgh, "contains") + rel(b, p05_rig, p05_tilt, "contains") + rel(b, p05_ifm, p05_fold, "interfaces_with") + rel(b, p05_cgh, p05_tilt, "interfaces_with") + + p05_vendor_dec = entity(b, "decision", "Vendor Path: Twyman-Green preferred", "p05-interferometer", + "4D technical lead but cost-challenged; Zygo Verifire SV at 55K is value path") + p05_vendor_zygo = entity(b, "vendor", "Zygo / AMETEK", "p05-interferometer", + "Certified used Verifire SV, 55K, Nabeel Sufi contact") + p05_vendor_4d = entity(b, "vendor", "4D Technology", "p05-interferometer", + "PC6110/PC4030, above budget but strongest technical option") + p05_vendor_aom = entity(b, "vendor", "AOM (CGH)", "p05-interferometer", + "CGH design and fabrication, 28-30K package") + rel(b, p05_vendor_dec, p05_ifm, "affected_by_decision") + + print("\n=== P06 Polisher ===") + p06 = entity(b, "project", "Polisher System", "p06-polisher", + "Machine overhaul + software suite for optical polishing") + + p06_machine = entity(b, "system", "Polisher Machine", "p06-polisher", + "Swing-arm polishing machine with force modulation") + p06_sw = entity(b, "system", "Software Suite", "p06-polisher", + "Three-layer software: polisher-sim, polisher-post, polisher-control") + rel(b, p06, p06_machine, "contains") + rel(b, p06, p06_sw, "contains") + + p06_sim = entity(b, "subsystem", "polisher-sim", "p06-polisher", + "Digital twin: surface assimilation, removal simulation, planning") + p06_post = entity(b, "subsystem", "polisher-post", "p06-polisher", + "Bridge: validation, translation, packaging for machine") + p06_ctrl = entity(b, "subsystem", "polisher-control", "p06-polisher", + "Executor: state machine, interlocks, telemetry, run logs") + rel(b, p06_sw, p06_sim, "contains") + rel(b, p06_sw, p06_post, "contains") + rel(b, p06_sw, p06_ctrl, "contains") + rel(b, p06_sim, p06_post, "interfaces_with") + rel(b, p06_post, p06_ctrl, "interfaces_with") + + p06_fc = entity(b, "subsystem", "Force Control", "p06-polisher", + "Frame-grounded counterweight actuator with cable tension modulation", + {"actuator_capacity_N": "150-200", "compliance_spring_Nmm": "3-5"}) + p06_zaxis = entity(b, "component", "Z-Axis", "p06-polisher", + "Binary engage/retract mechanism, not continuous position") + p06_cam = entity(b, "component", "Cam Mechanism", "p06-polisher", + "Mechanically set by operator, read by encoders, not actuated") + rel(b, p06_machine, p06_fc, "contains") + rel(b, p06_machine, p06_zaxis, "contains") + rel(b, p06_machine, p06_cam, "contains") + + p06_fw = entity(b, "decision", "Firmware Interface Contract", "p06-polisher", + "controller-job.v1 in, run-log.v1 + telemetry out — invariant") + p06_offline = entity(b, "decision", "Offline-First Design", "p06-polisher", + "Machine works fully offline; network is for remote access only") + p06_usb = entity(b, "decision", "USB SSD Storage", "p06-polisher", + "USB SSD mandatory on RPi, not SD card") + + p06_contracts = entity(b, "constraint", "Shared Contracts", "p06-polisher", + "Stable IDs, explicit versions, hashable artifacts, planned-vs-executed separation") + rel(b, p06_sw, p06_contracts, "constrained_by") + + p06_preston = entity(b, "parameter", "Preston Coefficient kp", "p06-polisher", + "Calibrated from before/after surface measurements, multi-run inverse-variance weighting") + + print(f"\nDone.") + + +if __name__ == "__main__": + main()