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.

SaludPublicaService is the core epidemiological analytics engine of Salud IA Bot. It loads Colombia’s national SIVIGILA surveillance dataset from an XML file on startup, aggregates records by event name, and exposes a rich set of query methods that cover rankings, demographic breakdowns, urban/rural distribution, gender analysis, and thematic categories. Because all answers come directly from structured data, no LLM call is made when this service returns a result — eliminating hallucination risk for factual health statistics. The XML source file is:
data/Eventos_de_Interés_en_Salud_Pública_20260514.xml

RAG Bypass

SaludPublicaService implements a structured-data-first pattern. The main intent router procesarPregunta() attempts synonym resolution and partial-match lookup before returning. If a match is found, BotUpdate formats and sends the answer directly — GenkitService is never invoked. Only if encontrado: false is returned does the bot fall back to the LLM.

Text Normalization

All free-text inputs and event names are passed through normalizeText() before comparison:
private normalizeText(texto: string): string {
  return texto
    .toLowerCase()
    .normalize('NFD')
    .replace(/[\u0300-\u036f]/g, '') // strip accent marks
    .replace(/[^\w\s]/g, '')          // remove punctuation
    .replace(/\s+/g, ' ')
    .trim();
}
This means queries like "Dengué", "dengue", and "DENGUE" all resolve to the same token, and Spanish accents never cause lookup misses.

Data Model: HealthEvent

Each aggregated event record exposes the following fields:
FieldTypeDescription
nombre_del_eventostringOfficial SIVIGILA event name
total_de_eventosnumberSum of all reported cases
urbanonumberCases in urban areas
ruralnumberCases in rural areas
femeninonumberCases in female patients
masculinonumberCases in male patients
primera_infancianumberAge 0-4
infancianumberAge 5-9
adolescencianumberAge 10-14
juventudnumberAge 15-19
adulto_j_vennumberAge 20-49
adulto_mayornumberAge 50+
When multiple XML rows share the same event name, aggregateEvent() sums every numeric field so each event appears exactly once in the in-memory store.

Core Methods

procesarPregunta(texto: string)

The primary intent router. Normalizes the input, runs it through the synonym table (30+ entries), then falls back to buscarEventosAmbigua if no synonym matches. Returns a structured object that BotUpdate can render immediately.
public async procesarPregunta(
  texto: string,
): Promise<{ contenido?: string; evento?: any; encontrado: boolean }>
Synonym table sample:
const sinonimos: Record<string, string> = {
  dengue: 'DENGUE',
  'fiebre del dengue': 'DENGUE',
  varicela: 'VARICELA INDIVIDUAL',
  mordeduras: 'AGRESIONES POR ANIMALES POTENCIALMENTE TRANSMISORES DE RABIA',
  zika: 'ZIKA',
  ansiedad: 'ANSIEDAD',
  suicidio: 'INTENTO DE SUICIDIO',
  vih: 'VIH/SIDA - MORTALIDAD POR SIDA',
  tuberculosis: 'TUBERCULOSIS',
  chikungunya: 'CHIKUNGUYA',
  // ...30+ more entries
};
encontrado
boolean
true if a matching event was found in the SIVIGILA dataset. false triggers the LLM fallback in BotUpdate.
evento
HealthEvent | undefined
The full aggregated HealthEvent object when found.

buscarEventosAmbigua(nombre: string)

Partial-match search. Normalizes the query and returns all events whose normalized name contains the query string as a substring.
public async buscarEventosAmbigua(nombre: string): Promise<HealthEvent[]>
Example: "malaria" returns MALARIA, MALARIA POR P.FALCIPARUM, and MALARIA POR P.VIVAX if all are present in the dataset.
This method is called by both MlPredictionService and AdvancedPredictionService to locate the base event record before scoring or forecasting.

buscarPorSimilitud(query: string, threshold = 0.6)

Fuzzy search using Levenshtein edit distance. Every event name is compared against the query; results with a similarity score ≥ threshold are returned sorted by descending score.
public async buscarPorSimilitud(
  query: string,
  threshold = 0.6,
): Promise<HealthEvent[]>
Useful for typo tolerance — "dgngue" returns DENGUE with a high enough similarity score.

topEventos(n: number)

Returns the top n events sorted by total_de_eventos descending. Default: n = 5.
public async topEventos(n = 5): Promise<HealthEvent[]>
Example query: "¿Cuáles son las 5 enfermedades más comunes en Colombia?"

