Files
IPSExtractor/services/cleaning/domain/csv_cleaning/apply.py
T

133 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Aplicación de reglas seleccionadas sobre un DataFrame."""
from __future__ import annotations
import logging
from typing import Any, Optional
import pandas as pd
from .cop_mod import (
_apply_cop_mod_bruto_neto,
_apply_cop_mod_split,
merge_moderadora_config,
)
from .documents import _apply_doc_id_split_columns
from .notas import _apply_nota_extract_columns
from .rules import get_rule_fn, get_rule_name
logger = logging.getLogger(__name__)
def apply_rules(
df: pd.DataFrame,
rules_per_column: dict[str, list[str]],
*,
cop_mod_fecha_column: Optional[dict[str, str]] = None,
cop_mod_config: Optional[dict[str, Any]] = None,
cop_mod_bruto_column: Optional[dict[str, str]] = None,
cop_mod_neto_column: Optional[dict[str, str]] = None,
) -> tuple[pd.DataFrame, list[str]]:
"""Aplica las reglas seleccionadas a cada columna.
`rules_per_column` es un dict de `columna -> lista de IDs de reglas en el
orden a aplicar`. Las reglas `cop_mod_split` y `cop_mod_bruto_neto` se
difieren a una segunda pasada tras el resto.
`cop_mod_fecha_column`: mapa columna ancla -> Fecha Servicio (para split).
`cop_mod_bruto_column` / `cop_mod_neto_column`: mapas ancla -> bruto / neto.
`cop_mod_config`: opcional; puede incluir ``moderadora_by_year``.
"""
result = df.copy()
log: list[str] = []
moderadora_years = merge_moderadora_config(cop_mod_config)
deferred = {"cop_mod_split", "cop_mod_bruto_neto"}
def _run_rules_for_column(col: str, rule_ids: list[str]) -> None:
if not rule_ids:
return
if col not in result.columns:
log.append(f"[WARN] Columna no encontrada: {col}")
return
split_has_homologation = (
"doc_id_split_tipo_numero" in rule_ids and "doc_type_homologate" in rule_ids
)
for rule_id in rule_ids:
if rule_id == "doc_id_split_tipo_numero":
try:
_apply_doc_id_split_columns(
result, col, log, homologate_type=split_has_homologation
)
except Exception as exc: # noqa: BLE001
logger.exception("Error aplicando regla %s a %s", rule_id, col)
log.append(f"[ERROR] '{col}' regla '{rule_id}': {exc}")
continue
if rule_id == "nota_paciente_autorizacion":
try:
_apply_nota_extract_columns(result, col, log)
except Exception as exc: # noqa: BLE001
logger.exception("Error aplicando regla %s a %s", rule_id, col)
log.append(f"[ERROR] '{col}' regla '{rule_id}': {exc}")
continue
if rule_id == "doc_type_homologate" and split_has_homologation:
continue
fn = get_rule_fn(rule_id)
if fn is None:
log.append(f"[WARN] Regla desconocida '{rule_id}' en columna '{col}'")
continue
try:
result[col] = fn(result[col])
log.append(f"[OK] '{col}' <- {get_rule_name(rule_id)}")
except Exception as exc: # noqa: BLE001
logger.exception("Error aplicando regla %s a %s", rule_id, col)
log.append(f"[ERROR] '{col}' regla '{rule_id}': {exc}")
# Fase 1: todas las reglas excepto COP/MOD diferidas
for col, rule_ids in rules_per_column.items():
filtered = [r for r in rule_ids if r not in deferred]
_run_rules_for_column(col, filtered)
# Fase 2a: Separa COP/MOD (cuotas por año)
fecha_map = cop_mod_fecha_column or {}
for col, rule_ids in rules_per_column.items():
if "cop_mod_split" not in rule_ids:
continue
fecha_col = fecha_map.get(col)
if not fecha_col:
log.append(
f"[WARN] COP/MOD en '{col}': indique la columna «Fecha Servicio» en la tarjeta; se omite."
)
continue
try:
_apply_cop_mod_split(
result,
col,
fecha_col,
log,
moderadora_by_year=moderadora_years,
)
except Exception as exc: # noqa: BLE001
logger.exception("Error aplicando COP/MOD a %s", col)
log.append(f"[ERROR] '{col}' regla 'cop_mod_split': {exc}")
# Fase 2b: COP/MOD = bruto − neto
bruto_map = cop_mod_bruto_column or {}
neto_map = cop_mod_neto_column or {}
for col, rule_ids in rules_per_column.items():
if "cop_mod_bruto_neto" not in rule_ids:
continue
bruto_col = (bruto_map.get(col) or col).strip()
neto_col = (neto_map.get(col) or "").strip()
if not neto_col:
log.append(
f"[WARN] COP/MOD (bruto−neto) en '{col}': indique la columna «Valor neto»; se omite."
)
continue
try:
_apply_cop_mod_bruto_neto(result, bruto_col, neto_col, log)
except Exception as exc: # noqa: BLE001
logger.exception("Error aplicando COP/MOD bruto−neto a %s", col)
log.append(f"[ERROR] '{col}' regla 'cop_mod_bruto_neto': {exc}")
return result, log