Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/RubenDarioGuerreroNeira/Ecosistema-IA-Colombia/llms.txt

Use this file to discover all available pages before exploring further.

Salud IA Bot’s predictive layer replaces external ML APIs with native TypeScript algorithms. Four services — MlPredictionService, AdvancedPredictionService, EarlyWarningService, and DatasetBuilderService — are orchestrated by PredictiveQuestionsService to answer risk classification, case projection, and outbreak alert queries without sending SIVIGILA microdata to third-party platforms.

Architecture

User query (riesgo / predicción / alerta)
  └─► BotUpdate
        └─► PredictiveQuestionsService (orchestrator)
              ├─► MlPredictionService     — composite risk scoring
              ├─► AdvancedPredictionService — time-series forecasting
              ├─► EarlyWarningService      — threshold alert detection
              └─► DatasetBuilderService    — data normalization pipeline

MlPredictionService

MlPredictionService implements a Composite Multi-Dimensional Scoring algorithm that ranks epidemic risk for any SIVIGILA event in any Colombian department. It combines four weighted dimensions into a 0–100 score and maps the result to a four-level risk classification.

Weights (PESOS)

DimensionWeightRationale
volumen40 %Case volume is the strongest single predictor of burden
brecha_vacunacion25 %Vaccination gaps directly increase outbreak probability
ruralidad20 %Rural concentration implies harder access and under-reporting
poblacion_vulnerable15 %Higher proportions of children and elderly raise severity
private readonly PESOS: PesosDimensiones = {
  volumen: 0.40,
  ruralidad: 0.20,
  brecha_vacunacion: 0.25,
  poblacion_vulnerable: 0.15,
};

Thresholds (UMBRALES)

LevelScore range
CRÍTICO>= 75
ALTO50 - 74
MEDIO25 - 49
BAJObelow 25
private readonly UMBRALES = {
  CRITICO: 75,
  ALTO: 50,
  MEDIO: 25,
};

TypeScript Interfaces

interface ClasificacionRiesgo {
  evento: string;
  departamento: string;
  nivel_riesgo: 'BAJO' | 'MEDIO' | 'ALTO' | 'CRÍTICO';
  puntaje_total: number;
  desglose_puntaje: DesgloseScore;
  factores_decisivos: string[];
  recomendacion_accion: string;
}

interface DesgloseScore {
  volumen: number;
  ruralidad: number;
  brecha_vacunacion: number;
  poblacion_vulnerable: number;
  total: number;
}

interface PesosDimensiones {
  volumen: number;
  ruralidad: number;
  brecha_vacunacion: number;
  poblacion_vulnerable: number;
}

Scoring Methods

calcularScoreVolumen(total, eventos)

Computes a position score (where the event falls in the sorted case-count distribution) and a logarithmic score (case magnitude), then blends them 60/40:
private calcularScoreVolumen(total: number, eventos: HealthEvent[]): number {
  const totales = eventos.map(e => e.total_de_eventos || 0).sort((a, b) => b - a);
  const maxTotal = totales[0] || 1;
  const minTotal = totales[eventos.length - 1] || 0;
  const rango = maxTotal - minTotal || 1;
  const scorePosicion = ((total - minTotal) / rango) * 100;
  const logTotal = Math.log10(total + 1);
  const logMax = Math.log10(maxTotal + 1);
  const scoreLog = logMax > 0 ? (logTotal / logMax) * 100 : 0;
  return Math.round(scorePosicion * 0.6 + scoreLog * 0.4);
}

calcularScoreRuralidad(evento)

Maps the rural percentage to a 0–100 score with five linear bands. Events where > 70 % of cases are rural score 100:
private calcularScoreRuralidad(evento: HealthEvent): number {
  const pctRural = (evento.rural || 0) / total;
  if (pctRural >= 0.7) return 100;
  if (pctRural >= 0.5) return 75 + ((pctRural - 0.5) / 0.2) * 25;
  if (pctRural >= 0.3) return 40 + ((pctRural - 0.3) / 0.2) * 35;
  if (pctRural >= 0.15) return 15 + ((pctRural - 0.15) / 0.15) * 25;
  return Math.round((pctRural / 0.15) * 15);
}

calcularScoreBrechaVacunacion(coberturaPromedio)

Converts average vaccination coverage (0–1) into a gap score. Coverage ≥ 95 % returns 0 risk; < 70 % returns the maximum gap score of 100:
if (coberturaPromedio >= 0.95) return 0;
if (coberturaPromedio >= 0.85) return Math.round((0.95 - coberturaPromedio) / 0.10 * 30);
if (coberturaPromedio >= 0.70) return 30 + Math.round((0.85 - coberturaPromedio) / 0.15 * 40);
return 70 + Math.round((0.70 - coberturaPromedio) / 0.70 * 30);

calcularScorePoblacionVulnerable(evento)

Sums primera_infancia + infancia + adulto_mayor as a proportion of total cases. Values ≥ 60 % score 100:
const poblacionVulnerable =
  (evento.primera_infancia || 0) +
  (evento.infancia || 0) +
  (evento.adulto_mayor || 0);
const pctVulnerable = poblacionVulnerable / total;
if (pctVulnerable >= 0.6) return 100;

Public Methods

MethodReturnsDescription
clasificarRiesgo(nombreEvento, departamento)Promise<ClasificacionRiesgo | null>Full risk classification for one event/department pair
obtenerAnalisisCompleto(departamento)Promise<string>Markdown table of risk scores for the top 8 events in a department
listarEventosDisponibles()Promise<string[]>First 20 SIVIGILA event names
listarUbicacionesDisponibles()Promise<string[]>Up to 12 unique locations from vaccination + air quality datasets

