chore: inject app.py to root for Streamlit Space
Browse files
app.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Interfaz visual interactiva para pathogen-forecast (streamlit + folium)."""
|
| 3 |
+
# Copyright (C) 2026 Pedro Sordo Martínez <amurlaniakea@gmail.com>
|
| 4 |
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
| 5 |
+
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
|
| 8 |
+
import folium
|
| 9 |
+
import streamlit as st
|
| 10 |
+
from streamlit_folium import st_folium
|
| 11 |
+
|
| 12 |
+
from data_loader import DataLoader
|
| 13 |
+
from epimodels import ModeloSEIR
|
| 14 |
+
from optimizer import OptimizadorSIR
|
| 15 |
+
|
| 16 |
+
st.set_page_config(page_title="pathogen-forecast", layout="wide")
|
| 17 |
+
st.title("pathogen-forecast — Predicción epidemiológica institucional")
|
| 18 |
+
st.markdown("**Autor:** Pedro Sordo Martínez <amurlaniakea@gmail.com> | **Licencia:** AGPL-3.0-or-later")
|
| 19 |
+
|
| 20 |
+
# Controles interactivos
|
| 21 |
+
col1, col2 = st.columns(2)
|
| 22 |
+
with col1:
|
| 23 |
+
r_contagiosidad = st.slider("R de contagiosidad (reproduction_rate aproximado)", 0.5, 10.0, 3.0, 0.1)
|
| 24 |
+
dias = st.slider("Días de simulación", 30, 730, 365, 30)
|
| 25 |
+
with col2:
|
| 26 |
+
pais = st.selectbox("País inicial (ejemplo del dataset)", ["Spain", "Afghanistan", "United States", "Global"])
|
| 27 |
+
poblacion = st.number_input("Población base (millones)", 1.0, 8000.0, 8.0, 1.0) * 1_000_000
|
| 28 |
+
|
| 29 |
+
import os
|
| 30 |
+
|
| 31 |
+
# Datos sintéticos por defecto (o dataset real si existe, vía env var)
|
| 32 |
+
ruta_env = os.getenv("COVID_DATASET_PATH", "")
|
| 33 |
+
ruta_dataset = Path(ruta_env) if ruta_env else Path(__file__).resolve().parent.parent / "tests" / "fixtures" / "synthetic_covid.csv"
|
| 34 |
+
if ruta_dataset.exists():
|
| 35 |
+
loader = DataLoader(ruta_dataset)
|
| 36 |
+
datos = loader.cargar_datos()
|
| 37 |
+
else:
|
| 38 |
+
loader = DataLoader(Path(__file__).resolve().parent.parent / "tests" / "fixtures" / "synthetic_covid.csv")
|
| 39 |
+
datos = loader.cargar_datos()
|
| 40 |
+
|
| 41 |
+
# Calibración (opcional, con datos sintéticos/reales)
|
| 42 |
+
if datos:
|
| 43 |
+
optimizador = OptimizadorSIR(datos)
|
| 44 |
+
try:
|
| 45 |
+
beta_cal, gamma_cal = optimizador.calibrar(N=poblacion)
|
| 46 |
+
st.success(f"Calibración real completada: beta={beta_cal:.3f}, gamma={gamma_cal:.3f}, R0={beta_cal/gamma_cal:.2f}")
|
| 47 |
+
except (ValueError, RuntimeError) as e:
|
| 48 |
+
beta_cal, gamma_cal = 0.3, 0.1
|
| 49 |
+
st.warning(f"Calibración con datos sintéticos de respaldo (error con datos reales: {e})")
|
| 50 |
+
else:
|
| 51 |
+
beta_cal, gamma_cal = 0.3, 0.1
|
| 52 |
+
st.info("Usando parámetros por defecto (sin datos disponibles).")
|
| 53 |
+
|
| 54 |
+
# Simulación SEIR (con latencia)
|
| 55 |
+
modelo = ModeloSEIR(beta=beta_cal, gamma=gamma_cal, sigma=0.2, N=poblacion)
|
| 56 |
+
t, y = modelo.simular(S0=poblacion - 100, E0=50, I0=100, R0=0, dias=dias)
|
| 57 |
+
|
| 58 |
+
# Métricas SEIR
|
| 59 |
+
S_final, E_final, I_final, R_final = y[:, -1]
|
| 60 |
+
col3, col4, col5, col6 = st.columns(4)
|
| 61 |
+
with col3:
|
| 62 |
+
st.metric("Susceptibles finales", f"{S_final:,.0f}")
|
| 63 |
+
with col4:
|
| 64 |
+
st.metric("Expuestos finales", f"{E_final:,.0f}")
|
| 65 |
+
with col5:
|
| 66 |
+
st.metric("Infectados finales", f"{I_final:,.0f}")
|
| 67 |
+
with col6:
|
| 68 |
+
st.metric("Recuperados finales", f"{R_final:,.0f}")
|
| 69 |
+
|
| 70 |
+
# Mapa interactivo (mapamundi básico con ubicación inicial)
|
| 71 |
+
m = folium.Map(location=[20.0, 0.0], zoom_start=2, tiles="cartodbpositron")
|
| 72 |
+
folium.CircleMarker(
|
| 73 |
+
location=[40.4168, -3.7038], # Ejemplo: Madrid (España)
|
| 74 |
+
radius=10,
|
| 75 |
+
popup=f"País inicial: {pais}\nR aproximado: {r_contagiosidad}\nSimulación: {dias} días",
|
| 76 |
+
color="red",
|
| 77 |
+
fill=True,
|
| 78 |
+
).add_to(m)
|
| 79 |
+
|
| 80 |
+
# Añadir puntos simulados de expansión (simplificado: puntos aleatorios alrededor del mundo)
|
| 81 |
+
import random
|
| 82 |
+
|
| 83 |
+
random.seed(42)
|
| 84 |
+
for _ in range(20):
|
| 85 |
+
lat = random.uniform(-60, 60)
|
| 86 |
+
lon = random.uniform(-180, 180)
|
| 87 |
+
folium.CircleMarker(
|
| 88 |
+
location=[lat, lon],
|
| 89 |
+
radius=3,
|
| 90 |
+
color="orange",
|
| 91 |
+
fill=True,
|
| 92 |
+
fill_opacity=0.6,
|
| 93 |
+
).add_to(m)
|
| 94 |
+
|
| 95 |
+
st.subheader("Mapa mundial interactivo de expansión simulada")
|
| 96 |
+
st_folium(m, width=1200, height=600)
|
| 97 |
+
|
| 98 |
+
# Nota de verificación
|
| 99 |
+
st.info("Nota: esta interfaz usa datos sintéticos por defecto o datos reales del dataset de Kaggle (`reproduction_rate` como referencia) para calibración. La expansión visual es una simulación teórica basada en el modelo SEIR básico.")
|