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)
| Dimension | Weight | Rationale |
|---|
volumen | 40 % | Case volume is the strongest single predictor of burden |
brecha_vacunacion | 25 % | Vaccination gaps directly increase outbreak probability |
ruralidad | 20 % | Rural concentration implies harder access and under-reporting |
poblacion_vulnerable | 15 % | 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)
| Level | Score range |
|---|
CRÍTICO | >= 75 |
ALTO | 50 - 74 |
MEDIO | 25 - 49 |
BAJO | below 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
| Method | Returns | Description |
|---|
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:
| Level | Action 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:
- Locate the event via
SaludPublicaService.buscarEventosAmbigua().
- Build a synthetic temporal series (
buildTemporalSeries) from age-group distribution as a proxy for periodic case evolution.
- Decompose the series into trend, seasonality, and residual components (
decomposeTimeSeries).
- Compute weighted linear regression over the decomposed series (
calcularRegresionLinealPonderada).
- Detect seasonality cycle via
detectarEstacionalidad.
- Project
periodosFuturos values; use proyecciones[0] as the primary estimate.
- Compute 95% confidence interval:
±1.96 × σ(residuals).
- Classify trend direction relative to 5% of current total cases.
- 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)
| Threshold | Value | Description |
|---|
casos_altos | 10 000 | Events with > 10 K cases enter vigilance automatically |
cobertura_baja | 0.80 | Vaccination coverage < 80 % is flagged as a risk factor |
cobertura_critica | 0.60 | Vaccination coverage < 60 % elevates alert level |
incremento_mensual | 0.20 | 20 % 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
| Method | Returns | Description |
|---|
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:
- SIVIGILA XML — event case counts and demographic breakdowns from
SaludPublicaService
- PAI Vaccination SQLite — department-level coverage figures from
VaccinationService
- 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 phrase | Service called | Method |
|---|
"clasificar riesgo", "riesgo de [evento]" | MlPredictionService | clasificarRiesgo() |
"predecir", "pronóstico", "proyección" | AdvancedPredictionService | predecirEvento() |
"alertas tempranas", "alertas de salud" | EarlyWarningService | obtenerResumenAlertas() |
"análisis completo" | MlPredictionService | obtenerAnalisisCompleto() |
"pronósticos múltiples" | AdvancedPredictionService | obtenerPronosticosMultiples() |
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.