bottomEventos(n: number)

Returns the bottom n events sorted by total_de_eventos ascending (lowest incidence, including zeros). Default: n = 3.
public async bottomEventos(n = 3): Promise<HealthEvent[]>
Example query: "¿Qué enfermedades tienen menos casos?"

eventosPorRango(min: number, max: number)

Returns all events whose total case count falls within [min, max].
public async eventosPorRango(min: number, max: number): Promise<HealthEvent[]>
Example query: "Enfermedades con entre 500 y 2000 casos"

distribucionPorEdad(nombreEvento: string)

Returns a structured breakdown of cases across all six age groups for a named event.
public async distribucionPorEdad(
  nombreEvento: string,
): Promise<{ grupo: string; casos: number }[]>
Sample output:
[
  { "grupo": "Primera infancia (0-4)", "casos": 1234 },
  { "grupo": "Adulto joven (20-49)", "casos": 8901 }
]

compararEventos(eventoA: string, eventoB: string)

Direct comparison of two named events. Returns both objects, the name of the one with more cases, the absolute difference, and a human-readable mensaje.
public async compararEventos(
  eventoA: string,
  eventoB: string,
): Promise<{ eventoA, eventoB, mayor: string, diferencia: number, mensaje: string }>
Example query: "¿Qué es más común, dengue o zika?"

eventosPorCategoria(categoria: string)

Filters events by thematic category. Supported values:
Category keyIncluded events (sample)
infecciososDENGUE, ZIKA, CHIKUNGUYA, MALARIA, TUBERCULOSIS, VIH/SIDA, TOS FERINA
mentalANSIEDAD, DEPRESIÓN, PSICOSIS, TRASTORNO AFECTIVO BIPOLAR, CONSUMO DE SPA
maternoMORTALIDAD MATERNA, BAJO PESO AL NACER, DEFECTOS CONGENITOS, SIFILIS GESTACIONAL
violenciaVIGILANCIA EN SALUD PÚBLICA DE LAS VIOLENCIAS DE GÉNERO E INTRAFAMILIAR, LESIONES DE CAUSA EXTERNA

obtenerResumenGeneral()

Executive summary: total case count, total unique events, top 3 events, per-category totals, and count of events with zero reported cases.
public async obtenerResumenGeneral(): Promise<{
  totalCasos: number;
  totalEventos: number;
  topEventos: HealthEvent[];
  casosCategoria: { categoria: string; casos: number }[];
  eventosConCeroCasos: number;
}>

Additional Analytics Methods

MethodDescription
compararSexo(nombreEvento)Female/male split with percentages for a specific event
proporcionSexoGlobal()Global sex ratio across all events
eventosMayorBrechaSexo(n)Top N events with highest absolute gender gap
eventoMasRural()Event with highest percentage of rural cases
eventoMasUrbano()Event with highest percentage of urban cases
eventoPrincipalPorGrupoEtario(grupo)Top event for a given age group key
eventosSobreUmbral(umbral)Events exceeding a given case count threshold
eventosInfantiles()Events ranked by combined primera_infancia + infancia cases
eventosMasAfectanMujeres(n)Top N events by proportion of female cases
eventosMasAfectanHombres(n)Top N events by proportion of male cases
exportarACSV()Serializes the aggregated dataset to a CSV file under exports/

NLG Output Format

_formatearRespuesta() generates the human-readable text blocks shown in Telegram. The 'detalle' type produces:
--- DETALLE: DENGUE ---
👥 **Casos Totales:** 45321
📍 **Distribución Zona:** 38900 Urbano / 6421 Rural
👥 **Distribución Sexo:** 22100 Mujeres / 23221 Hombres
For richer urban/rural breakdowns with percentages and ratios, HealthStatsService.getDiseaseComparison() (a separate service) builds on the same raw data:
--- ANÁLISIS ESTADÍSTICO: DENGUE ---
Total de Casos: 45321
📍 Zona Urbana: 38900 casos (85.8%)
🏡 Zona Rural: 6421 casos (14.2%)
📈 Relación Urbano/Rural: 6.1 veces más casos en zonas urbanas.
Conclusión: La incidencia es predominantemente urbana.
To add a new synonym (e.g. mapping "gripa" to a SIVIGILA event), add an entry to the sinonimos map inside procesarPregunta(). No structural changes to the service are required.

Build docs developers (and LLMs) love