"""El puerto Python del suelo de seguridad coincide con el motor TS validado por veterinario. `app/motor/gravedad.py` es una SEGUNDA implementación de los rangos y la gravedad de `frontend/src/analisis.ts`. Se aceptó esa duplicación a cambio de no meter Node en la imagen de producción ni portar las 1062 líneas del motor entero (sólo se portó lo que sostiene el suelo de derivación, ~250). El riesgo obvio de tener dos implementaciones es que diverjan en silencio: alguien afina un umbral en el TS y el suelo del servidor se queda con el viejo, sin que nada falle. Esta prueba es el guardarraíl de esa decisión: ejecuta el motor REAL por el puente que ya usan las evals (`evals/engine_runner.ts`) y compara clave a clave con el puerto. Se omite donde no haya Node —igual que las pruebas de RAG se omiten sin índice— porque ahí no puede afirmar nada. """ from __future__ import annotations import json import shutil import subprocess import pytest from app.config import RAIZ_REPO from app.motor.gravedad import evaluar from app.schemas import PacienteEntrada RUNNER = RAIZ_REPO / "evals" / "engine_runner.ts" pytestmark = pytest.mark.skipif( shutil.which("node") is None or not RUNNER.exists(), reason="requiere Node y evals/engine_runner.ts", ) # Casos elegidos por lo que DECIDEN, no por cubrir analitos: cada uno toca una regla que, si # diverge, cambia si se deriva o no. CASOS = [ ({"hct": 12.0}, {"especie": "canino"}), # corte bajo: grave ({"hct": 25.0}, {"especie": "canino"}), # corte bajo: moderado ({"hct": 34.0}, {"especie": "canino"}), # corte bajo: leve ({"plt": 25.0}, {"especie": "felino"}), # trombocitopenia grave ({"creat": 6.0}, {"especie": "canino"}), # IRIS 4 → grave ({"creat": 2.0}, {"especie": "canino"}), # IRIS 2 → leve ({"fosf": 8.0}, {"especie": "canino", "edad_meses": 4}), # ajuste cachorro ({"fosf": 8.0}, {"especie": "canino", "edad_meses": 48}), # sin ajuste ({"plt": 160.0}, {"especie": "canino", "raza": "Galgo"}), # ajuste lebrel ({"plt": 160.0}, {"especie": "canino", "raza": "Mestizo"}), ({"t4_total": 1.0}, {"especie": "canino", "raza": "greyhound"}), # falso hipotiroidismo ({"vcm": 58.0}, {"especie": "canino", "raza": "Shiba Inu"}), # grupos combinados ({"bun": 40.0}, {"especie": "canino", "edad_meses": 130}), # geriátrico ({"wbc": 30.0}, {"especie": "felino", "edad_meses": 5}), ({"hct": 20.0, "hgb": 6.0, "rbc": 3.0}, {"especie": "felino"}), # panel múltiple ({"hct": 45.0, "creat": 1.0, "alt": 40.0}, {"especie": "canino"}), # todo normal ({"upc": 1.2}, {"especie": "canino"}), # tope en moderado ({"sdma": 60.0}, {"especie": "felino"}), ] def _motor_ts(valores: dict, paciente: dict) -> dict[str, str]: entrada = json.dumps({ "valores": valores, "paciente": { "especie": paciente.get("especie"), "raza": paciente.get("raza"), "edad_meses": paciente.get("edad_meses"), "sexo": paciente.get("sexo"), }, }) proc = subprocess.run( ["node", "--experimental-strip-types", str(RUNNER)], input=entrada, capture_output=True, text=True, cwd=RAIZ_REPO, ) assert proc.returncode == 0, proc.stderr[-500:] return {h["clave"]: h["gravedad"] for h in json.loads(proc.stdout)["hallazgos"]} @pytest.mark.parametrize(("valores", "paciente"), CASOS) def test_el_puerto_coincide_con_el_motor_real(valores, paciente): esperado = _motor_ts(valores, paciente) obtenido = { h.clave: h.gravedad.value for h in evaluar(valores, PacienteEntrada.model_validate(paciente)) } assert obtenido == esperado, ( "el puerto Python y analisis.ts discrepan: si el cambio del TS es intencionado, " "replícalo en app/motor/gravedad.py" )