csc8114 / code / src / shared / resolve_scenario_config.py
resolve_scenario_config.py
Raw
"""
Resolve and write a merged scenario config for native-run-single.

Usage:
    python -m src.shared.resolve_scenario_config <SCENARIO_ID>

Finds the scenario in matrix.yaml's experiment_matrix.scenarios, deep-merges
its overrides onto the base config (config.yaml), writes the result to
matrix_configs/, and prints the output path.  Prints an empty string if
SCENARIO_ID is empty or not found (caller falls back to the default config.yaml).
"""
import copy
import sys
import yaml
from datetime import datetime
from pathlib import Path


def _deep_merge(base: dict, override: dict) -> dict:
    for key, value in override.items():
        if key in base and isinstance(base[key], dict) and isinstance(value, dict):
            _deep_merge(base[key], value)
        else:
            base[key] = value
    return base


def main() -> None:
    scenario_id = sys.argv[1].strip() if len(sys.argv) > 1 else ""
    if not scenario_id:
        print("")
        return

    here = Path(__file__).resolve().parent
    project_root = here.parent.parent
    config_path = project_root / "config.yaml"
    matrix_path = project_root / "matrix.yaml"

    with open(config_path, "r", encoding="utf-8") as f:
        root_cfg = yaml.safe_load(f) or {}

    with open(matrix_path, "r", encoding="utf-8") as f:
        matrix_raw = yaml.safe_load(f) or {}

    scenarios = matrix_raw.get("experiment_matrix", {}).get("scenarios", [])
    scenario = next(
        (s for s in scenarios if str(s.get("id", "")).strip() == scenario_id),
        None,
    )
    if scenario is None:
        print(
            f"[resolve_scenario_config] WARNING: scenario '{scenario_id}' not found "
            f"in experiment_matrix.scenarios — using default config.yaml",
            file=sys.stderr,
        )
        print("")
        return

    run_cfg = copy.deepcopy(root_cfg)
    overrides = scenario.get("overrides", {})
    if isinstance(overrides, dict) and overrides:
        _deep_merge(run_cfg, overrides)

    configs_dir = project_root / "results" / "matrix_configs"
    configs_dir.mkdir(exist_ok=True)
    stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    out_path = configs_dir / f"{stamp}_{scenario_id}_native.yaml"
    with open(out_path, "w", encoding="utf-8") as f:
        yaml.safe_dump(run_cfg, f, sort_keys=False)

    print(str(out_path))


if __name__ == "__main__":
    main()