Spaces:
Sleeping
Sleeping
File size: 7,280 Bytes
bf9f7d1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | """Cosecha TODOS los intervalos de referencia impresos en los dos libros del índice RAG y los
compara con data/valores_referencia.json.
No usa recuperación semántica: escanea las 6763 filas de la tabla LanceDB y extrae filas de
tabla markdown con forma `|Analito (unidad)|valor|lo–hi|`. La especie se infiere del texto del
propio fragmento (señalamiento del caso) y la clave del analito se decide POR UNIDAD, para no
confundir un recuento relativo (%) con uno absoluto (×10³/µL).
"""
from __future__ import annotations
import json
import re
import sys
from collections import Counter, defaultdict
from pathlib import Path
RAIZ = Path("/Users/josesalazar/morphos_rev/morphos")
sys.path.insert(0, str(RAIZ / "backend"))
import lancedb # noqa: E402
from app.config import obtener_config # noqa: E402
# nombre normalizado -> (clave si la unidad es absoluta/propia, clave si la unidad es %)
ANALITOS: dict[str, tuple[str | None, str | None]] = {
"pcv": ("hct", None), "hct": ("hct", None), "hematocrit": ("hct", None),
"hgb": ("hgb", None), "hemoglobin": ("hgb", None),
"rbc": ("rbc", None), "rbcs": ("rbc", None),
"mcv": ("vcm", None), "mchc": ("chcm", None), "mch": ("hcm", None), "rdw": ("rdw", None),
"wbc": ("wbc", None), "nucleated cells": ("wbc", None), "ncc": ("wbc", None),
"segs": ("neutro_abs", "neutro"), "segmented neutrophils": ("neutro_abs", "neutro"),
"neutrophils": ("neutro_abs", "neutro"),
"lymphs": ("linfo_abs", "linfo"), "lymphocytes": ("linfo_abs", "linfo"),
"monos": ("mono_abs", "mono"), "monocytes": ("mono_abs", "mono"),
"eos": ("eosino_abs", "eosino"), "eosinophils": ("eosino_abs", "eosino"),
"platelets": ("plt", None), "plt": ("plt", None),
"retics": ("reti_abs", "reti"), "reticulocytes": ("reti_abs", "reti"),
"gluc": ("gluc", None), "glucose": ("gluc", None),
"bun": ("bun", None), "urea": ("bun", None), "un": ("bun", None),
"creat": ("creat", None), "creatinine": ("creat", None),
"ca": ("calc", None), "calcium": ("calc", None), "tca": ("calc", None),
"phos": ("fosf", None), "phosphorus": ("fosf", None),
"tp": ("prot", None), "total protein": ("prot", None), "tp p": ("prot", None),
"alb": ("alb", None), "albumin": ("alb", None),
"glob": ("glob", None), "globulin": ("glob", None), "globulins": ("glob", None),
"t. bili": ("bili", None), "tbili": ("bili", None), "bilirubin": ("bili", None),
"chol": ("colest", None), "cholesterol": ("colest", None),
"alt": ("alt", None), "ast": ("ast", None), "alp": ("fal", None), "sap": ("fal", None),
"ggt": ("ggt", None), "ck": ("ck", None), "creatine kinase": ("ck", None),
"amylase": ("amylasa", None), "lipase": ("lipasa", None),
"na": ("sodio", None), "sodium": ("sodio", None),
"cl": ("cloro", None), "chloride": ("cloro", None),
"k": ("potasio", None), "potassium": ("potasio", None),
"tco2": ("tco2", None), "mg": ("magnesio", None), "magnesium": ("magnesio", None),
"t4": ("t4_total", None), "total t4": ("t4_total", None), "tt4": ("t4_total", None),
"plasma protein": ("prot", None), "pp": ("prot", None),
}
PERRO = re.compile(r"\b(dog|canine|bitch|puppy|puppies|retriever|shepherd|terrier|poodle|beagle|dachshund|boxer|schnauzer|spaniel|rottweiler|collie|husky|greyhound)\b", re.I)
GATO = re.compile(r"\b(cat|feline|kitten|queen|DSH|DLH|domestic shorthair|domestic longhair|siamese|persian|abyssinian)\b", re.I)
OTRAS = re.compile(r"\b(horse|equine|foal|pony|cow|bovine|calf|cattle|sheep|ovine|goat|caprine|llama|alpaca|pig|porcine|ferret|rabbit|bird|avian|parrot|frog|reptile|snake|turtle)\b", re.I)
FILA = re.compile(
r"\|\s*\*{0,2}~{0,2}([A-Za-z][^|]{0,34}?)~{0,2}\*{0,2}\s*\|"
r"[^|]{0,40}?\|?"
r"\s*\*{0,2}(\d[\d.,]*)\s*[–-]\s*(\d[\d.,]*)\*{0,2}\s*\|"
)
def limpiar(txt: str) -> tuple[str, str]:
"""(nombre normalizado, unidad en minúsculas)."""
txt = re.sub(r"<[^>]+>", " ", txt)
unidad = " ".join(re.findall(r"\(([^)]*)\)", txt)).lower()
nombre = re.sub(r"\([^)]*\)", " ", txt)
nombre = re.sub(r"[~_*]", " ", nombre)
return re.sub(r"\s+", " ", nombre).strip().lower(), unidad
def especie_del_fragmento(texto: str) -> str | None:
if OTRAS.search(texto):
return None
perro, gato = len(PERRO.findall(texto)), len(GATO.findall(texto))
if perro and not gato:
return "canino"
if gato and not perro:
return "felino"
return None
def main() -> None:
cfg = obtener_config()
df = lancedb.connect(str(cfg.rag_index_dir)).open_table("literatura").to_pandas()
cosecha: dict[tuple[str, str, str], Counter] = defaultdict(Counter)
for _, fila in df.iterrows():
texto = fila["texto"]
if "|" not in texto:
continue
especie = especie_del_fragmento(texto)
if especie is None:
continue
libro = "Thrall" if "Hematology" in fila["libro"] else "Fundamentals"
for m in FILA.finditer(texto):
nombre, unidad = limpiar(m.group(1))
par = ANALITOS.get(nombre)
if not par:
continue
clave = par[1] if "%" in unidad and par[1] else par[0]
if not clave:
continue
try:
lo, hi = float(m.group(2).replace(",", "")), float(m.group(3).replace(",", ""))
except ValueError:
continue
if lo >= hi or hi > 100000:
continue
cosecha[(especie, clave, libro)][(lo, hi)] += 1
nuestros = json.loads((RAIZ / "data/valores_referencia.json").read_text(encoding="utf-8"))
salida: dict = {}
for (especie, clave, libro), cnt in cosecha.items():
salida.setdefault(especie, {}).setdefault(clave, {})[libro] = {
"modal": list(cnt.most_common(1)[0][0]),
"n": sum(cnt.values()),
"variantes": [[*k, v] for k, v in cnt.most_common()],
}
for especie in ("canino", "felino"):
print(f"\n{'='*112}\n{especie.upper()}\n{'='*112}")
print(f"{'analito':<11}{'Morphos':<15}{'Thrall (modal, n, variantes)':<42}{'Fundamentals':<30}{'¿coincide?'}")
for clave in sorted(salida.get(especie, {})):
ref = nuestros[especie].get(clave)
mio = f"{ref['inferior']}-{ref['superior']}" if ref else "SIN RANGO"
celdas = []
for libro in ("Thrall", "Fundamentals"):
d = salida[especie][clave].get(libro)
if not d:
celdas.append("—")
continue
var = "; ".join(f"{a:g}-{b:g}×{c}" for a, b, c in d["variantes"][:3])
celdas.append(f"{d['modal'][0]:g}-{d['modal'][1]:g} (n={d['n']}) [{var}]")
veredicto = ""
d = salida[especie][clave].get("Thrall")
if ref and d:
mlo, mhi = d["modal"]
veredicto = "=" if (mlo, mhi) == (ref["inferior"], ref["superior"]) else "DIFIERE"
print(f"{clave:<11}{mio:<15}{celdas[0]:<42}{celdas[1]:<30}{veredicto}")
Path("/Users/josesalazar/.claude/jobs/cf8c6f9d/tmp/cosecha_rangos.json").write_text(
json.dumps(salida, indent=1, ensure_ascii=False), encoding="utf-8")
if __name__ == "__main__":
main()
|