| from __future__ import annotations |
| from pathlib import Path |
|
|
| import yaml |
|
|
| from core.runner import run_skill_script_raw |
|
|
| |
| _CATALOG_DATE = "2026-06-13" |
|
|
|
|
| def assemble_spec_yaml( |
| session_dir: Path, |
| slug: str, |
| route: dict, |
| bind_results: dict, |
| indicator_defs: dict, |
| ) -> Path: |
| """Assemble analysis_spec_<slug>.yaml from bind loop results. |
| |
| Returns the path to the written YAML file. |
| Never reads catalog/*.yaml directly — definitions come from indicator_defs (state). |
| """ |
| in_scope_sectors = route.get("sectors", []) |
| primary_sector = in_scope_sectors[0] if in_scope_sectors else "unknown" |
|
|
| indicators: dict = {} |
| for ind_id, bind in bind_results.items(): |
| ind_def = indicator_defs.get(ind_id, {}) |
| cluster = ind_def.get("cluster", primary_sector) |
| entry: dict = { |
| "label": ind_def.get("label", ind_id), |
| "dimension": f"sector::{cluster}", |
| "definition": ind_def.get("definition", ""), |
| "measurable": bind["measurable"], |
| "reasons": bind["reasons"], |
| "variables": bind["variables"], |
| |
| |
| |
| "result_ids": [ind_id], |
| } |
| indicators[ind_id] = entry |
|
|
| spec = { |
| "dataset": slug, |
| "unit_of_analysis": "key_informant", |
| "n_total": 0, |
| "disaggregation": { |
| "by": "analyst-specified", |
| "groups": {}, |
| "source_variables": [], |
| "trigger": "analyst-specified", |
| }, |
| "step1_framework_route": { |
| "sector": primary_sector, |
| "pillar_2d": None, |
| "subpillar_2d": None, |
| "cross_cutting": None, |
| }, |
| "step2_catalog_version": _CATALOG_DATE, |
| "indicators": indicators, |
| "uncovered_modules": [], |
| "gates": [], |
| } |
|
|
| out_path = session_dir / f"analysis_spec_{slug}.yaml" |
| out_path.write_text( |
| yaml.dump(spec, allow_unicode=True, sort_keys=False, default_flow_style=False) |
| ) |
| return out_path |
|
|
|
|
| def render_spec_md(spec_yaml_path: Path, session_dir: Path) -> Path: |
| """Render analysis_spec_<slug>.yaml to analysis_spec_<slug>.md. |
| |
| Calls render_spec.py with no -o flag so it writes markdown to stdout. |
| Captures stdout and writes it to the session dir. |
| Returns the path to the written .md file. |
| """ |
| md_content = run_skill_script_raw("render_spec.py", [str(spec_yaml_path)]) |
| md_path = session_dir / spec_yaml_path.with_suffix(".md").name |
| md_path.write_text(md_content) |
| return md_path |
|
|