1# Author: Simon-Pierre Boucher — simon-pierre.boucher@uqo.ca2"""Paths, configuration loading and seeds. All scripts import from here."""3from __future__ import annotations45import os6from functools import lru_cache7from pathlib import Path8from typing import Any910import yaml11from dotenv import load_dotenv1213ROOT = Path(__file__).resolve().parents[2]14DATA = ROOT / "data"15RAW = DATA / "raw"16INTERIM = DATA / "interim"17PROCESSED = DATA / "processed"18SYNTHETIC = DATA / "synthetic"19RESULTS = ROOT / "results"20FIGURES = ROOT / "figures"21CONFIGS = ROOT / "configs"22REPORTS = ROOT / "reports"23LOGS = ROOT / "logs"24PAPER = ROOT / "paper"2526load_dotenv(ROOT / ".env", override=False)2728SEED = 20261027293031def ensure_dirs() -> None:32 for d in (RAW, INTERIM, PROCESSED, SYNTHETIC, RESULTS, FIGURES, REPORTS, LOGS, RESULTS / "ledger", RESULTS / "runs"):33 d.mkdir(parents=True, exist_ok=True)343536@lru_cache(maxsize=None)37def load_config(name: str) -> dict[str, Any]:38 """Load configs/<name>.yaml."""39 with open(CONFIGS / f"{name}.yaml", encoding="utf-8") as fh:40 return yaml.safe_load(fh)414243def provider_mode() -> str:44 """'live' or 'mock'. Mock is forced when AV_PROVIDER_MODE=mock or when no key is present."""45 mode = os.environ.get("AV_PROVIDER_MODE", "").strip().lower()46 if mode in {"mock", "live"}:47 return mode48 if os.environ.get("ANTHROPIC_API_KEY") or os.environ.get("OPENAI_API_KEY"):49 return "live"50 return "mock"515253def max_spend_usd() -> float:54 try:55 return float(os.environ.get("AV_MAX_SPEND_USD", "50"))56 except ValueError:57 return 50.0585960def model_info(model_id: str) -> dict[str, Any]:61 models = load_config("models")["models"]62 if model_id not in models:63 raise KeyError(f"model {model_id!r} is not registered in configs/models.yaml")64 return models[model_id]65