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) ==========
|
||||
app.get('/api/state', async (req, res) => {
|
||||
try {
|
||||
const areasList = await getAllAreas();
|
||||
const usuariosList = await getAllUsuarios();
|
||||
const [
|
||||
pacientes,
|
||||
areasList,
|
||||
camas,
|
||||
internaciones,
|
||||
rawEvoluciones,
|
||||
rawLaboratorios,
|
||||
otrosLaboratorios,
|
||||
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,
|
||||
signosVitales: typeof e.signosVitales === 'string' ? JSON.parse(e.signosVitales) : e.signosVitales,
|
||||
examenFisico: typeof e.examenFisico === 'string' ? JSON.parse(e.examenFisico) : e.examenFisico
|
||||
}));
|
||||
|
||||
const laboratorios = rawLaboratorios.map(l => ({
|
||||
...l,
|
||||
resultados: typeof l.resultados === 'string' ? JSON.parse(l.resultados) : l.resultados
|
||||
}));
|
||||
|
||||
const interconsultas = rawInterconsultas.map(ic => ({
|
||||
...ic,
|
||||
realizada: !!ic.realizada
|
||||
}));
|
||||
|
||||
const usuarios = usuariosList.map(({ passwordHash, ...u }) => u);
|
||||
|
||||
const state = {
|
||||
pacientes: await getAllPacientes(),
|
||||
pacientes,
|
||||
areas: areasList,
|
||||
grupos: areasList,
|
||||
camas: await getAllCamas(),
|
||||
internaciones: await getAllInternaciones(),
|
||||
evoluciones: (await getAllEvoluciones()).map(e => ({
|
||||
...e,
|
||||
signosVitales: typeof e.signosVitales === 'string' ? JSON.parse(e.signosVitales) : e.signosVitales,
|
||||
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(),
|
||||
glucemias: await getAllGlucemias(),
|
||||
acidosBase: await getAllAcidosBase(),
|
||||
cultivos: await getAllCultivos(),
|
||||
tiposCultivo: await getAllTiposCultivo(),
|
||||
gruposDeterminacionesLab: await getAllGruposLaboratorio(),
|
||||
estudiosComplementarios: await getAllEstudiosComplementarios(),
|
||||
interconsultas: (await getAllInterconsultas()).map(ic => ({ ...ic, realizada: !!ic.realizada })),
|
||||
atb: await getAllAtb(),
|
||||
indicaciones: await getAllIndicaciones(),
|
||||
movimientosIndicaciones: await getAllMovimientosIndicaciones(),
|
||||
pendientes: await getAllPendientes(),
|
||||
usuarios: usuariosList.map(({ passwordHash, ...u }) => u),
|
||||
camas,
|
||||
internaciones,
|
||||
evoluciones,
|
||||
laboratorios,
|
||||
otrosLaboratorios,
|
||||
glucemias,
|
||||
acidosBase,
|
||||
cultivos,
|
||||
tiposCultivo,
|
||||
gruposDeterminacionesLab,
|
||||
estudiosComplementarios,
|
||||
interconsultas,
|
||||
atb,
|
||||
indicaciones,
|
||||
movimientosIndicaciones,
|
||||
pendientes,
|
||||
usuarios,
|
||||
vistaActual: 'dashboard',
|
||||
currentInternacionId: null
|
||||
};
|
||||
|
||||
+55
-54
@@ -160,16 +160,19 @@ export const CORE_PARAMETERS_MAPPING = [
|
||||
{ claves: ['aptt', 'kptt'], nombre: 'KPTT', unidad: 'seg', esPrincipal: true },
|
||||
];
|
||||
|
||||
const CORE_LIST_SET = new Set([
|
||||
'hematocrito', 'hto', 'hemoglobina', 'hb', 'leucocitos', 'gb', 'plaquetas', 'plaq',
|
||||
'glucemia', 'glucosa', 'urea', 'creatinina', 'creat', 'sodio', 'na', 'potasio', 'k', 'k+',
|
||||
'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'
|
||||
]);
|
||||
|
||||
export function esParametroCore(param: string): boolean {
|
||||
const coreList = [
|
||||
'hematocrito', 'hto', 'hemoglobina', 'hb', 'leucocitos', 'gb', 'plaquetas', 'plaq',
|
||||
'glucemia', 'glucosa', 'urea', 'creatinina', 'creat', 'sodio', 'na', 'potasio', 'k', 'k+',
|
||||
'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'
|
||||
];
|
||||
return coreList.includes(param.toLowerCase().trim());
|
||||
return CORE_LIST_SET.has(param.toLowerCase().trim());
|
||||
}
|
||||
|
||||
const regexCache = new Map<string, RegExp>();
|
||||
|
||||
export function matchClaveTexto(lineaLower: string, clave: string): boolean {
|
||||
const c = clave.toLowerCase().trim();
|
||||
if (!c) return false;
|
||||
@@ -182,13 +185,21 @@ export function matchClaveTexto(lineaLower: string, clave: string): boolean {
|
||||
if (c.length > 4) {
|
||||
return lineaLower.includes(c);
|
||||
}
|
||||
const escaped = c.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const regex = new RegExp(`(?:^|[^a-z0-9_])${escaped}(?:$|[^a-z0-9_])`, 'i');
|
||||
let regex = regexCache.get(c);
|
||||
if (!regex) {
|
||||
const escaped = c.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
regex = new RegExp(`(?:^|[^a-z0-9_])${escaped}(?:$|[^a-z0-9_])`, 'i');
|
||||
regexCache.set(c, regex);
|
||||
}
|
||||
return regex.test(lineaLower);
|
||||
}
|
||||
|
||||
export function parseAcidoBaseTexto(texto: string, fecha?: string, pacienteId?: string): Omit<AcidoBase, 'id'> | null {
|
||||
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 pco2: number | undefined;
|
||||
let po2: number | undefined;
|
||||
@@ -198,56 +209,43 @@ export function parseAcidoBaseTexto(texto: string, fecha?: string, pacienteId?:
|
||||
let lactato: number | undefined;
|
||||
let fio2: number | undefined;
|
||||
|
||||
for (const linea of lineas) {
|
||||
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);
|
||||
if (idx === -1) return undefined;
|
||||
const after = cleanLinea.slice(idx + param.length).trim();
|
||||
const parts = after.split(' ');
|
||||
for (const p of parts) {
|
||||
const v = parseFloat(p);
|
||||
if (!isNaN(v) && v > 0 && v < 1000) return v;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
const getValue = (cleanLinea: string, param: string): number | undefined => {
|
||||
const idx = cleanLinea.indexOf(param);
|
||||
if (idx === -1) return undefined;
|
||||
const after = cleanLinea.slice(idx + param.length).trim();
|
||||
const parts = after.split(' ');
|
||||
for (const p of parts) {
|
||||
const v = parseFloat(p);
|
||||
if (!isNaN(v) && v > 0 && v < 1000) return v;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
for (const cleanLinea of cleanLineas) {
|
||||
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);
|
||||
pco2 = getValue('pco2') || getValue('pco₂');
|
||||
po2 = getValue('po2') || getValue('po₂');
|
||||
hco3 = getValue('hco3');
|
||||
be = getValue('exceso de base') || getValue('base excess') || getValue('exceso');
|
||||
sato2 = getValue('saturación') || getValue('sato2') || getValue('sat');
|
||||
lactato = getValue('lactato');
|
||||
fio2 = getValue('fio2');
|
||||
const phMatch = cleanLinea.match(/ph\s+(\d+\.?\d*)/);
|
||||
ph = getValue(cleanLinea, 'ph') || (phMatch ? parseFloat(phMatch[1]) : undefined);
|
||||
pco2 = getValue(cleanLinea, 'pco2') || getValue(cleanLinea, 'pco₂');
|
||||
po2 = getValue(cleanLinea, 'po2') || getValue(cleanLinea, 'po₂');
|
||||
hco3 = getValue(cleanLinea, 'hco3');
|
||||
be = getValue(cleanLinea, 'exceso de base') || getValue(cleanLinea, 'base excess') || getValue(cleanLinea, 'exceso');
|
||||
sato2 = getValue(cleanLinea, 'saturación') || getValue(cleanLinea, 'sato2') || getValue(cleanLinea, 'sat');
|
||||
lactato = getValue(cleanLinea, 'lactato');
|
||||
fio2 = getValue(cleanLinea, 'fio2');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ph) {
|
||||
for (const linea of lineas) {
|
||||
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);
|
||||
if (idx === -1) return undefined;
|
||||
const after = cleanLinea.slice(idx + param.length).trim();
|
||||
const parts = after.split(' ');
|
||||
for (const p of parts) {
|
||||
const v = parseFloat(p);
|
||||
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'); }
|
||||
for (const cleanLinea of cleanLineas) {
|
||||
if (cleanLinea.includes('ph') && !cleanLinea.includes('pco2') && !cleanLinea.includes('pco')) { ph = getValue(cleanLinea, 'ph'); }
|
||||
else if (cleanLinea.includes('pco2') || cleanLinea.includes('pco₂')) { pco2 = getValue(cleanLinea, 'pco2'); }
|
||||
else if (cleanLinea.includes('po2') || cleanLinea.includes('po₂')) { po2 = getValue(cleanLinea, 'po2'); }
|
||||
else if (cleanLinea.includes('hco3')) { hco3 = getValue(cleanLinea, 'hco3'); }
|
||||
else if (cleanLinea.includes('exceso') || cleanLinea.includes('base excess')) { be = getValue(cleanLinea, 'exceso') || getValue(cleanLinea, 'base'); }
|
||||
else if (cleanLinea.includes('saturación') || cleanLinea.includes('sato2')) { sato2 = getValue(cleanLinea, 'saturación') || getValue(cleanLinea, 'sat'); }
|
||||
else if (cleanLinea.includes('lactato')) { lactato = getValue(cleanLinea, 'lactato'); }
|
||||
else if (cleanLinea.includes('fio2')) { fio2 = getValue(cleanLinea, 'fio2'); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,6 +277,7 @@ export function parseLaboratorioTextoCompleto(
|
||||
const resultados: ResultadoLaboratorio[] = [];
|
||||
const reconocidos: RecognizedDetermination[] = [];
|
||||
const processedLines = new Set<number>();
|
||||
const matchedParamNames = new Set<string>();
|
||||
const lineas = texto.split('\n');
|
||||
|
||||
// 1. Process Core Parameters
|
||||
@@ -288,7 +287,7 @@ export function parseLaboratorioTextoCompleto(
|
||||
if (!lineaLower) continue;
|
||||
|
||||
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 matchedClaveString = '';
|
||||
@@ -337,6 +336,7 @@ export function parseLaboratorioTextoCompleto(
|
||||
estado: calcularEstadoLaboratorioExtendido(core.nombre, String(valorFinal))
|
||||
});
|
||||
|
||||
matchedParamNames.add(core.nombre);
|
||||
processedLines.add(lIdx);
|
||||
break;
|
||||
}
|
||||
@@ -350,7 +350,7 @@ export function parseLaboratorioTextoCompleto(
|
||||
|
||||
for (const grupo of activeGroups) {
|
||||
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++) {
|
||||
const linea = lineas[lIdx];
|
||||
@@ -409,6 +409,7 @@ export function parseLaboratorioTextoCompleto(
|
||||
|
||||
reconocidos.push(recItem);
|
||||
processedLines.add(lIdx);
|
||||
matchedParamNames.add(det.nombre);
|
||||
|
||||
resultados.push({
|
||||
parametro: det.nombre,
|
||||
|
||||
+35
-46
@@ -53,55 +53,44 @@ export function isCamaFueraDeGrupo(
|
||||
}
|
||||
|
||||
export function sortCamas<T extends { numero: string }>(camas: T[]): T[] {
|
||||
const parseBedNumber = (numero: string) => {
|
||||
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)
|
||||
};
|
||||
if (!camas || camas.length <= 1) return camas || [];
|
||||
|
||||
const parseBedKey = (numero: string) => {
|
||||
const clean = (numero || '').trim();
|
||||
const match = clean.match(/^(\d{3})-(\d+)$/);
|
||||
if (!match) return { weight: 10, salaDir: 0, cama: 0 };
|
||||
|
||||
const sala = parseInt(match[1], 10);
|
||||
const cama = parseInt(match[2], 10);
|
||||
const grupo = Math.floor(sala / 100);
|
||||
const esPar = sala % 2 === 0;
|
||||
|
||||
let weight = 10;
|
||||
let isDesc = false;
|
||||
|
||||
if (grupo === 2) {
|
||||
if (!esPar) { weight = 1; isDesc = true; }
|
||||
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; }
|
||||
}
|
||||
|
||||
const salaDir = isDesc ? -sala : sala;
|
||||
return { weight, salaDir, cama };
|
||||
};
|
||||
|
||||
return [...camas].sort((a, b) => {
|
||||
const pa = parseBedNumber(a.numero);
|
||||
const pb = parseBedNumber(b.numero);
|
||||
|
||||
const getOrden = (sala: number) => {
|
||||
const grupo = Math.floor(sala / 100);
|
||||
const esPar = sala % 2 === 0;
|
||||
|
||||
if (grupo === 2) {
|
||||
if (!esPar) return { weight: 1, order: 'desc', sala }; // 2XX impares, mayor a menor
|
||||
return { weight: 2, order: 'asc', sala }; // 2XX pares, menor a mayor
|
||||
}
|
||||
if (grupo === 3) {
|
||||
if (esPar) return { weight: 3, order: 'asc', sala }; // 3XX pares, menor a mayor
|
||||
return { weight: 4, order: 'desc', sala }; // 3XX impares, mayor a menor
|
||||
}
|
||||
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 ob = getOrden(pb.sala);
|
||||
|
||||
if (oa.weight !== ob.weight) {
|
||||
return oa.weight - ob.weight;
|
||||
}
|
||||
|
||||
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;
|
||||
const mapped = camas.map(item => ({ item, key: parseBedKey(item.numero) }));
|
||||
mapped.sort((a, b) => {
|
||||
if (a.key.weight !== b.key.weight) return a.key.weight - b.key.weight;
|
||||
if (a.key.salaDir !== b.key.salaDir) return a.key.salaDir - b.key.salaDir;
|
||||
return a.key.cama - b.key.cama;
|
||||
});
|
||||
|
||||
return mapped.map(m => m.item);
|
||||
}
|
||||
|
||||
export function computeSector(numero: string): "En Área" | "Fuera de Área" {
|
||||
|
||||
Reference in New Issue
Block a user