$ cat node-template.py
H
Human Codex Assemble
// Deterministic final stage of the Human Codex interview pipeline (no LLM). Parses the three pass outputs (Pass 1 per_item array, Pass 3 analysis object, Pass 4 presentation object), tolerating code fences and surrounding prose, assembles the single schema v2.0 profile JSON {schema_version, per_item, analysis, presentation}, renders a readable second-person Markdown report from the presentation layer, and reports validation notes (OK, or WARNING/ERROR lines naming the pass to re-run). Fails when analysis or presentation is not valid JSON.
Process
Document
#human-codex#personality#profile#assemble#json#markdown#report#deterministic
template.py
1"""2Human Codex Assemble34Deterministic, LLM-free final stage of the Human Codex four-pass interview5pipeline:6 - parses the three LLM outputs (Pass 1 per_item array, Pass 3 analysis7 object, Pass 4 presentation object), tolerating code fences and8 surrounding prose,9 - assembles the single schema v2.0 JSON object10 {schema_version, per_item, analysis, presentation},11 - renders a readable Markdown report from the presentation layer12 (plus the epistemics block of analysis),13 - reports validation notes (missing keys, unparseable passes) so the14 driving agent can re-run a pass instead of shipping a broken file.1516Hard failure (exit 1) only when analysis or presentation cannot be parsed:17those two are the substance of the deliverable. An unparseable per_item18array degrades to [] with a warning.19"""20from __future__ import annotations2122import json23import re24import sys25from datetime import date2627TOP_KEYS_ANALYSIS = [28 "context", "dimensions", "edges_active", "configurations", "coherence",29 "self_model", "operating_model", "forecast", "leverage_path", "epistemics",30]31TOP_KEYS_PRESENTATION = [32 "headline", "codex_map_3d", "axis_map", "operating_snapshot",33 "operating_conditions", "self_model_panel", "pattern_cards", "shadow_panel",34 "strengths", "growth_edges", "forecast_panel", "actionable_intelligence",35 "evidence_signals", "map_edges_note", "derived_metrics", "radar_scores", "meta",36]3738HEADINGS = {39 "en": {40 "title": "Human Codex Profile", "date": "Interview date", "mode": "Interview mode",41 "frame": "This is a non-clinical self-reflection instrument. It is a profile of pressures, not a prediction, and it does not diagnose, treat, or replace professional care. Every reading below is a hypothesis with stated evidence, confidence, and limits, and you retain the right to question and contest it.",42 "crisis": "Practitioner notice",43 "headline": "Headline", "thesis": "Organizing thesis",44 "axes": "The four axes", "strength": "Strength", "growth": "Growth", "shadow": "Shadow", "reflection": "Reflection",45 "snapshot": "Operating snapshot", "strengths": "Strengths", "growth_edges": "Growth edges",46 "patterns": "Pattern cards", "shadow_panel": "Shadow and subconscious", "conditions": "Operating conditions",47 "resourced": "When resourced", "depleted": "When depleted", "flips": "Flips when",48 "self_model": "How accurately you see yourself", "clear": "You see clearly", "overrate": "You may overrate", "blind": "Blind spots",49 "forecast": "Trajectory and pressures", "when": "When", "tends": "Tends toward", "horizon": "Horizon", "shift": "What would shift it",50 "actions": "Actionable intelligence", "why": "Why", "first": "Do first because", "unlocks": "Then unlocks",51 "evidence": "Evidence signals", "map_edges": "Where this map runs out", "codex_map": "Codex map",52 "node": "Dimension", "layer": "Layer", "strength_col": "Strength", "position": "Position", "durability": "Durability", "confidence": "Confidence",53 "edges": "Active relations", "metrics": "Derived metrics", "radar": "Radar scores",54 "epistemics": "Epistemics", "overall_confidence": "Overall confidence", "data_quality": "Data quality",55 "highest": "Highest-confidence findings", "lowest": "Lowest-confidence findings", "limits": "Limitations",56 "cultural": "Cultural bias flags", "follow_up": "Recommended follow-up", "meta": "Meta",57 "depth": "Analysis depth", "confidence_score": "Confidence score", "signals_used": "Signals used",58 "raises": "raises", "lowers": "lowers",59 },60 "it": {61 "title": "Profilo Human Codex", "date": "Data dell'intervista", "mode": "Modalità di intervista",62 "frame": "Questo è uno strumento non clinico di auto-riflessione. È un profilo di pressioni, non una previsione, e non diagnostica, non cura e non sostituisce un percorso professionale. Ogni lettura qui sotto è un'ipotesi con evidenze, confidenza e limiti dichiarati, e mantieni il diritto di metterla in discussione e contestarla.",63 "crisis": "Avviso per il professionista",64 "headline": "In sintesi", "thesis": "Tesi organizzante",65 "axes": "I quattro assi", "strength": "Forza", "growth": "Crescita", "shadow": "Ombra", "reflection": "Riflessione",66 "snapshot": "Istantanea operativa", "strengths": "Punti di forza", "growth_edges": "Margini di crescita",67 "patterns": "Schede dei pattern", "shadow_panel": "Ombra e inconscio", "conditions": "Condizioni operative",68 "resourced": "Quando hai risorse", "depleted": "Quando sei in riserva", "flips": "Cambia quando",69 "self_model": "Quanto ti vedi con precisione", "clear": "Vedi con chiarezza", "overrate": "Potresti sovrastimare", "blind": "Punti ciechi",70 "forecast": "Traiettoria e pressioni", "when": "Quando", "tends": "Tende verso", "horizon": "Orizzonte", "shift": "Cosa la cambierebbe",71 "actions": "Intelligenza attuabile", "why": "Perché", "first": "Prima di tutto perché", "unlocks": "Poi sblocca",72 "evidence": "Segnali di evidenza", "map_edges": "Dove finisce questa mappa", "codex_map": "Mappa del Codex",73 "node": "Dimensione", "layer": "Livello", "strength_col": "Forza", "position": "Posizione", "durability": "Durabilità", "confidence": "Confidenza",74 "edges": "Relazioni attive", "metrics": "Metriche derivate", "radar": "Punteggi radar",75 "epistemics": "Epistemica", "overall_confidence": "Confidenza complessiva", "data_quality": "Qualità dei dati",76 "highest": "Risultati a confidenza più alta", "lowest": "Risultati a confidenza più bassa", "limits": "Limiti",77 "cultural": "Segnalazioni di bias culturale", "follow_up": "Approfondimenti consigliati", "meta": "Meta",78 "depth": "Profondità dell'analisi", "confidence_score": "Punteggio di confidenza", "signals_used": "Segnali usati",79 "raises": "alza", "lowers": "abbassa",80 },81}828384class AssembleError(ValueError):85 """Raised when an essential pass output cannot be parsed."""868788# ---------------------------------------------------------------------------89# Parsing helpers90# ---------------------------------------------------------------------------9192def _strip_fences(text: str) -> str:93 s = (text or "").strip()94 m = re.match(r"^```[a-zA-Z]*\s*(.*?)\s*```$", s, re.S)95 if m:96 return m.group(1).strip()97 m = re.search(r"```(?:json)?\s*(.*?)\s*```", s, re.S)98 if m and m.group(1).lstrip()[:1] in "{[":99 return m.group(1).strip()100 return s101102103def parse_pass_output(text: str, expect: str, unwrap_key: str):104 """Parse an LLM output into JSON.105106 expect: 'object' | 'array'. Returns (value, note); value is None on failure.107 Tolerates code fences, surrounding prose, and a wrapper object such as108 {"analysis": {...}} or {"per_item": [...]}.109 """110 s = _strip_fences(text)111 if not s:112 return None, "empty output"113 open_ch, close_ch = ("{", "}") if expect == "object" else ("[", "]")114 starts = [i for i in (s.find("{"), s.find("[")) if i != -1]115 if not starts:116 return None, "no JSON found"117 start = min(starts)118 end = max(s.rfind("}"), s.rfind("]"))119 if end < start:120 return None, "no JSON found"121 try:122 value = json.loads(s[start:end + 1])123 except json.JSONDecodeError as e:124 i, j = s.find(open_ch), s.rfind(close_ch)125 if i != -1 and j > i:126 try:127 value = json.loads(s[i:j + 1])128 except json.JSONDecodeError as e2:129 return None, f"invalid or truncated JSON ({e2.msg} at char {e2.pos} of {len(s)})"130 else:131 return None, f"invalid or truncated JSON ({e.msg} at char {e.pos} of {len(s)})"132 if isinstance(value, dict) and unwrap_key in value and len(value) <= 2:133 value = value[unwrap_key]134 if expect == "array" and isinstance(value, dict) and unwrap_key in value:135 value = value[unwrap_key]136 if expect == "array" and not isinstance(value, list):137 return None, f"expected a JSON array, got {type(value).__name__}"138 if expect == "object" and not isinstance(value, dict):139 return None, f"expected a JSON object, got {type(value).__name__}"140 return value, ""141142143def parse_settings(text: str) -> dict:144 out = {}145 for line in (text or "").splitlines():146 if ":" in line and not line.startswith("="):147 k, v = line.split(":", 1)148 k = k.strip().upper().replace(" ", "_")149 if k:150 out[k] = v.strip()151 return out152153154# ---------------------------------------------------------------------------155# Markdown rendering156# ---------------------------------------------------------------------------157158def _g(d, *path, default=None):159 cur = d160 for p in path:161 if isinstance(cur, dict) and p in cur:162 cur = cur[p]163 else:164 return default165 return cur166167168def _bullets(items, fmt=None) -> str:169 lines = []170 for it in items or []:171 if fmt:172 lines.append(f"- {fmt(it)}")173 elif isinstance(it, str):174 lines.append(f"- {it}")175 else:176 lines.append(f"- {json.dumps(it, ensure_ascii=False)}")177 return "\n".join(lines) if lines else "-"178179180def crisis_note(analysis: dict):181 for item in _g(analysis, "epistemics", "limitations", default=[]) or []:182 if isinstance(item, str) and item.strip().upper().startswith("CRISIS FLAG"):183 return item184 return None185186187def render_markdown(profile: dict, settings: dict) -> str:188 lang = (settings.get("REPORT_LANGUAGE") or "en").lower()[:2]189 H = HEADINGS.get(lang, HEADINGS["en"])190 pres = profile.get("presentation") or {}191 ana = profile.get("analysis") or {}192 subject = settings.get("SUBJECT_LABEL") or ""193 when = settings.get("INTERVIEW_DATE") or date.today().isoformat()194 mode = settings.get("INTERVIEW_MODE") or ""195196 out = []197 out.append(f"# {H['title']}" + (f" — {subject}" if subject else ""))198 out.append(f"*{H['date']}: {when}*" + (f" · *{H['mode']}: {mode}*" if mode else ""))199 out.append("")200 out.append(f"> {H['frame']}")201 out.append("")202203 crisis = crisis_note(ana)204 if crisis:205 out.append(f"## ⚠ {H['crisis']}")206 out.append(crisis)207 out.append("")208209 head = pres.get("headline") or {}210 archetype = head.get("archetype") or ""211 out.append(f"## {H['headline']}" + (f": {archetype}" if archetype else ""))212 if head.get("organizing_thesis_plain"):213 out.append(f"**{H['thesis']}.** {head['organizing_thesis_plain']}")214 out.append("")215 if head.get("summary"):216 out.append(head["summary"])217 out.append("")218219 axes = pres.get("axis_map") or {}220 out.append(f"## {H['axes']}")221 for key in ("strength", "growth", "shadow", "reflection"):222 ax = axes.get(key) or {}223 out.append(f"### {H[key]} — {ax.get('score', 50)}/100")224 if ax.get("summary"):225 out.append(ax["summary"])226 out.append(_bullets(ax.get("details")))227 out.append("")228229 snap = pres.get("operating_snapshot") or []230 if snap:231 out.append(f"## {H['snapshot']}")232 out.append(_bullets(snap, lambda s: f"**{s.get('label', '')}: {s.get('state', '')}.** {s.get('description', '')}"))233 out.append("")234235 out.append(f"## {H['strengths']}")236 out.append(_bullets(pres.get("strengths")))237 out.append("")238 out.append(f"## {H['growth_edges']}")239 out.append(_bullets(pres.get("growth_edges")))240 out.append("")241242 cards = pres.get("pattern_cards") or []243 if cards:244 out.append(f"## {H['patterns']}")245 for c in cards:246 out.append(f"### {c.get('title', '')} ({c.get('pattern_type', '')})")247 if c.get("summary"):248 out.append(c["summary"])249 if c.get("impact"):250 out.append(f"*{c['impact']}*")251 out.append("")252253 sh = pres.get("shadow_panel") or {}254 out.append(f"## {H['shadow_panel']}")255 if sh.get("summary"):256 out.append(sh["summary"])257 out.append(_bullets(sh.get("items")))258 if sh.get("framing_note"):259 out.append(f"\n*{sh['framing_note']}*")260 out.append("")261262 conds = pres.get("operating_conditions") or []263 if conds:264 out.append(f"## {H['conditions']}")265 for c in conds:266 out.append(f"### {c.get('dimension', '')}")267 out.append(f"- **{H['resourced']}:** {c.get('when_resourced', '')}")268 out.append(f"- **{H['depleted']}:** {c.get('when_depleted', '')}")269 out.append(f"- **{H['flips']}:** {c.get('flips_when', '')}")270 out.append("")271272 sm = pres.get("self_model_panel") or {}273 out.append(f"## {H['self_model']}")274 if sm.get("summary"):275 out.append(sm["summary"])276 out.append(f"\n**{H['clear']}**\n{_bullets(sm.get('you_see_clearly'))}")277 out.append(f"\n**{H['overrate']}**\n{_bullets(sm.get('you_may_overrate'))}")278 out.append(f"\n**{H['blind']}**\n{_bullets(sm.get('blind_spots'))}")279 out.append("")280281 fc = pres.get("forecast_panel") or {}282 out.append(f"## {H['forecast']}")283 if fc.get("trajectory"):284 out.append(fc["trajectory"])285 out.append("")286 for p in fc.get("pressures") or []:287 out.append(f"### {p.get('name', '')}")288 out.append(f"- **{H['when']}:** {p.get('when', '')}")289 out.append(f"- **{H['tends']}:** {p.get('tends_toward', '')}")290 out.append(f"- **{H['horizon']}:** {p.get('horizon', '')}")291 out.append(f"- **{H['shift']}:** {p.get('what_would_shift', '')}")292 out.append("")293 if fc.get("note"):294 out.append(f"*{fc['note']}*")295 out.append("")296297 acts = pres.get("actionable_intelligence") or []298 if acts:299 out.append(f"## {H['actions']}")300 for a in acts:301 out.append(f"### {a.get('step', '')}. {a.get('focus', '')}")302 out.append(f"- **{H['why']}:** {a.get('why', '')}")303 out.append(f"- **{H['first']}:** {a.get('do_first_because', '')}")304 out.append(f"- **{H['unlocks']}:** {a.get('then_unlocks', '')}")305 out.append("")306307 ev = pres.get("evidence_signals") or []308 if ev:309 out.append(f"## {H['evidence']}")310 out.append(_bullets(ev, lambda e: f"**{e.get('label', '')}** — “{e.get('quote', '')}” — {e.get('insight', '')}"))311 out.append("")312313 if pres.get("map_edges_note"):314 out.append(f"## {H['map_edges']}")315 out.append(pres["map_edges_note"])316 out.append("")317318 cm = pres.get("codex_map_3d") or {}319 nodes = cm.get("nodes") or []320 if nodes:321 out.append(f"## {H['codex_map']}")322 if cm.get("narrative_thread"):323 out.append(cm["narrative_thread"])324 out.append("")325 out.append(f"| # | {H['node']} | {H['layer']} | {H['strength_col']} | {H['position']} | {H['durability']} | {H['confidence']} |")326 out.append("|---|---|---|---|---|---|---|")327 for n in nodes:328 out.append(f"| {n.get('node_id', '')} | {n.get('node_name', '')} | {n.get('layer', '')} | {n.get('strength', '')} | {n.get('spectrum_position', '')} | {n.get('durability', '')} | {n.get('confidence', '')} |")329 out.append("")330 for n in nodes:331 if n.get("click_note"):332 out.append(f"- **{n.get('node_name', '')}** — {n['click_note']}")333 out.append("")334 edges = cm.get("edges") or []335 if edges:336 out.append(f"### {H['edges']}")337 out.append(_bullets(edges, lambda e: f"{e.get('source_id', '')} → {e.get('target_id', '')} ({H.get(e.get('sign', ''), e.get('sign', ''))}, w{e.get('weight', '')}, {e.get('interaction_type', '')}): {e.get('rationale', '')}"))338 out.append("")339340 dm = pres.get("derived_metrics") or {}341 rs = pres.get("radar_scores") or {}342 if dm or rs:343 out.append(f"## {H['metrics']}")344 for k, v in dm.items():345 out.append(f"- {k}: {v}")346 if rs:347 out.append(f"\n**{H['radar']}**")348 for k, v in rs.items():349 out.append(f"- {k}: {v}")350 out.append("")351352 ep = ana.get("epistemics") or {}353 if ep:354 out.append(f"## {H['epistemics']}")355 out.append(f"- **{H['overall_confidence']}:** {ep.get('overall_confidence', '')}")356 out.append(f"- **{H['data_quality']}:** {ep.get('data_quality', '')}")357 out.append(f"\n**{H['highest']}**\n{_bullets(ep.get('highest_confidence_findings'))}")358 out.append(f"\n**{H['lowest']}**\n{_bullets(ep.get('lowest_confidence_findings'))}")359 out.append(f"\n**{H['limits']}**\n{_bullets((ep.get('limitations') or []) + (ep.get('edges_of_knowledge') or []))}")360 if ep.get("cultural_bias_flags"):361 out.append(f"\n**{H['cultural']}**\n{_bullets(ep.get('cultural_bias_flags'))}")362 if ep.get("follow_up_recommended"):363 out.append(f"\n**{H['follow_up']}**\n{_bullets(ep.get('follow_up_recommended'))}")364 out.append("")365366 meta = pres.get("meta") or {}367 if meta:368 out.append(f"## {H['meta']}")369 out.append(f"- {H['confidence_score']}: {meta.get('confidence_score', '')}")370 out.append(f"- {H['depth']}: {meta.get('analysis_depth', '')}")371 out.append(f"- {H['signals_used']}: {', '.join(meta.get('language_signals_used') or [])}")372 out.append("")373374 return "\n".join(out).strip() + "\n"375376377# ---------------------------------------------------------------------------378# Core379# ---------------------------------------------------------------------------380381def process(inputs: dict) -> dict:382 """Assemble the profile. Raises AssembleError when analysis/presentation are unusable."""383 notes = []384 per_item, n1 = parse_pass_output(inputs.get("per_item") or "", "array", "per_item")385 if per_item is None:386 notes.append(f"WARNING per_item (Pass 1): {n1}; using []")387 per_item = []388 analysis, n3 = parse_pass_output(inputs.get("analysis") or "", "object", "analysis")389 presentation, n4 = parse_pass_output(inputs.get("presentation") or "", "object", "presentation")390 if analysis is None:391 raise AssembleError(f"ERROR analysis (Pass 3) could not be parsed: {n3}. Re-run 'Pass 3 · Map & Score Dimensions'.")392 if presentation is None:393 raise AssembleError(f"ERROR presentation (Pass 4) could not be parsed: {n4}. Re-run 'Pass 4 · Compose Profile'.")394395 for k in TOP_KEYS_ANALYSIS:396 if k not in analysis:397 notes.append(f"WARNING analysis missing key '{k}'")398 for k in TOP_KEYS_PRESENTATION:399 if k not in presentation:400 notes.append(f"WARNING presentation missing key '{k}'")401 dims = analysis.get("dimensions") or []402 edges = analysis.get("edges_active") or []403 configs = analysis.get("configurations") or []404 if len(dims) < 3:405 notes.append(f"WARNING analysis.dimensions has {len(dims)} entries (minimum 3)")406 if len(edges) < 2:407 notes.append("WARNING analysis.edges_active has fewer than 2 entries")408 if len(configs) < 1:409 notes.append("WARNING analysis.configurations is empty")410 if len(per_item) == 0:411 notes.append("WARNING per_item is empty")412413 settings = parse_settings(inputs.get("settings") or "")414 profile = {415 "schema_version": "2.0",416 "per_item": per_item,417 "analysis": analysis,418 "presentation": presentation,419 }420 report = render_markdown(profile, settings)421 headline = _g(presentation, "headline", "archetype", default="") or ""422 validation = "OK" if not notes else "\n".join(notes)423 validation += f"\nitems={len(per_item)} dimensions={len(dims)} edges={len(edges)} configurations={len(configs)}"424 return {425 "profile_json": json.dumps(profile, ensure_ascii=False, indent=2),426 "report_markdown": report,427 "validation_notes": validation,428 "headline": headline,429 }430431432def main() -> None:433 try:434 envelope = json.loads(sys.stdin.read() or "{}")435 inputs = envelope.get("inputs", {}) if isinstance(envelope, dict) else {}436 result = process(inputs)437 print(f"[human-codex-assemble] {result['validation_notes'].splitlines()[-1]}", file=sys.stderr)438 json.dump(result, sys.stdout, ensure_ascii=False)439 except Exception as e: # noqa: BLE001440 print(json.dumps({"error": str(e), "errorType": type(e).__name__}), file=sys.stderr)441 sys.exit(1)442443444if __name__ == "__main__":445 main()446$ git log --oneline
v1.0.0
HEAD
2026-09-04Initial release: deterministic assembler for the Human Codex interview pipeline