249c5de76a
- Refactor memory system (episodic/STM/LTM with components) - Implement complete subtitle domain (scanner, matcher, placer) - Add YAML workflow infrastructure - Externalize knowledge base (patterns, release groups) - Add comprehensive testing suite - Create manual testing CLIs
117 lines
4.2 KiB
Python
117 lines
4.2 KiB
Python
"""RuleSetRepository — loads SubtitleRuleSet from .alfred/ YAML files."""
|
|
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING
|
|
|
|
import yaml
|
|
|
|
from alfred.domain.subtitles.aggregates import SubtitleRuleSet
|
|
from alfred.domain.subtitles.value_objects import RuleScope
|
|
|
|
if TYPE_CHECKING:
|
|
from alfred.infrastructure.persistence.memory.ltm.components.subtitle_preferences import SubtitlePreferences
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _load_yaml(path: Path) -> dict:
|
|
if not path.exists():
|
|
return {}
|
|
try:
|
|
with open(path, encoding="utf-8") as f:
|
|
return yaml.safe_load(f) or {}
|
|
except Exception as e:
|
|
logger.warning(f"RuleSetRepository: could not read {path}: {e}")
|
|
return {}
|
|
|
|
|
|
class RuleSetRepository:
|
|
"""
|
|
Builds a fully chained SubtitleRuleSet by reading YAML from .alfred/.
|
|
|
|
Inheritance chain:
|
|
global (hardcoded defaults)
|
|
└── release_group (.alfred/release_groups/{GROUP}.yaml)
|
|
└── local (.alfred/rules.yaml)
|
|
|
|
Rules are delta-only — None means "inherit from parent".
|
|
The repository only creates intermediate nodes when the corresponding
|
|
file exists and contains an override section.
|
|
"""
|
|
|
|
def __init__(self, library_root: Path):
|
|
self._alfred_dir = library_root / ".alfred"
|
|
|
|
def load(
|
|
self,
|
|
release_group: str | None = None,
|
|
subtitle_preferences: "SubtitlePreferences | None" = None,
|
|
) -> SubtitleRuleSet:
|
|
"""
|
|
Build and return the resolved RuleSet chain.
|
|
|
|
If subtitle_preferences is provided, it seeds the global base rule set
|
|
from LTM (overriding the hardcoded DEFAULT_RULES).
|
|
Returns global default if no overrides exist.
|
|
"""
|
|
base = SubtitleRuleSet.global_default()
|
|
if subtitle_preferences is not None:
|
|
base.override(
|
|
languages=subtitle_preferences.languages,
|
|
formats=subtitle_preferences.formats,
|
|
types=subtitle_preferences.types,
|
|
)
|
|
current = base
|
|
|
|
# Release group level
|
|
if release_group:
|
|
rg_path = self._alfred_dir / "release_groups" / f"{release_group}.yaml"
|
|
rg_data = _load_yaml(rg_path).get("override", {})
|
|
if rg_data:
|
|
rg_ruleset = SubtitleRuleSet(
|
|
scope=RuleScope(level="release_group", identifier=release_group),
|
|
parent=current,
|
|
)
|
|
rg_ruleset.override(**_filter_override(rg_data))
|
|
current = rg_ruleset
|
|
logger.debug(f"RuleSetRepository: loaded release_group override for '{release_group}'")
|
|
|
|
# Local (show/movie) level
|
|
local_data = _load_yaml(self._alfred_dir / "rules.yaml").get("override", {})
|
|
if local_data:
|
|
local_ruleset = SubtitleRuleSet(
|
|
scope=RuleScope(level="show"),
|
|
parent=current,
|
|
)
|
|
local_ruleset.override(**_filter_override(local_data))
|
|
current = local_ruleset
|
|
logger.debug("RuleSetRepository: loaded local rules.yaml override")
|
|
|
|
return current
|
|
|
|
def save_local(self, delta: dict) -> None:
|
|
"""Write or update .alfred/rules.yaml with override delta."""
|
|
self._alfred_dir.mkdir(parents=True, exist_ok=True)
|
|
path = self._alfred_dir / "rules.yaml"
|
|
existing = _load_yaml(path)
|
|
existing_override = existing.get("override", {})
|
|
existing_override.update(delta)
|
|
data = {"override": existing_override}
|
|
tmp = path.with_suffix(".yaml.tmp")
|
|
try:
|
|
with open(tmp, "w", encoding="utf-8") as f:
|
|
yaml.safe_dump(data, f, allow_unicode=True, default_flow_style=False, sort_keys=False)
|
|
tmp.rename(path)
|
|
logger.info(f"RuleSetRepository: saved local rules to {path}")
|
|
except Exception as e:
|
|
logger.error(f"RuleSetRepository: could not write {path}: {e}")
|
|
tmp.unlink(missing_ok=True)
|
|
raise
|
|
|
|
|
|
def _filter_override(data: dict) -> dict:
|
|
"""Keep only keys that SubtitleRuleSet.override() accepts."""
|
|
valid = {"languages", "formats", "types", "format_priority", "min_confidence"}
|
|
return {k: v for k, v in data.items() if k in valid}
|