Optimizaciones de rendimiento: consultas paralelas en API, caché de Regex en parser de laboratorio y pre-cálculo de claves en sortCamas
This commit is contained in:
+78
-24
@@ -79,33 +79,87 @@ function generateUUID() {
|
|||||||
// ========== STATE ENDPOINT (initial load) ==========
|
// ========== STATE ENDPOINT (initial load) ==========
|
||||||
app.get('/api/state', async (req, res) => {
|
app.get('/api/state', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const areasList = await getAllAreas();
|
const [
|
||||||
const usuariosList = await getAllUsuarios();
|
pacientes,
|
||||||
const state = {
|
areasList,
|
||||||
pacientes: await getAllPacientes(),
|
camas,
|
||||||
areas: areasList,
|
internaciones,
|
||||||
grupos: areasList,
|
rawEvoluciones,
|
||||||
camas: await getAllCamas(),
|
rawLaboratorios,
|
||||||
internaciones: await getAllInternaciones(),
|
otrosLaboratorios,
|
||||||
evoluciones: (await getAllEvoluciones()).map(e => ({
|
glucemias,
|
||||||
|
acidosBase,
|
||||||
|
cultivos,
|
||||||
|
tiposCultivo,
|
||||||
|
gruposDeterminacionesLab,
|
||||||
|
estudiosComplementarios,
|
||||||
|
rawInterconsultas,
|
||||||
|
atb,
|
||||||
|
indicaciones,
|
||||||
|
movimientosIndicaciones,
|
||||||
|
pendientes,
|
||||||
|
usuariosList
|
||||||
|
] = await Promise.all([
|
||||||
|
getAllPacientes(),
|
||||||
|
getAllAreas(),
|
||||||
|
getAllCamas(),
|
||||||
|
getAllInternaciones(),
|
||||||
|
getAllEvoluciones(),
|
||||||
|
getAllLaboratorios(),
|
||||||
|
getAllOtrosLaboratorios(),
|
||||||
|
getAllGlucemias(),
|
||||||
|
getAllAcidosBase(),
|
||||||
|
getAllCultivos(),
|
||||||
|
getAllTiposCultivo(),
|
||||||
|
getAllGruposLaboratorio(),
|
||||||
|
getAllEstudiosComplementarios(),
|
||||||
|
getAllInterconsultas(),
|
||||||
|
getAllAtb(),
|
||||||
|
getAllIndicaciones(),
|
||||||
|
getAllMovimientosIndicaciones(),
|
||||||
|
getAllPendientes(),
|
||||||
|
getAllUsuarios()
|
||||||
|
]);
|
||||||
|
|
||||||
|
const evoluciones = rawEvoluciones.map(e => ({
|
||||||
...e,
|
...e,
|
||||||
signosVitales: typeof e.signosVitales === 'string' ? JSON.parse(e.signosVitales) : e.signosVitales,
|
signosVitales: typeof e.signosVitales === 'string' ? JSON.parse(e.signosVitales) : e.signosVitales,
|
||||||
examenFisico: typeof e.examenFisico === 'string' ? JSON.parse(e.examenFisico) : e.examenFisico
|
examenFisico: typeof e.examenFisico === 'string' ? JSON.parse(e.examenFisico) : e.examenFisico
|
||||||
})),
|
}));
|
||||||
laboratorios: (await getAllLaboratorios()).map(l => ({ ...l, resultados: typeof l.resultados === 'string' ? JSON.parse(l.resultados) : l.resultados })),
|
|
||||||
otrosLaboratorios: await getAllOtrosLaboratorios(),
|
const laboratorios = rawLaboratorios.map(l => ({
|
||||||
glucemias: await getAllGlucemias(),
|
...l,
|
||||||
acidosBase: await getAllAcidosBase(),
|
resultados: typeof l.resultados === 'string' ? JSON.parse(l.resultados) : l.resultados
|
||||||
cultivos: await getAllCultivos(),
|
}));
|
||||||
tiposCultivo: await getAllTiposCultivo(),
|
|
||||||
gruposDeterminacionesLab: await getAllGruposLaboratorio(),
|
const interconsultas = rawInterconsultas.map(ic => ({
|
||||||
estudiosComplementarios: await getAllEstudiosComplementarios(),
|
...ic,
|
||||||
interconsultas: (await getAllInterconsultas()).map(ic => ({ ...ic, realizada: !!ic.realizada })),
|
realizada: !!ic.realizada
|
||||||
atb: await getAllAtb(),
|
}));
|
||||||
indicaciones: await getAllIndicaciones(),
|
|
||||||
movimientosIndicaciones: await getAllMovimientosIndicaciones(),
|
const usuarios = usuariosList.map(({ passwordHash, ...u }) => u);
|
||||||
pendientes: await getAllPendientes(),
|
|
||||||
usuarios: usuariosList.map(({ passwordHash, ...u }) => u),
|
const state = {
|
||||||
|
pacientes,
|
||||||
|
areas: areasList,
|
||||||
|
grupos: areasList,
|
||||||
|
camas,
|
||||||
|
internaciones,
|
||||||
|
evoluciones,
|
||||||
|
laboratorios,
|
||||||
|
otrosLaboratorios,
|
||||||
|
glucemias,
|
||||||
|
acidosBase,
|
||||||
|
cultivos,
|
||||||
|
tiposCultivo,
|
||||||
|
gruposDeterminacionesLab,
|
||||||
|
estudiosComplementarios,
|
||||||
|
interconsultas,
|
||||||
|
atb,
|
||||||
|
indicaciones,
|
||||||
|
movimientosIndicaciones,
|
||||||
|
pendientes,
|
||||||
|
usuarios,
|
||||||
vistaActual: 'dashboard',
|
vistaActual: 'dashboard',
|
||||||
currentInternacionId: null
|
currentInternacionId: null
|
||||||
};
|
};
|
||||||
|
|||||||
+41
-40
@@ -160,16 +160,19 @@ export const CORE_PARAMETERS_MAPPING = [
|
|||||||
{ claves: ['aptt', 'kptt'], nombre: 'KPTT', unidad: 'seg', esPrincipal: true },
|
{ claves: ['aptt', 'kptt'], nombre: 'KPTT', unidad: 'seg', esPrincipal: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function esParametroCore(param: string): boolean {
|
const CORE_LIST_SET = new Set([
|
||||||
const coreList = [
|
|
||||||
'hematocrito', 'hto', 'hemoglobina', 'hb', 'leucocitos', 'gb', 'plaquetas', 'plaq',
|
'hematocrito', 'hto', 'hemoglobina', 'hb', 'leucocitos', 'gb', 'plaquetas', 'plaq',
|
||||||
'glucemia', 'glucosa', 'urea', 'creatinina', 'creat', 'sodio', 'na', 'potasio', 'k', 'k+',
|
'glucemia', 'glucosa', 'urea', 'creatinina', 'creat', 'sodio', 'na', 'potasio', 'k', 'k+',
|
||||||
'cloro', 'cl', 'cl-', 'bilirrubina total', 'bt', 'bilirrubina directa', 'bd', 'got', 'ast', 'gpt', 'alt',
|
'cloro', 'cl', 'cl-', 'bilirrubina total', 'bt', 'bilirrubina directa', 'bd', 'got', 'ast', 'gpt', 'alt',
|
||||||
'tiempo de protrombina', 'tp', 't.p.', 't.p', 'rin', 'inr', 'aptt', 'kptt', 'proteínas totales', 'proteinas totales'
|
'tiempo de protrombina', 'tp', 't.p.', 't.p', 'rin', 'inr', 'aptt', 'kptt', 'proteínas totales', 'proteinas totales'
|
||||||
];
|
]);
|
||||||
return coreList.includes(param.toLowerCase().trim());
|
|
||||||
|
export function esParametroCore(param: string): boolean {
|
||||||
|
return CORE_LIST_SET.has(param.toLowerCase().trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const regexCache = new Map<string, RegExp>();
|
||||||
|
|
||||||
export function matchClaveTexto(lineaLower: string, clave: string): boolean {
|
export function matchClaveTexto(lineaLower: string, clave: string): boolean {
|
||||||
const c = clave.toLowerCase().trim();
|
const c = clave.toLowerCase().trim();
|
||||||
if (!c) return false;
|
if (!c) return false;
|
||||||
@@ -182,13 +185,21 @@ export function matchClaveTexto(lineaLower: string, clave: string): boolean {
|
|||||||
if (c.length > 4) {
|
if (c.length > 4) {
|
||||||
return lineaLower.includes(c);
|
return lineaLower.includes(c);
|
||||||
}
|
}
|
||||||
|
let regex = regexCache.get(c);
|
||||||
|
if (!regex) {
|
||||||
const escaped = c.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
const escaped = c.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
const regex = new RegExp(`(?:^|[^a-z0-9_])${escaped}(?:$|[^a-z0-9_])`, 'i');
|
regex = new RegExp(`(?:^|[^a-z0-9_])${escaped}(?:$|[^a-z0-9_])`, 'i');
|
||||||
|
regexCache.set(c, regex);
|
||||||
|
}
|
||||||
return regex.test(lineaLower);
|
return regex.test(lineaLower);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseAcidoBaseTexto(texto: string, fecha?: string, pacienteId?: string): Omit<AcidoBase, 'id'> | null {
|
export function parseAcidoBaseTexto(texto: string, fecha?: string, pacienteId?: string): Omit<AcidoBase, 'id'> | null {
|
||||||
const lineas = texto.split('\n');
|
const lineas = texto.split('\n');
|
||||||
|
const cleanLineas = lineas.map(linea =>
|
||||||
|
linea.replace(/show_chart|list_alt|\t+/g, ' ').replace(/,/g, '.').replace(/\s+/g, ' ').trim().toLowerCase()
|
||||||
|
);
|
||||||
|
|
||||||
let ph: number | undefined;
|
let ph: number | undefined;
|
||||||
let pco2: number | undefined;
|
let pco2: number | undefined;
|
||||||
let po2: number | undefined;
|
let po2: number | undefined;
|
||||||
@@ -198,9 +209,7 @@ export function parseAcidoBaseTexto(texto: string, fecha?: string, pacienteId?:
|
|||||||
let lactato: number | undefined;
|
let lactato: number | undefined;
|
||||||
let fio2: number | undefined;
|
let fio2: number | undefined;
|
||||||
|
|
||||||
for (const linea of lineas) {
|
const getValue = (cleanLinea: string, param: string): number | undefined => {
|
||||||
const cleanLinea = linea.replace(/show_chart|list_alt|\t+/g, ' ').replace(/,/g, '.').replace(/\s+/g, ' ').trim().toLowerCase();
|
|
||||||
const getValue = (param: string): number | undefined => {
|
|
||||||
const idx = cleanLinea.indexOf(param);
|
const idx = cleanLinea.indexOf(param);
|
||||||
if (idx === -1) return undefined;
|
if (idx === -1) return undefined;
|
||||||
const after = cleanLinea.slice(idx + param.length).trim();
|
const after = cleanLinea.slice(idx + param.length).trim();
|
||||||
@@ -212,42 +221,31 @@ export function parseAcidoBaseTexto(texto: string, fecha?: string, pacienteId?:
|
|||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
for (const cleanLinea of cleanLineas) {
|
||||||
if ((cleanLinea.includes('estado') && cleanLinea.includes('ácido')) || cleanLinea.includes('base') || cleanLinea.includes('gases en sangre')) {
|
if ((cleanLinea.includes('estado') && cleanLinea.includes('ácido')) || cleanLinea.includes('base') || cleanLinea.includes('gases en sangre')) {
|
||||||
ph = getValue('ph') || (cleanLinea.match(/ph\s+(\d+\.?\d*)/)?.[1] ? parseFloat(cleanLinea.match(/ph\s+(\d+\.?\d*)/)![1]) : undefined);
|
const phMatch = cleanLinea.match(/ph\s+(\d+\.?\d*)/);
|
||||||
pco2 = getValue('pco2') || getValue('pco₂');
|
ph = getValue(cleanLinea, 'ph') || (phMatch ? parseFloat(phMatch[1]) : undefined);
|
||||||
po2 = getValue('po2') || getValue('po₂');
|
pco2 = getValue(cleanLinea, 'pco2') || getValue(cleanLinea, 'pco₂');
|
||||||
hco3 = getValue('hco3');
|
po2 = getValue(cleanLinea, 'po2') || getValue(cleanLinea, 'po₂');
|
||||||
be = getValue('exceso de base') || getValue('base excess') || getValue('exceso');
|
hco3 = getValue(cleanLinea, 'hco3');
|
||||||
sato2 = getValue('saturación') || getValue('sato2') || getValue('sat');
|
be = getValue(cleanLinea, 'exceso de base') || getValue(cleanLinea, 'base excess') || getValue(cleanLinea, 'exceso');
|
||||||
lactato = getValue('lactato');
|
sato2 = getValue(cleanLinea, 'saturación') || getValue(cleanLinea, 'sato2') || getValue(cleanLinea, 'sat');
|
||||||
fio2 = getValue('fio2');
|
lactato = getValue(cleanLinea, 'lactato');
|
||||||
|
fio2 = getValue(cleanLinea, 'fio2');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ph) {
|
if (!ph) {
|
||||||
for (const linea of lineas) {
|
for (const cleanLinea of cleanLineas) {
|
||||||
const cleanLinea = linea.replace(/show_chart|list_alt|\t+/g, ' ').replace(/,/g, '.').replace(/\s+/g, ' ').trim().toLowerCase();
|
if (cleanLinea.includes('ph') && !cleanLinea.includes('pco2') && !cleanLinea.includes('pco')) { ph = getValue(cleanLinea, 'ph'); }
|
||||||
const getValue = (param: string): number | undefined => {
|
else if (cleanLinea.includes('pco2') || cleanLinea.includes('pco₂')) { pco2 = getValue(cleanLinea, 'pco2'); }
|
||||||
const idx = cleanLinea.indexOf(param);
|
else if (cleanLinea.includes('po2') || cleanLinea.includes('po₂')) { po2 = getValue(cleanLinea, 'po2'); }
|
||||||
if (idx === -1) return undefined;
|
else if (cleanLinea.includes('hco3')) { hco3 = getValue(cleanLinea, 'hco3'); }
|
||||||
const after = cleanLinea.slice(idx + param.length).trim();
|
else if (cleanLinea.includes('exceso') || cleanLinea.includes('base excess')) { be = getValue(cleanLinea, 'exceso') || getValue(cleanLinea, 'base'); }
|
||||||
const parts = after.split(' ');
|
else if (cleanLinea.includes('saturación') || cleanLinea.includes('sato2')) { sato2 = getValue(cleanLinea, 'saturación') || getValue(cleanLinea, 'sat'); }
|
||||||
for (const p of parts) {
|
else if (cleanLinea.includes('lactato')) { lactato = getValue(cleanLinea, 'lactato'); }
|
||||||
const v = parseFloat(p);
|
else if (cleanLinea.includes('fio2')) { fio2 = getValue(cleanLinea, 'fio2'); }
|
||||||
if (!isNaN(v) && v > 0 && v < 1000) return v;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (cleanLinea.includes('ph') && !cleanLinea.includes('pco2') && !cleanLinea.includes('pco')) { ph = getValue('ph'); }
|
|
||||||
else if (cleanLinea.includes('pco2') || cleanLinea.includes('pco₂')) { pco2 = getValue('pco2'); }
|
|
||||||
else if (cleanLinea.includes('po2') || cleanLinea.includes('po₂')) { po2 = getValue('po2'); }
|
|
||||||
else if (cleanLinea.includes('hco3')) { hco3 = getValue('hco3'); }
|
|
||||||
else if (cleanLinea.includes('exceso') || cleanLinea.includes('base excess')) { be = getValue('exceso') || getValue('base'); }
|
|
||||||
else if (cleanLinea.includes('saturación') || cleanLinea.includes('sato2')) { sato2 = getValue('saturación') || getValue('sat'); }
|
|
||||||
else if (cleanLinea.includes('lactato')) { lactato = getValue('lactato'); }
|
|
||||||
else if (cleanLinea.includes('fio2')) { fio2 = getValue('fio2'); }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,6 +277,7 @@ export function parseLaboratorioTextoCompleto(
|
|||||||
const resultados: ResultadoLaboratorio[] = [];
|
const resultados: ResultadoLaboratorio[] = [];
|
||||||
const reconocidos: RecognizedDetermination[] = [];
|
const reconocidos: RecognizedDetermination[] = [];
|
||||||
const processedLines = new Set<number>();
|
const processedLines = new Set<number>();
|
||||||
|
const matchedParamNames = new Set<string>();
|
||||||
const lineas = texto.split('\n');
|
const lineas = texto.split('\n');
|
||||||
|
|
||||||
// 1. Process Core Parameters
|
// 1. Process Core Parameters
|
||||||
@@ -288,7 +287,7 @@ export function parseLaboratorioTextoCompleto(
|
|||||||
if (!lineaLower) continue;
|
if (!lineaLower) continue;
|
||||||
|
|
||||||
for (const core of CORE_PARAMETERS_MAPPING) {
|
for (const core of CORE_PARAMETERS_MAPPING) {
|
||||||
if (resultados.some(r => r.parametro === core.nombre)) continue;
|
if (matchedParamNames.has(core.nombre)) continue;
|
||||||
|
|
||||||
let matchedClave = false;
|
let matchedClave = false;
|
||||||
let matchedClaveString = '';
|
let matchedClaveString = '';
|
||||||
@@ -337,6 +336,7 @@ export function parseLaboratorioTextoCompleto(
|
|||||||
estado: calcularEstadoLaboratorioExtendido(core.nombre, String(valorFinal))
|
estado: calcularEstadoLaboratorioExtendido(core.nombre, String(valorFinal))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
matchedParamNames.add(core.nombre);
|
||||||
processedLines.add(lIdx);
|
processedLines.add(lIdx);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -350,7 +350,7 @@ export function parseLaboratorioTextoCompleto(
|
|||||||
|
|
||||||
for (const grupo of activeGroups) {
|
for (const grupo of activeGroups) {
|
||||||
for (const det of (grupo.determinaciones || [])) {
|
for (const det of (grupo.determinaciones || [])) {
|
||||||
if (resultados.some(r => r.parametro === det.nombre)) continue;
|
if (matchedParamNames.has(det.nombre)) continue;
|
||||||
|
|
||||||
for (let lIdx = 0; lIdx < lineas.length; lIdx++) {
|
for (let lIdx = 0; lIdx < lineas.length; lIdx++) {
|
||||||
const linea = lineas[lIdx];
|
const linea = lineas[lIdx];
|
||||||
@@ -409,6 +409,7 @@ export function parseLaboratorioTextoCompleto(
|
|||||||
|
|
||||||
reconocidos.push(recItem);
|
reconocidos.push(recItem);
|
||||||
processedLines.add(lIdx);
|
processedLines.add(lIdx);
|
||||||
|
matchedParamNames.add(det.nombre);
|
||||||
|
|
||||||
resultados.push({
|
resultados.push({
|
||||||
parametro: det.nombre,
|
parametro: det.nombre,
|
||||||
|
|||||||
+28
-39
@@ -53,55 +53,44 @@ export function isCamaFueraDeGrupo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function sortCamas<T extends { numero: string }>(camas: T[]): T[] {
|
export function sortCamas<T extends { numero: string }>(camas: T[]): T[] {
|
||||||
const parseBedNumber = (numero: string) => {
|
if (!camas || camas.length <= 1) return camas || [];
|
||||||
const match = (numero || '').match(/^(\d{3})-(\d+)$/);
|
|
||||||
if (!match) return { sala: 0, cama: 0 };
|
|
||||||
return {
|
|
||||||
sala: parseInt(match[1], 10),
|
|
||||||
cama: parseInt(match[2], 10)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return [...camas].sort((a, b) => {
|
const parseBedKey = (numero: string) => {
|
||||||
const pa = parseBedNumber(a.numero);
|
const clean = (numero || '').trim();
|
||||||
const pb = parseBedNumber(b.numero);
|
const match = clean.match(/^(\d{3})-(\d+)$/);
|
||||||
|
if (!match) return { weight: 10, salaDir: 0, cama: 0 };
|
||||||
|
|
||||||
const getOrden = (sala: number) => {
|
const sala = parseInt(match[1], 10);
|
||||||
|
const cama = parseInt(match[2], 10);
|
||||||
const grupo = Math.floor(sala / 100);
|
const grupo = Math.floor(sala / 100);
|
||||||
const esPar = sala % 2 === 0;
|
const esPar = sala % 2 === 0;
|
||||||
|
|
||||||
|
let weight = 10;
|
||||||
|
let isDesc = false;
|
||||||
|
|
||||||
if (grupo === 2) {
|
if (grupo === 2) {
|
||||||
if (!esPar) return { weight: 1, order: 'desc', sala }; // 2XX impares, mayor a menor
|
if (!esPar) { weight = 1; isDesc = true; }
|
||||||
return { weight: 2, order: 'asc', sala }; // 2XX pares, menor a mayor
|
else { weight = 2; isDesc = false; }
|
||||||
|
} else if (grupo === 3) {
|
||||||
|
if (esPar) { weight = 3; isDesc = false; }
|
||||||
|
else { weight = 4; isDesc = true; }
|
||||||
|
} else if (grupo === 4) {
|
||||||
|
if (!esPar) { weight = 5; isDesc = true; }
|
||||||
|
else { weight = 6; isDesc = false; }
|
||||||
}
|
}
|
||||||
if (grupo === 3) {
|
|
||||||
if (esPar) return { weight: 3, order: 'asc', sala }; // 3XX pares, menor a mayor
|
const salaDir = isDesc ? -sala : sala;
|
||||||
return { weight: 4, order: 'desc', sala }; // 3XX impares, mayor a menor
|
return { weight, salaDir, cama };
|
||||||
}
|
|
||||||
if (grupo === 4) {
|
|
||||||
if (!esPar) return { weight: 5, order: 'desc', sala }; // 4XX impares, mayor a menor
|
|
||||||
return { weight: 6, order: 'asc', sala }; // 4XX pares, menor a mayor
|
|
||||||
}
|
|
||||||
return { weight: 10, order: 'asc', sala };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const oa = getOrden(pa.sala);
|
const mapped = camas.map(item => ({ item, key: parseBedKey(item.numero) }));
|
||||||
const ob = getOrden(pb.sala);
|
mapped.sort((a, b) => {
|
||||||
|
if (a.key.weight !== b.key.weight) return a.key.weight - b.key.weight;
|
||||||
if (oa.weight !== ob.weight) {
|
if (a.key.salaDir !== b.key.salaDir) return a.key.salaDir - b.key.salaDir;
|
||||||
return oa.weight - ob.weight;
|
return a.key.cama - b.key.cama;
|
||||||
}
|
|
||||||
|
|
||||||
if (oa.sala !== ob.sala) {
|
|
||||||
if (oa.order === 'desc') {
|
|
||||||
return ob.sala - oa.sala;
|
|
||||||
} else {
|
|
||||||
return oa.sala - ob.sala;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pa.cama - pb.cama;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return mapped.map(m => m.item);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function computeSector(numero: string): "En Área" | "Fuera de Área" {
|
export function computeSector(numero: string): "En Área" | "Fuera de Área" {
|
||||||
|
|||||||
Reference in New Issue
Block a user