Recommendation messages

generarRecomendacion() returns action-level text based on risk tier:
LevelAction prefix
CRÍTICO🚨 ACTIVAR PROTOCOLO DE EMERGENCIA - movilizar COE
ALTO⚠️ ALERTA SANITARIA - intensificar vigilancia activa
MEDIO📋 REQUIERE ATENCIÓN - monitoreo semanal
BAJO🟢 BAJO RIESGO - vigilancia rutinaria

AdvancedPredictionService

AdvancedPredictionService projects future case counts using a Prophet-style simplified time-series decomposition: the historical series is split into trend + seasonal + residual components; a weighted linear regression over the trend component extrapolates future values. This is a custom TypeScript implementation — it does not use the Prophet library or any external ML dependency.

PredictionResult interface

interface PredictionResult {
  evento: string;
  departamento: string;
  valor_proyectado: number;
  intervalo_confianza_bajo: number;
  intervalo_confianza_alto: number;
  tendencia: 'creciente' | 'decreciente' | 'estable';
  estacionalidad_detectada: string[];
  factor_influencia: string;
  recomendacion: string;
}

Method: predecirEvento(nombreEvento, departamento, periodosFuturos = 3)

Pipeline steps:
  1. Locate the event via SaludPublicaService.buscarEventosAmbigua().
  2. Build a synthetic temporal series (buildTemporalSeries) from age-group distribution as a proxy for periodic case evolution.
  3. Decompose the series into trend, seasonality, and residual components (decomposeTimeSeries).
  4. Compute weighted linear regression over the decomposed series (calcularRegresionLinealPonderada).
  5. Detect seasonality cycle via detectarEstacionalidad.
  6. Project periodosFuturos values; use proyecciones[0] as the primary estimate.
  7. Compute 95% confidence interval: ±1.96 × σ(residuals).
  8. Classify trend direction relative to 5% of current total cases.
  9. Fetch vaccination and air quality factors for the narrative recommendation (obtenerFactoresInfluencia).
async predecirEvento(
  nombreEvento: string,
  departamento: string,
  periodosFuturos: number = 3,
): Promise<PredictionResult | null>

Method: obtenerPronosticosMultiples(departamento)

Returns a formatted Markdown block with forecasts for the top 5 SIVIGILA events, including projected cases, confidence interval, and recommendation for each.

EarlyWarningService

EarlyWarningService evaluates all SIVIGILA events against static thresholds and returns a sorted list of EarlyWarning objects, ordered from most to least severe.

EarlyWarning interface

interface EarlyWarning {
  nivel: '🟢 NORMAL' | '🟡 VIGILANCIA' | '🟠 ALERTA' | '🔴 EMERGENCIA';
  evento: string;
  departamento: string;
  casos: number;
  factor_detonante: string;
  recomendacion: string;
  tendencia: 'estable' | 'creciente' | 'decreciente';
  variacion_porcentual: number;
}

Alert thresholds (UMBRALES)

ThresholdValueDescription
casos_altos10 000Events with > 10 K cases enter vigilance automatically
cobertura_baja0.80Vaccination coverage < 80 % is flagged as a risk factor
cobertura_critica0.60Vaccination coverage < 60 % elevates alert level
incremento_mensual0.2020 % month-over-month growth triggers an alert

Alert level logic

private determinarNivelAlerta(numFactores, tendencia, totalCasos): EarlyWarning['nivel'] {
  if (totalCasos > 50000 || (numFactores >= 3 && tendencia === 'creciente'))
    return '🔴 EMERGENCIA';
  if (totalCasos > 20000 || (numFactores >= 2 && tendencia === 'creciente'))
    return '🟠 ALERTA';
  if (totalCasos > 10000 || numFactores >= 1)
    return '🟡 VIGILANCIA';
  return '🟢 NORMAL';
}

Methods

MethodReturnsDescription
evaluarAlertas()Promise<EarlyWarning[]>Full evaluation across all events, sorted by severity
obtenerResumenAlertas()Promise<string>Markdown-formatted alert summary for Telegram

Trend proxy

Because the SIVIGILA XML does not include time series, tendency is approximated from the age distribution: events where young cohorts (primera_infancia + infancia + adolescencia) represent > 60 % of cases are classified as 'creciente' (susceptible population signal); < 30 % → 'decreciente'.

DatasetBuilderService

DatasetBuilderService is the data normalization pipeline that prepares a unified, clean dataset for the ML models. It merges three source streams:
  1. SIVIGILA XML — event case counts and demographic breakdowns from SaludPublicaService
  2. PAI Vaccination SQLite — department-level coverage figures from VaccinationService
  3. Air quality API — municipal environmental readings from AirQualityService
The service implements a 24-hour in-memory cache to avoid redundant API calls across prediction requests. Normalized output vectors are compatible with MlPredictionService’s scoring inputs.

PredictiveQuestionsService

PredictiveQuestionsService acts as the orchestrator invoked by BotUpdate for all predictive intents:
Trigger phraseService calledMethod
"clasificar riesgo", "riesgo de [evento]"MlPredictionServiceclasificarRiesgo()
"predecir", "pronóstico", "proyección"AdvancedPredictionServicepredecirEvento()
"alertas tempranas", "alertas de salud"EarlyWarningServiceobtenerResumenAlertas()
"análisis completo"MlPredictionServiceobtenerAnalisisCompleto()
"pronósticos múltiples"AdvancedPredictionServiceobtenerPronosticosMultiples()
All four services are injected into PredictiveQuestionsService via NestJS dependency injection. Adding a new predictive capability requires only implementing the algorithm in one of the existing services and wiring it in the orchestrator’s intent router.

Build docs developers (and LLMs) love