Files
IPSExtractor/services/cleaning/domain/csv_cleaning/rules/registry.py
T

81 lines
2.0 KiB
Python

"""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