Metodología
PoliScore busca ser un índice transparente y reproducible. Esta página describe cómo se construye el score y qué representa.
Qué es y qué no es
- No representa intención de voto.
- No recomienda ni descarta candidaturas.
- Sí agrega indicadores públicos (encuestas, eventos, cobertura, gestión) en una sola escala 0–100.
- Sí normaliza fuentes distintas y mantiene trazabilidad: cada variación se puede descomponer en factores.
Modelo del score
El score compuesto es una suma ponderada de cinco sub-escalas, cada una en 0–100:
score_total = 0.40 · poll_score
+ 0.15 · poll_trend_score
+ 0.25 · news_score
+ 0.10 · management_score
+ 0.10 · controversy_score (acotado a 0–100)
| Sub-escala | Peso | Cómo se calcula |
|---|---|---|
| poll_score | 40% | Media ponderada (recencia × muestra × credibilidad de la casa) de las encuestas de los últimos 45 días, cada una normalizada a 0–100. |
| poll_trend_score | 15% | Pendiente reciente de esas encuestas: media de la ventana corta (28 d) menos la anterior, combinada con una ventana más corta. 50 = plano. |
| news_score | 25% | 50 + aporte neto de eventos de tipo news/event, con decay exponencial (vida media 6 d). |
| management_score | 10% | 50 + aporte neto de eventos de gestión (decay más lento, vida media 20 d). |
| controversy_score | 10% | 50 + aporte neto de controversias (normalmente negativo; vida media 8 d). |
Como cada sub-escala ya está en 0–100, la suma ponderada cae en 0–100 por construcción; igualmente se aplica un clamp final.
Normalización de encuestas
No se suman porcentajes de escalas distintas. Cada medición se transforma primero a una escala común 0–100 (100 = mejor):
| Escala de origen | Transformación |
|---|---|
approval_pct (0–100) | directo |
net_favorability (−100…100) | (v + 100) / 2 |
scale_1_7 / scale_1_10 | reescalado lineal a 0–100 |
Cada encuesta pesa según su recencia (decay, vida media 18 d), su tamaño de muestra (∝ √n, con tope) y la credibilidad de la casa. Si no hay encuestas en la ventana, poll_score = 50 y la confianza cae.
De la noticia al score
Una regla central: 20 publicaciones de la misma noticia no son 20 eventos. La cadena es:
- Noticia: cada publicación individual de un medio.
- Evento: las noticias que cubren el mismo hecho se agrupan por
cluster_keyen un único evento. El número de fuentes se guarda como señal de relevancia, no como multiplicador de eventos. - Político → Impacto: el evento se asocia a uno o más políticos con un impacto firmado (positivo o negativo) en puntos de score.
- Magnitud: tamaño base del hecho (0–1), independiente de a quién afecta.
- Confianza: qué tan sólido es el impacto, según cantidad y credibilidad de las fuentes (0–1).
- Decay temporal: el impacto decae de forma exponencial. Vida media ≈ 6 días para noticias/eventos: a los ~6 días un evento pesa la mitad; a las dos semanas, cerca de un cuarto. Gestión decae en ~20 días y controversias en ~8.
El aporte vigente de un evento es exactamente:
effective_impact = impact · confidence · decay_factor(edad) decay_factor(edad) = 0.5 ^ (edad_en_días / vida_media)
source_count (número de notas agrupadas) no multiplica el impacto: 20 notas del mismo hecho aportan lo mismo que una. Solo influye en la confianza.
Clases de señal
Cada evento se clasifica en una de cinco clases de señal. El modelo mantiene esta distinción internamente para la próxima fase (ingesta real), aunque el algoritmo de ponderación todavía no cambia:
Cada movimiento almacena, además: impact (aporte firmado en t0), confidence (0–1), source_count (noticias agrupadas), occurred_at y el decay vigente. Es el contrato que consumirá el pipeline real.
Explicación de cada variación
La explicación sale directamente de los números del cálculo, no de un relato aparte. La variación entre dos fechas se descompone en los cinco componentes ponderados:
Δ = 0.40·Δpoll_score + 0.15·Δpoll_trend_score + 0.25·Δnews_score + 0.10·Δmanagement_score + 0.10·Δcontroversy_score La suma de los cinco deltas es igual a la variación del score.
Ejemplo real (variación 30 días de una figura del índice):
Variación 30D: +6.2 +3.7 Encuestas +2.2 Tendencia +0.3 Eventos / noticias +0.1 Gestión -0.1 Controversias ---------------------- Suma +6.2 Principal explicación: El avance se explica principalmente por encuestas y tendencia.
La frase solo nombra componentes cuyo delta supera un umbral: no se generan explicaciones que los números no respalden.
Confidence score
Cada político tiene un Confidence 0–100 que no mide si lo hace bien, sino cuántos datos recientes y confiables hay para calcular su score. Combina seis factores:
- cantidad de encuestas de los últimos 30 días;
- actualidad de la encuesta más reciente;
- diversidad de casas encuestadoras;
- volumen y confianza agregada de los eventos recientes (21 días);
- diversidad de medios que cubrieron esos eventos;
- confianza media por evento.
Un político con pocos datos igual tiene score, pero con confianza baja: sin encuestas recientes la confianza queda con techo de 35.
Trazabilidad y snapshots
Cada día se guarda un snapshot inmutable por político con: score_total, las cinco sub-escalas, confidence_score, la etiqueta de tendencia, y calculated_at / engine_version. Los snapshots pasados no se sobrescriben: recalcular el presente solo agrega días nuevos. Como el motor es determinista y "a fecha" (as-of), cualquier score histórico se puede reconstruir.
Intelligence: Momentum, Volatilidad y Poll Consensus
Tres métricas derivadas, deterministas y reproducibles: se calculan solo desde la
serie de snapshots y las encuestas. No hay explicación fuera de los números; cada una
expone sus componentes (/api/v1/intelligence).
Momentum score (−100 … +100)
Combina el movimiento reciente, el de mediano plazo y la aceleración, y lo pondera por la eficiencia de la trayectoria para distinguir un crecimiento sostenido de un salto puntual.
r7 = score(t) − score(t−7) [pts/semana] r30w = (score(t) − score(t−30)) · 7/30 [pts/semana equiv.] accel = r7 − ( score(t−7) − score(t−14) ) [cambio de la tasa semanal] blend = 0.45·r7 + 0.35·r30w + 0.20·accel efficiency = |score(t) − score(t−30)| / Σ|Δ diario en 30d| (0…1) sustain = 0.5 + 0.5·efficiency momentum = 100 · tanh( blend · sustain / 4.0 )
Una trayectoria limpia (efficiency ≈ 1) mantiene todo el peso; un movimiento
que sube y baja (efficiency baja) se amortigua hasta la mitad. Etiqueta:
«sostenido» si efficiency ≥ 0.55, si no «salto puntual».
Volatility score (0 … 100)
Cuánto oscila el PoliScore día a día en 30 / 60 / 90 días (desviación de los cambios diarios; un ascenso suave no cuenta como volátil).
σ_W = desviación estándar (poblacional) de los Δ diarios del score en W días vol_raw = (0.5·σ_30 + 0.3·σ_60 + 0.2·σ_90) / (Σ pesos con datos) volatility = 100 · ( 1 − exp( −vol_raw / 0.9 ) )
Poll consensus (0 … 100)
Grado de acuerdo entre encuestadoras. Alta dispersión = menor consenso.
Sobre las encuestas de los últimos 45 días, normalizadas a 0–100, con peso de recencia
0.5^(edad/21):
sd = desviación estándar ponderada de los valores normalizados poll_consensus = 100 · exp( −sd / 4.5 )
Requiere ≥ 2 encuestadoras distintas en la ventana; si no, queda «no disponible» y no entra al ranking.
Data Lab y exportaciones
El Data Lab consulta las series por político / período / métrica. Cada
exportación (CSV, XLSX, JSON, PYTHON) incluye generated_at, data_mode,
engine_version, período y campos utilizados.
- CSV — dataset largo (una fila por día y político); la cabecera va en líneas
#(compatibles conpandas.read_csv(comment='#')). - XLSX — hojas Data, Metadata y Methodology.
- JSON — estructura versionada (
format_version) conheader,meta,seriesysummary. - PYTHON — archivo
.pyautocontenido que conpandascarga los datos de la consulta (load_dataframe(),pivot(),to_csv(),to_xlsx()).
Endpoints equivalentes: /api/v1/data,
/api/v1/intelligence,
/api/v1/data/export?fmt=csv|xlsx|json|py.
Estado de esta versión
El motor de scoring está implementado y probado (suite en tests/). Los datos crudos son simulados: ~11 figuras, 120 días, ~270 encuestas y ~130 eventos generados de forma coherente. El pipeline datos crudos → motor → snapshots es el mismo que usará la ingesta real; reemplazar el generador (app/seed.py) por scraping + NLP + encuestas no obliga a tocar el motor ni las vistas. El endpoint /api/v1/ranking expone los datos en JSON.