from __future__ import annotations import yaml def test_assemble_spec_yaml_creates_valid_yaml(tmp_path): from core.spec import assemble_spec_yaml route = {"sectors": ["WASH", "Food Security"], "lens": [], "type": []} bind_results = { "rcsi": { "indicator_id": "rcsi", "variables": ["cope_q1"], "measurable": "PROXY", "reasons": "Proves: ordinal coping. Cannot prove: full rCSI score.", "result_ids": ["rcsi_proxy"], } } indicator_defs = { "rcsi": { "label": "Reduced Coping Strategies Index", "definition": "Measures food insecurity via coping behaviour frequency.", "cluster": "Food Security", } } yaml_path = assemble_spec_yaml(tmp_path, "vasyr_2023", route, bind_results, indicator_defs) assert yaml_path.exists() assert yaml_path.name == "analysis_spec_vasyr_2023.yaml" data = yaml.safe_load(yaml_path.read_text()) assert data["dataset"] == "vasyr_2023" assert data["unit_of_analysis"] == "key_informant" assert "rcsi" in data["indicators"] ind = data["indicators"]["rcsi"] assert ind["measurable"] == "PROXY" assert ind["definition"] == "Measures food insecurity via coping behaviour frequency." assert ind["variables"] == ["cope_q1"] assert ind["reasons"] == "Proves: ordinal coping. Cannot prove: full rCSI score." assert ind["label"] == "Reduced Coping Strategies Index" def test_assemble_spec_yaml_result_ids_is_catalog_slug(tmp_path): """result_ids must be the catalog slug (the dict key), never the bind call's value. The bind call leaks dataset variable names into result_ids; code overrides it so the 'Indicator name in the analysis' column always shows the stable catalog slug. """ from core.spec import assemble_spec_yaml route = {"sectors": ["WASH"], "lens": [], "type": []} bind_results = { "jmp_water_safely_managed": { "indicator_id": "jmp_water_safely_managed", "variables": ["main_water_source_now"], "measurable": "NOT_MEASURABLE", "reasons": "Requires on-premises + availability + E. coli — none collected.", # Model garbage: a dataset variable name leaked into result_ids. "result_ids": ["main_water_source_now"], } } indicator_defs = { "jmp_water_safely_managed": { "label": "JMP Drinking Water — Safely Managed", "definition": "Improved source on premises, available, free of contamination.", "cluster": "wash", } } yaml_path = assemble_spec_yaml(tmp_path, "vasyr_2023", route, bind_results, indicator_defs) data = yaml.safe_load(yaml_path.read_text()) ind = data["indicators"]["jmp_water_safely_managed"] assert ind["result_ids"] == ["jmp_water_safely_managed"] def test_assemble_spec_yaml_dimension_uses_cluster(tmp_path): from core.spec import assemble_spec_yaml route = {"sectors": ["WASH"], "lens": [], "type": []} bind_results = { "jmp_water_basic": { "indicator_id": "jmp_water_basic", "variables": ["water_src"], "measurable": "MEASURABLE", "reasons": "Proves: JMP basic access. Cannot prove: safely managed.", "result_ids": ["jmp_basic"], } } indicator_defs = { "jmp_water_basic": { "label": "JMP Drinking Water — Basic", "definition": "Access to an improved source within 30 minutes.", "cluster": "WASH", } } yaml_path = assemble_spec_yaml(tmp_path, "test_slug", route, bind_results, indicator_defs) data = yaml.safe_load(yaml_path.read_text()) assert data["indicators"]["jmp_water_basic"]["dimension"] == "sector::WASH" assert data["step1_framework_route"]["sector"] == "WASH" def test_render_spec_md_calls_render_script(monkeypatch, tmp_path): from core import spec as spec_mod calls = [] monkeypatch.setattr(spec_mod, "run_skill_script_raw", lambda name, args: (calls.append((name, args)) or "# rendered md")) spec_yaml = tmp_path / "analysis_spec_test.yaml" spec_yaml.write_text("dataset: test\nindicators: {}") md_path = spec_mod.render_spec_md(spec_yaml, tmp_path) assert calls[0][0] == "render_spec.py" assert str(spec_yaml) in calls[0][1] assert md_path.exists() assert md_path.read_text() == "# rendered md" assert md_path.name == "analysis_spec_test.md"