"""Load app and model config from YAML. Single source for hyperparameters and tunables.""" from __future__ import annotations import os # ClearML UI project name (must match the project in your ClearML workspace). CLEARML_PROJECT_NAME = "FocusGuards Large Group Project" from pathlib import Path from typing import Any _CONFIG: dict[str, Any] | None = None def _default_path() -> Path: return Path(__file__).resolve().parent / "default.yaml" def load_config(path: str | Path | None = None) -> dict[str, Any]: """Load YAML config. Uses FOCUSGUARD_CONFIG env or config/default.yaml.""" global _CONFIG if _CONFIG is not None: return _CONFIG import yaml p = path or os.environ.get("FOCUSGUARD_CONFIG") or _default_path() p = Path(p) if not p.is_file(): _CONFIG = {} return _CONFIG with open(p, "r", encoding="utf-8") as f: _CONFIG = yaml.safe_load(f) or {} return _CONFIG def get(key_path: str, default: Any = None) -> Any: """Return a nested config value. E.g. get('app.db_path'), get('mlp.epochs').""" cfg = load_config() for part in key_path.split("."): if not isinstance(cfg, dict) or part not in cfg: return default cfg = cfg[part] return cfg def flatten_for_clearml(cfg: dict[str, Any] | None = None, prefix: str = "") -> dict[str, Any]: """Flatten nested config so every value appears as a ClearML task parameter (no nested dicts).""" cfg = cfg if cfg is not None else load_config() out = {} for k, v in cfg.items(): key = f"{prefix}/{k}" if prefix else k if isinstance(v, dict) and v and not any(isinstance(x, (dict, list)) for x in v.values()): for k2, v2 in v.items(): out[f"{key}/{k2}"] = v2 elif isinstance(v, dict) and v: out.update(flatten_for_clearml(v, key)) elif isinstance(v, list): out[key] = str(v) else: out[key] = v return out