"""Registro de reglas de limpieza. Para agregar una regla nueva: 1. Implementa ``fn(series: pd.Series) -> pd.Series`` (o passthrough si es especial). 2. Llama a ``register_rule(...)`` en ``rules/builtins.py``. 3. Si la regla es especial (aƱade columnas, usa config), maneja el id en ``apply.py``. 4. Incluye el id en ``_applicable_rules_for_type`` (type_inference.py) si debe sugerirse. """ from __future__ import annotations from dataclasses import dataclass from typing import Any, Callable, Optional import pandas as pd RuleFn = Callable[[pd.Series], pd.Series] @dataclass(frozen=True) class RuleSpec: id: str name: str description: str fn: RuleFn def as_dict(self) -> dict[str, Any]: return { "id": self.id, "name": self.name, "description": self.description, "fn": self.fn, } _REGISTRY: dict[str, RuleSpec] = {} _ORDER: list[str] = [] def register_rule( rule_id: str, *, name: str, description: str, fn: RuleFn, ) -> RuleSpec: """Registra una regla. Si el id ya existe, lo reemplaza manteniendo el orden.""" spec = RuleSpec(id=rule_id, name=name, description=description, fn=fn) if rule_id not in _REGISTRY: _ORDER.append(rule_id) _REGISTRY[rule_id] = spec return spec def get_rule(rule_id: str) -> Optional[dict[str, Any]]: spec = _REGISTRY.get(rule_id) return spec.as_dict() if spec else None def get_rule_fn(rule_id: str) -> Optional[RuleFn]: spec = _REGISTRY.get(rule_id) return spec.fn if spec else None def get_rule_name(rule_id: str) -> str: spec = _REGISTRY.get(rule_id) return spec.name if spec else rule_id def list_rules() -> list[dict[str, Any]]: return [_REGISTRY[rid].as_dict() for rid in _ORDER if rid in _REGISTRY] # Compat: lista mutable usada por la API (se rellena al importar builtins). CLEANING_RULES: list[dict[str, Any]] = [] def rebuild_cleaning_rules() -> list[dict[str, Any]]: CLEANING_RULES.clear() CLEANING_RULES.extend(list_rules()) return CLEANING_RULES