348 lines
9.6 KiB
Python
348 lines
9.6 KiB
Python
from __future__ import annotations
|
|
|
|
import pandas as pd
|
|
|
|
from application.csv_cleaning import (
|
|
apply_rules,
|
|
homologate_doc_type,
|
|
split_doc_tipo_y_numero_texto,
|
|
)
|
|
|
|
|
|
def test_homologate_doc_type_aliases() -> None:
|
|
cases = {
|
|
"Cedula": "CC",
|
|
"C.C": "CC",
|
|
"C C": "CC",
|
|
"Registro Civil": "RC",
|
|
"Salvoconducto": "SC",
|
|
"TC": "RC",
|
|
"C\u00e9dula de Extranjeria": "CE",
|
|
}
|
|
|
|
for raw, expected in cases.items():
|
|
assert homologate_doc_type(raw) == expected
|
|
|
|
|
|
def test_split_doc_type_with_long_names() -> None:
|
|
cases = {
|
|
"Cedula 123.456": ("CC", "123456"),
|
|
"Registro Civil: 001-23": ("RC", "00123"),
|
|
"Permiso especial de permanencia 999": ("PE", "999"),
|
|
"Permiso por proteccion Temporal / 555": ("PT", "555"),
|
|
}
|
|
|
|
for raw, expected in cases.items():
|
|
assert split_doc_tipo_y_numero_texto(raw) == expected
|
|
|
|
|
|
def test_split_doc_type_with_abbreviations_and_noise() -> None:
|
|
cases = {
|
|
"C.C-123": ("CC", "123"),
|
|
"TL_987": ("TI", "987"),
|
|
"CE/1.234.567": ("CE", "1234567"),
|
|
"T1 # 44": ("TI", "44"),
|
|
}
|
|
|
|
for raw, expected in cases.items():
|
|
assert split_doc_tipo_y_numero_texto(raw) == expected
|
|
|
|
|
|
def test_split_unknown_type_preserves_clean_uppercase() -> None:
|
|
assert split_doc_tipo_y_numero_texto("ABC Especial: 123-45") == (
|
|
"ABC ESPECIAL",
|
|
"12345",
|
|
)
|
|
|
|
|
|
def test_split_without_homologation_preserves_clean_type() -> None:
|
|
assert split_doc_tipo_y_numero_texto(
|
|
"Cedula 123.456",
|
|
homologate=False,
|
|
) == ("CEDULA", "123456")
|
|
|
|
|
|
def test_apply_rules_homologates_type_column_only() -> None:
|
|
df = pd.DataFrame(
|
|
{
|
|
"tipo": [
|
|
"Cedula",
|
|
"C.C",
|
|
"Registro Civil",
|
|
"Salvoconducto",
|
|
"TC",
|
|
"Extranjera",
|
|
]
|
|
}
|
|
)
|
|
|
|
result, log = apply_rules(df, {"tipo": ["doc_type_homologate"]})
|
|
|
|
assert result["tipo"].tolist() == ["CC", "CC", "RC", "SC", "RC", "CE"]
|
|
assert any("Homologar tipo de documento" in line for line in log)
|
|
|
|
|
|
def test_apply_rules_split_uses_homologation_when_selected() -> None:
|
|
df = pd.DataFrame(
|
|
{
|
|
"documento": [
|
|
"Cedula 123.456",
|
|
"Registro Civil: 001-23",
|
|
"ABC Especial: 123-45",
|
|
]
|
|
}
|
|
)
|
|
|
|
result, _log = apply_rules(
|
|
df,
|
|
{"documento": ["doc_id_split_tipo_numero", "doc_type_homologate"]},
|
|
)
|
|
|
|
assert result["documento_doc_tipo"].tolist() == ["CC", "RC", "ABC ESPECIAL"]
|
|
assert result["documento_doc_numeros"].tolist() == ["123456", "00123", "12345"]
|
|
assert result["documento"].tolist() == df["documento"].tolist()
|
|
|
|
|
|
def test_apply_rules_split_can_skip_homologation() -> None:
|
|
df = pd.DataFrame({"documento": ["Cedula 123.456"]})
|
|
|
|
result, _log = apply_rules(df, {"documento": ["doc_id_split_tipo_numero"]})
|
|
|
|
assert result["documento_doc_tipo"].tolist() == ["CEDULA"]
|
|
assert result["documento_doc_numeros"].tolist() == ["123456"]
|
|
|
|
|
|
def test_apply_rules_unified_date_rule_numeric_and_separated() -> None:
|
|
df = pd.DataFrame(
|
|
{
|
|
"fecha": [
|
|
"20260426",
|
|
"26042026",
|
|
"260426",
|
|
"26/04/2026",
|
|
"04/26/2026",
|
|
"2026-04-26 10:30",
|
|
]
|
|
}
|
|
)
|
|
|
|
result, log = apply_rules(df, {"fecha": ["date_to_ddmmyyyy"]})
|
|
|
|
assert result["fecha"].tolist() == [
|
|
"26/04/2026",
|
|
"26/04/2026",
|
|
"26/04/2026",
|
|
"26/04/2026",
|
|
"26/04/2026",
|
|
"26/04/2026",
|
|
]
|
|
assert any("Normalizar fecha" in line for line in log)
|
|
|
|
|
|
def test_apply_rules_unified_date_rule_text_months() -> None:
|
|
df = pd.DataFrame(
|
|
{
|
|
"fecha": [
|
|
"26-abril-2026",
|
|
"26 abril 2026",
|
|
"26/abr/2026",
|
|
"abril 26 2026",
|
|
"2026 abril 26",
|
|
"15-sept-25",
|
|
"07 setiembre 2024",
|
|
]
|
|
}
|
|
)
|
|
|
|
result, _log = apply_rules(df, {"fecha": ["date_to_ddmmyyyy"]})
|
|
|
|
assert result["fecha"].tolist() == [
|
|
"26/04/2026",
|
|
"26/04/2026",
|
|
"26/04/2026",
|
|
"26/04/2026",
|
|
"26/04/2026",
|
|
"15/09/2025",
|
|
"07/09/2024",
|
|
]
|
|
|
|
|
|
def test_apply_rules_unified_date_spanish_abbr_month_with_time() -> None:
|
|
df = pd.DataFrame(
|
|
{
|
|
"fecha": [
|
|
"15/may./2024 13:44",
|
|
"22/may./2024 11:09",
|
|
"30/04/2024",
|
|
]
|
|
}
|
|
)
|
|
|
|
result, _log = apply_rules(df, {"fecha": ["date_to_ddmmyyyy"]})
|
|
|
|
assert result["fecha"].tolist() == [
|
|
"15/05/2024",
|
|
"22/05/2024",
|
|
"30/04/2024",
|
|
]
|
|
|
|
|
|
def test_apply_rules_date_after_strip_special_spaces_glued_year_time() -> None:
|
|
df = pd.DataFrame({"fecha": ["15/may./2024 13:44"]})
|
|
|
|
result, _log = apply_rules(
|
|
df,
|
|
{"fecha": ["strip_special_spaces", "date_to_ddmmyyyy"]},
|
|
)
|
|
|
|
assert result["fecha"].tolist() == ["15/05/2024"]
|
|
|
|
|
|
def test_apply_rules_keep_date_seps_preserves_time_then_normalizes() -> None:
|
|
df = pd.DataFrame({"fecha": ["30/04/2024 13:44"]})
|
|
|
|
result, _log = apply_rules(
|
|
df,
|
|
{"fecha": ["strip_special_keep_date_seps", "date_to_ddmmyyyy"]},
|
|
)
|
|
|
|
assert result["fecha"].tolist() == ["30/04/2024"]
|
|
|
|
|
|
def test_apply_rules_us_style_date_comma_then_am_pm_strips_time() -> None:
|
|
"""Formato tipo Excel/locale US: mes/día/año, coma y hora — solo fecha DD/MM/AAAA."""
|
|
df = pd.DataFrame({"fecha": ["12/27/24, 12:00 AM"]})
|
|
|
|
result, _log = apply_rules(df, {"fecha": ["date_to_ddmmyyyy"]})
|
|
|
|
assert result["fecha"].tolist() == ["27/12/2024"]
|
|
|
|
|
|
def test_apply_rules_unified_date_rule_noise_and_unparseable_values() -> None:
|
|
df = pd.DataFrame(
|
|
{
|
|
"fecha": [
|
|
"**26_abril_2026++",
|
|
"Fecha: 26-abril-2026",
|
|
"xx26/04/2026yy",
|
|
"no es fecha",
|
|
"31-febrero-2026",
|
|
]
|
|
}
|
|
)
|
|
|
|
result, _log = apply_rules(df, {"fecha": ["date_to_ddmmyyyy"]})
|
|
|
|
assert result["fecha"].tolist() == [
|
|
"26/04/2026",
|
|
"26/04/2026",
|
|
"26/04/2026",
|
|
"no es fecha",
|
|
"31-febrero-2026",
|
|
]
|
|
|
|
|
|
def test_apply_rules_cop_mod_moderadora_matches_year() -> None:
|
|
df = pd.DataFrame(
|
|
{"Fecha Servicio": ["01/01/2024"], "Importe": [4500]},
|
|
)
|
|
result, _log = apply_rules(
|
|
df,
|
|
{"Importe": ["cop_mod_split"]},
|
|
cop_mod_fecha_column={"Importe": "Fecha Servicio"},
|
|
cop_mod_config={"moderadora_by_year": {"2024": [4500, 18200, 47700]}},
|
|
)
|
|
assert result["Importe_moderadora"].tolist() == [4500]
|
|
assert result["Importe_copago"].tolist() == [0]
|
|
|
|
|
|
def test_apply_rules_cop_mod_non_moderadora_to_copago() -> None:
|
|
df = pd.DataFrame(
|
|
{"Fecha Servicio": ["01/01/2024"], "Importe": [4982]},
|
|
)
|
|
result, _log = apply_rules(
|
|
df,
|
|
{"Importe": ["cop_mod_split"]},
|
|
cop_mod_fecha_column={"Importe": "Fecha Servicio"},
|
|
cop_mod_config={"moderadora_by_year": {"2024": [4500, 18200, 47700]}},
|
|
)
|
|
assert result["Importe_copago"].tolist() == [4982]
|
|
assert result["Importe_moderadora"].tolist() == [0]
|
|
|
|
|
|
def test_apply_rules_cop_mod_two_phase_dirty_date() -> None:
|
|
df = pd.DataFrame(
|
|
{"Fecha Servicio": ["15/jun/2024"], "Importe": [4500]},
|
|
)
|
|
result, _log = apply_rules(
|
|
df,
|
|
{
|
|
"Fecha Servicio": ["date_to_ddmmyyyy"],
|
|
"Importe": ["cop_mod_split"],
|
|
},
|
|
cop_mod_fecha_column={"Importe": "Fecha Servicio"},
|
|
cop_mod_config={"moderadora_by_year": {"2024": [4500, 18200, 47700]}},
|
|
)
|
|
assert result["Fecha Servicio"].iloc[0] == "15/06/2024"
|
|
assert result["Importe_moderadora"].iloc[0] == 4500
|
|
|
|
|
|
def test_apply_rules_cop_mod_year_not_in_table_goes_copago() -> None:
|
|
df = pd.DataFrame(
|
|
{"Fecha Servicio": ["01/01/2030"], "Importe": [4500]},
|
|
)
|
|
result, _log = apply_rules(
|
|
df,
|
|
{"Importe": ["cop_mod_split"]},
|
|
cop_mod_fecha_column={"Importe": "Fecha Servicio"},
|
|
cop_mod_config={"moderadora_by_year": {"2024": [4500]}},
|
|
)
|
|
assert result["Importe_copago"].tolist() == [4500]
|
|
assert result["Importe_moderadora"].tolist() == [0]
|
|
|
|
|
|
def test_apply_rules_cop_mod_null_amount_zeros() -> None:
|
|
df = pd.DataFrame({"Fecha Servicio": ["01/01/2024"], "Importe": [pd.NA]})
|
|
result, _log = apply_rules(
|
|
df,
|
|
{"Importe": ["cop_mod_split"]},
|
|
cop_mod_fecha_column={"Importe": "Fecha Servicio"},
|
|
cop_mod_config={"moderadora_by_year": {"2024": [4500]}},
|
|
)
|
|
assert result["Importe_copago"].tolist() == [0]
|
|
assert result["Importe_moderadora"].tolist() == [0]
|
|
|
|
|
|
def test_apply_rules_nota_extract_one_line_sin_autorizacion() -> None:
|
|
texto = (
|
|
"Las retenciones son a nivel informativo no afecta el total a pagar. "
|
|
"Paciente: 1063146603 - VILLA BARRIOS NORMA JUDITH Atención No: 63728; "
|
|
"Contrato No. URGENCIAS. Valor en Letras: CIENTO OCHENTA Y SIETE MIL "
|
|
"QUINIENTOS TREINTA Y SIETE PESOS Fecha ingreso 19/11/2023 fecha egreso 19/11/2023"
|
|
)
|
|
df = pd.DataFrame({"notas": [texto]})
|
|
result, log = apply_rules(df, {"notas": ["nota_paciente_autorizacion"]})
|
|
assert result["notas_nota_paciente_doc"].iloc[0] == "1063146603"
|
|
assert pd.isna(result["notas_nota_autorizacion"].iloc[0])
|
|
assert any("Extraer datos de notas" in line for line in log)
|
|
|
|
|
|
def test_apply_rules_nota_extract_multiline_con_autorizacion() -> None:
|
|
texto = (
|
|
"Las retenciones son a nivel informativo no afecta el total a pagar.\n"
|
|
"Paciente: 30662603 - LLORENTE HERNANDEZ CARMEN LUCIA Atención No: 69426;\n"
|
|
"Autorización No. 227490546; Contrato No. 118. Valor en Letras: CIENTO NOVENTA Y OCHO\n"
|
|
"MIL SEISCIENTOS DIEZ PESOS Fecha ingreso 23/01/2024 fecha egreso 23/01/2024"
|
|
)
|
|
df = pd.DataFrame({"notas": [texto]})
|
|
result, _log = apply_rules(df, {"notas": ["nota_paciente_autorizacion"]})
|
|
assert result["notas_nota_paciente_doc"].iloc[0] == "30662603"
|
|
assert result["notas_nota_autorizacion"].iloc[0] == "227490546"
|
|
|
|
|
|
def test_apply_rules_nota_extract_sin_patrones() -> None:
|
|
df = pd.DataFrame({"notas": ["sin datos relevantes aquí"]})
|
|
result, _log = apply_rules(df, {"notas": ["nota_paciente_autorizacion"]})
|
|
assert pd.isna(result["notas_nota_paciente_doc"].iloc[0])
|
|
assert pd.isna(result["notas_nota_autorizacion"].iloc[0])
|