📄 loader.py 4,488 bytes Yesterday 13:46 📋 Raw

"""Family configuration loader — reads YAML from config/families/."""

import os
import re
from pathlib import Path
from typing import Optional

import yaml

Default data directory — overridden by ICARUS_DATA_DIR env var

DEFAULT_DATA_DIR = Path.home() / ".icarus"

def get_data_dir() -> Path:
"""Return the data directory for Icarus state (DBs, logs, etc.)."""
return Path(os.environ.get("ICARUS_DATA_DIR", str(DEFAULT_DATA_DIR)))

---------------------------------------------------------------------------

Family config model

---------------------------------------------------------------------------

class FamilyConfig:
"""Loaded family configuration with member profiles and inference rules."""

def __init__(self, data: dict):
    self.family_id: str = data.get("family_id", "default")
    self.version: str = data.get("version", "0.1.0")
    self.last_updated: str = data.get("last_updated", "")
    self.members: list[dict] = data.get("members", [])
    self.inference_rules: list[dict] = data.get("inference_rules", [])
    self.telemetry: dict = data.get("telemetry", {})

    # Build lookup maps
    self._member_by_id: dict[str, dict] = {m["id"]: m for m in self.members}
    self._member_by_name: dict[str, dict] = {}
    self._compiled_rules: list[tuple[re.Pattern, dict]] = []

    for m in self.members:
        name = m.get("name", "").lower()
        nick = (m.get("nickname") or "").lower()
        if name:
            self._member_by_name[name] = m
        if nick:
            self._member_by_name[nick] = m

    for rule in self.inference_rules:
        try:
            pattern = re.compile(rule["pattern"], re.IGNORECASE)
            self._compiled_rules.append((pattern, rule))
        except re.error as e:
            pass  # skip invalid rules

def get_member(self, member_id: str) -> Optional[dict]:
    return self._member_by_id.get(member_id)

def find_member_by_name(self, name: str) -> Optional[dict]:
    return self._member_by_name.get(name.lower())

def all_parents(self) -> list[dict]:
    return [m for m in self.members if m.get("is_parent")]

def all_children(self) -> list[dict]:
    return [m for m in self.members if not m.get("is_parent")]

def match_inference_rules(self, text: str) -> list[tuple[str, float]]:
    """Run inference rules against text, return [(assigned_id, confidence), ...]."""
    results = []
    for pattern, rule in self._compiled_rules:
        if pattern.search(text):
            confidence = rule.get("confidence", 0.7)
            for assignee in rule.get("assign_to", []):
                results.append((assignee, confidence))
    return results

@property
def fallback_threshold(self) -> float:
    return self.telemetry.get("fallback_threshold", 0.70)

---------------------------------------------------------------------------

Loader

---------------------------------------------------------------------------

def _find_family_yaml() -> Optional[Path]:
"""Search known locations for family config YAML."""
search_paths = [
Path(os.environ.get("ICARUS_FAMILY_CONFIG", "")),
Path.cwd() / "config" / "families",
Path(file).parent / "families",
get_data_dir() / "families",
]

for base in search_paths:
    if base.name and not base.exists():
        continue
    if base.is_file() and base.suffix in (".yaml", ".yml"):
        return base
    if base.is_dir():
        # Pick the first .yaml file
        for f in sorted(base.iterdir()):
            if f.suffix in (".yaml", ".yml"):
                return f
return None

def load_family(path: Optional[str] = None) -> FamilyConfig:
"""Load family configuration from YAML file.

Resolution order:
1. Explicit path argument
2. ICARUS_FAMILY_CONFIG env var
3. ./config/families/*.yaml
4. ~/.icarus/families/*.yaml
"""
if path:
    yaml_path = Path(path)
else:
    found = _find_family_yaml()
    if not found:
        raise FileNotFoundError(
            "No family config found. Create a YAML file in config/families/ "
            "or set ICARUS_FAMILY_CONFIG."
        )
    yaml_path = found

with open(yaml_path) as f:
    data = yaml.safe_load(f)

return FamilyConfig(data)