0 Resumen Ejecutivo
Plataforma SaaS que automatiza la obtención, procesamiento y análisis de datos sísmicos para generar Seismic Source Models (SSM) e informes de Probabilistic Seismic Hazard Analysis (PSHA). Motor de cálculo PSHA propio en Python (integral Cornell-McGuire + librería GMPEs). Validado contra R-CRISIS y OpenQuake como benchmarks independientes. Nuestro valor: stack 100% cloud-native que transforma coordenadas en informes técnicos profesionales.
1 Decisiones Cerradas
Todas las decisiones estratégicas y técnicas, con justificación.
Motor de cálculo PSHA propio en Python. Integral Cornell-McGuire con librería GMPEs propia. R-CRISIS y OpenQuake como benchmarks de validación, no dependencias de producción.
Python 3.12 con FastAPI como framework web. Todo el pipeline científico en Python.
Next.js con App Router, TypeScript, Tailwind CSS. Mapbox GL JS para visualización geoespacial.
PostgreSQL 16 con extensión PostGIS para queries geoespaciales nativas.
Temporal.io para orquestación del pipeline multi-paso con retry y estado persistente.
Claude Sonnet para generación de informes narrativos, quality reports y resúmenes ejecutivos. Scikit-learn para anomaly detection en datos.
Railway para PoC y MVP. Migrar a AWS ECS Fargate cuando necesitemos workers dedicados para cálculos PSHA pesados (Tier 3). Todo Linux, todo containerizado.
Monorepo: /apps/api (FastAPI), /apps/web (Next.js), /packages/pipeline (core Python), /packages/shared (tipos compartidos).
2 Arquitectura del Sistema
6 capas, de presentación a datos externos. El pipeline de datos es el core — todo lo demás son interfaces.
3 Stack Tecnológico
| Componente | Tecnología | Versión | Propósito |
|---|---|---|---|
| Runtime | Python | 3.12 | Backend + Pipeline |
| Web Framework | FastAPI | 0.110+ | REST API async |
| Validation | Pydantic v2 | 2.6+ | Input/output validation, schemas |
| DB | PostgreSQL + PostGIS | 16 + 3.4 | Data + geospatial queries |
| ORM | SQLAlchemy + GeoAlchemy2 | 2.0+ | Async ORM con soporte geo |
| Migrations | Alembic | 1.13+ | Schema migrations |
| Cache | Redis | 7+ | Query cache + job queue |
| Orchestration | Temporal.io | Python SDK | Pipeline workflow orchestration |
| Frontend | Next.js | 15+ | Dashboard SPA + SSR |
| Maps | Mapbox GL JS | 3.x | Mapas interactivos geoespaciales |
| Charts | Plotly.js | 2.x | Gráficos científicos interactivos |
| WeasyPrint | 62+ | HTML → PDF profesional | |
| Geo libs | Shapely, GeoPandas, PyProj | latest | Operaciones geoespaciales en Python |
| Science | NumPy, SciPy, ObsPy | latest | Cálculos numéricos + sismología |
| ML | scikit-learn | 1.4+ | Anomaly detection, clustering |
| AI | Anthropic Claude API | Sonnet 4.5 | Report generation, QA narrativo |
| PSHA Engine | Custom (NumPy/SciPy) | — | Cornell-McGuire integral, GMPEs, UHS, disaggregation |
| Auth | Supabase Auth | latest | JWT + OAuth + Row Level Security |
| Storage | Cloudflare R2 | — | PDFs generados, exports |
4 Fuentes de Datos Externas
Todas las fuentes son públicas, gratuitas, accesibles vía API o descarga directa. Sin intermediarios humanos.
| Fuente | Datos | Acceso | Formato | Licencia |
|---|---|---|---|---|
| USGS FDSN | Catálogo sísmico global (~1890+). Magnitud, coords, profundidad, tipo. | REST API | GeoJSON, CSV | Público |
| GEM GAF-DB | Fallas activas mundiales. Geometría, dip, rake, slip rate, tipo cinemático. | GitHub raw | GeoJSON | CC-BY-SA 4.0 |
| ISC Bulletin | Catálogo revisado alta precisión. Complementa USGS pre-1970. | REST API | CSV, ISF | Público |
| USGS Vs30 | Velocidad onda corte 30m. Clasificación suelo del sitio. | Download | GeoTIFF | Público |
| GEM Exposure | Inventario edificaciones por país (tipo, ocupación, valor). | GitHub | CSV, XML | CC-BY |
| SHARE/ESHM | Modelo hazard europeo, fuentes sismogénicas Europa. | EFEHR portal | GeoJSON, XML | CC-BY |
USGS FDSN API — Query de Referencia
GET https://earthquake.usgs.gov/fdsnws/event/1/query
?format=geojson
&latitude=14.5995
&longitude=120.9842
&maxradiuskm=300
&minmagnitude=5
&starttime=1900-01-01
&orderby=magnitude
&limit=20000
GEM GAF-DB — Acceso Directo
https://raw.githubusercontent.com/GEMScienceTools/
gem-global-active-faults/master/geojson/
gem_active_faults.geojson
# Campos por feature:
# - geometry: LineString/MultiLineString (traza de falla)
# - properties.dip_dir, properties.dip
# - properties.rake, properties.slip_type
# - properties.slip_rate_min/max/pref
# - properties.net_slip_rate_min/max/pref
# - properties.fault_name, properties.catalog_id
5 Pipeline de Datos — Las 4 Fases
Input mínimo: (latitude, longitude, radius_km). Cada fase es un Activity de Temporal.io con retry independiente.
FASE 1 — EXTRACT (Multi-Source Fetcher)
Conecta con todas las fuentes simultáneamente y devuelve datos crudos normalizados.
class BaseFetcher(ABC):
@abstractmethod
async def fetch(self, params: SearchParams) -> RawDataset: ...
class USGSFetcher(BaseFetcher):
"""USGS FDSN API — catálogo sísmico"""
BASE_URL = "https://earthquake.usgs.gov/fdsnws/event/1/query"
async def fetch(self, params: SearchParams) -> EarthquakeCatalog:
query = {
"format": "geojson",
"latitude": params.latitude,
"longitude": params.longitude,
"maxradiuskm": params.radius_km,
"minmagnitude": params.min_magnitude, # default 5.0
"starttime": params.start_date, # default 1900-01-01
"orderby": "magnitude",
"limit": 20000,
}
async with httpx.AsyncClient() as client:
resp = await client.get(self.BASE_URL, params=query)
return self._parse_geojson(resp.json())
class GAFDBFetcher(BaseFetcher):
"""GEM Global Active Faults — geometría fallas"""
async def fetch(self, params: SearchParams) -> FaultCatalog:
# Spatial query sobre GeoJSON local (cacheado)
point = Point(params.longitude, params.latitude)
buffer = point.buffer(params.radius_km / 111.32) # deg approx
faults_gdf = gpd.read_file(self.LOCAL_GEOJSON_PATH)
nearby = faults_gdf[faults_gdf.intersects(buffer)]
return self._normalize(nearby)
class ISCFetcher(BaseFetcher):
"""ISC Bulletin — catálogo complementario pre-1970"""
...
class Vs30Fetcher(BaseFetcher):
"""USGS Vs30 raster — site classification"""
...
El extractor ejecuta todos los fetchers en paralelo con asyncio.gather(). Si un fetcher falla, no bloquea el resto — se marca como degradado en el quality report.
FASE 2 — ANALYZE (Procesador Inteligente)
Cada paso es una función pura que transforma datos. Orden estricto.
| # | Paso | Input | Output | Librería |
|---|---|---|---|---|
| 2.1 | Deduplicación | USGS + ISC catalogs | Catálogo unificado (prioriza magnitud revisada ISC) | Pandas |
| 2.2 | Homogeneización Mw | Catálogo con mb, Ms, Ml | Todas las magnitudes convertidas a Mw | ObsPy / relaciones empíricas |
| 2.3 | Declustering | Catálogo completo | Catálogo mainshock-only (sin réplicas/premonitorios) | Gardner-Knopoff (NumPy) |
| 2.4 | Completitud | Catálogo declustered | Mc (magnitud completitud) + períodos completos | Método Stepp (SciPy) |
| 2.5 | Separación Falla/BG | Catálogo + geometría fallas | Eventos asignados a falla (≤5km) vs background | Shapely + GeoPandas |
| 2.6 | Gutenberg-Richter | Eventos por zona | Parámetros a, b, λ, β con CI 95% | SciPy (least squares fit) |
@dataclass
class GRParameters:
a_value: float # Intersección
b_value: float # Pendiente (típico 0.7-1.3)
lambda_m0: float # Tasa anual M≥M0
beta: float # b * ln(10)
r_squared: float # Calidad del ajuste
n_events: int # Eventos usados
mc: float # Magnitud completitud
confidence_95: tuple # (b_lower, b_upper)
def fit_gutenberg_richter(
magnitudes: np.ndarray,
mc: float,
bin_width: float = 0.1
) -> GRParameters:
"""Ajuste G-R por mínimos cuadrados sobre distribución acumulada."""
mags = magnitudes[magnitudes >= mc]
bins = np.arange(mc, mags.max() + bin_width, bin_width)
cum_rates = np.array([np.sum(mags >= m) for m in bins])
# Log10(N) = a - b*M
log_rates = np.log10(cum_rates[cum_rates > 0])
slope, intercept, r, _, stderr = linregress(
bins[:len(log_rates)], log_rates
)
return GRParameters(
a_value=intercept, b_value=-slope,
lambda_m0=10**intercept, beta=-slope * np.log(10),
r_squared=r**2, n_events=len(mags), mc=mc,
confidence_95=(-slope - 1.96*stderr, -slope + 1.96*stderr)
)
FASE 3 — VALIDATE (Quality Assurance)
Tres niveles de validación. Cada check produce un score parcial que se agrega en quality score global.
| Nivel | Check | Criterio Pasa | Flag |
|---|---|---|---|
| Data Integrity | Coordenadas válidas | lat ∈ [-90,90], lon ∈ [-180,180] | 🟢/🔴 |
| Magnitudes coherentes | Mw ∈ [0, 10] | 🟢/🔴 | |
| Profundidades razonables | depth ∈ [0, 700] km | 🟢/🟡 | |
| Statistical | Valor b en rango | b ∈ [0.5, 1.8] | 🟢/🟡/🔴 |
| R² de G-R | R² ≥ 0.90 | 🟢/🟡 | |
| Eventos suficientes | N ≥ 30 (post Mc) | 🟢/🟡/🔴 | |
| Cross-reference | Coherencia con GEM Risk Profile | PGA dentro de ±30% del perfil país | 🟢/🟡 |
| Consistencia fallas vs sismicidad | Fallas activas con sismicidad asociada | 🟢/🟡 |
class QualityScore(BaseModel):
overall: Literal["HIGH", "MEDIUM", "LOW"] # 🟢🟡🔴
score: float # 0-100
checks: list[CheckResult]
flags: list[str] # Warnings para el usuario
ai_summary: str # Narrativa generada por Claude
@property
def emoji(self) -> str:
return {"HIGH": "🟢", "MEDIUM": "🟡", "LOW": "🔴"}[self.overall]
FASE 4 — TRANSFORM (Output Generator)
Convierte resultados validados a formatos de entrega según el Tier contratado.
| Output | Formato | Tier | Generador |
|---|---|---|---|
| Mapa interactivo | Mapbox GL (web) | 1, 2, 3 | Frontend render |
| Informe PDF básico | PDF (A4) | 1 | WeasyPrint |
| Informe PDF técnico | PDF (A4, 30-50 pp) | 2, 3 | WeasyPrint + Claude |
| PSHA Results Package | JSON + CSV | 3 | PSHA Engine output |
| NRML XML (OpenQuake) | .xml | 2, 3 | NRMLSerializer |
| GIS Package | GeoJSON + Shapefile | 2, 3 | GeoPandas export |
| Hazard Curves | CSV + Plot | 3 | PSHA Engine (propio) |
| UHS (Uniform Hazard Spectra) | CSV + Plot | 3 | PSHA Engine (propio) |
| Resumen ejecutivo AI | Markdown → PDF | 2, 3 | Claude API |
6 Modelo de Datos
PostgreSQL con PostGIS. Tablas principales del dominio sísmico + gestión de proyectos.
CREATE INDEX idx_earthquakes_location ON earthquakes USING GIST(location); — Permite ST_DWithin(location, center, radius) en <50ms para 100K+ eventos.
7 API REST
FastAPI con autenticación JWT (Supabase). Todos los endpoints retornan JSON. Paginación cursor-based.
Proyectos
{name, latitude, longitude, radius_km, tier, config?}Datos Sísmicos
?min_mag=&max_mag=&mainshock_only=true&assigned_fault=. Formato: GeoJSON FeatureCollection.Análisis & Reportes
{types: ["pdf_technical", "nrml", "hazard_curves"]}Quick Analysis (sin proyecto)
?lat=&lon=&radius=300. Retorna: conteo de fallas/terremotos, mapa preview, estimación de calidad. Para el landing page.8 Motor PSHA Propio
Implementación Python de la integral de Cornell-McGuire con librería de GMPEs, logic trees, disaggregación y UHS. Validado contra R-CRISIS (Carlos) + OpenQuake (container).
Sin dependencias Windows, sin CLI externo, sin formatos propietarios. El Tier 3 es simplemente más pasos del mismo pipeline Python.
8.1 — Ecuación Core (Cornell-McGuire)
"""
PSHA Hazard Calculator — Integral de Cornell-McGuire
λ(a) = Σ_sources ∫∫ P(A>a|m,r) × f(m) × f(r) dm dr
Donde:
λ(a) = tasa anual de excedencia del nivel de aceleración 'a'
P(A>a|m,r) = probabilidad de excedencia dada magnitud m y distancia r (GMPE)
f(m) = PDF de magnitudes (Gutenberg-Richter truncada)
f(r) = PDF de distancias (geometría fuente-sitio)
"""
@dataclass
class HazardCurveResult:
imls: np.ndarray # Intensity Measure Levels (g)
poes: np.ndarray # Probabilities of Exceedance (annual)
return_periods: np.ndarray # 1/poes (años)
source_contributions: dict # {source_name: poe_array}
class HazardCalculator:
def __init__(self, gmpe_registry: GMPERegistry):
self.gmpe_registry = gmpe_registry
def compute_hazard_curve(
self,
site: Site, # lat, lon, vs30
sources: list[SeismicSource], # faults + area sources
imls: np.ndarray, # acceleration levels to evaluate
gmpe_logic_tree: LogicTree, # weighted GMPE combinations
truncation: float = 3.0, # sigma truncation
integration_step_m: float = 0.1,
integration_step_r: float = 1.0, # km
) -> HazardCurveResult:
"""Calcula hazard curve para un sitio."""
total_poe = np.zeros(len(imls))
contributions = {}
for source in sources:
source_poe = np.zeros(len(imls))
mag_range = np.arange(source.mmin, source.mmax, integration_step_m)
dist_range = source.distance_distribution(site, step=integration_step_r)
for m in mag_range:
rate_m = source.occurrence_rate(m, integration_step_m) # G-R
for r, weight_r in dist_range:
# Weighted sum over GMPE logic tree
for gmpe, weight_gmpe in gmpe_logic_tree.branches:
mean_ln, sigma_ln = gmpe.get_mean_and_sigma(
m, r, site.vs30, source.tectonic_type
)
for iml_idx, iml in enumerate(imls):
ln_iml = np.log(iml)
# P(A > a | m, r) via complementary normal CDF
epsilon = (ln_iml - mean_ln) / sigma_ln
epsilon = min(epsilon, truncation)
p_exceed = 1 - norm.cdf(epsilon)
source_poe[iml_idx] += (
rate_m * weight_r * weight_gmpe * p_exceed
)
contributions[source.name] = source_poe
total_poe += source_poe
return HazardCurveResult(
imls=imls,
poes=1 - np.exp(-total_poe), # Poisson probability
return_periods=1 / total_poe,
source_contributions=contributions,
)
8.2 — Librería de GMPEs
Las 15 GMPEs que cubren >95% de los casos globales. Cada una es una función Python con coeficientes publicados en papers peer-reviewed.
| GMPE | Régimen | Región | Referencia | Est. |
|---|---|---|---|---|
| Boore et al. (2014) | Crustal active | Global | NGA-West2 | ~150 líneas |
| Abrahamson et al. (2014) | Crustal active | Global | NGA-West2 | ~200 líneas |
| Campbell & Bozorgnia (2014) | Crustal active | Global | NGA-West2 | ~200 líneas |
| Chiou & Youngs (2014) | Crustal active | Global | NGA-West2 | ~200 líneas |
| Zhao et al. (2006) | Subduction | Japón / global | BSSA | ~120 líneas |
| Atkinson & Boore (2003) | Subduction | Global | BSSA | ~100 líneas |
| Youngs et al. (1997) | Subduction | Global | SRL | ~80 líneas |
| Akkar & Bommer (2010) | Crustal active | Europa / Med. | SRL | ~100 líneas |
| Cauzzi et al. (2015) | Crustal active | Europa | BSSA | ~120 líneas |
| Atkinson & Boore (2006) | Stable continental | ENA | BSSA | ~100 líneas |
| García et al. (2005) | Subduction inslab | México / LATAM | BSSA | ~80 líneas |
| Kanno et al. (2006) | Crustal + subduction | Japón | BSSA | ~100 líneas |
| Total estimado | ~1,600 líneas | |||
class GMPE(ABC):
"""Interfaz base para Ground Motion Prediction Equations."""
name: str
tectonic_types: list[str] # ["crustal_active", "subduction_interface", ...]
supported_imts: list[str] # ["PGA", "SA(0.1)", "SA(0.2)", ...]
@abstractmethod
def get_mean_and_sigma(
self,
magnitude: float,
distance: float, # Rjb, Rrup, o Rhypo según GMPE
vs30: float,
imt: str = "PGA",
**kwargs, # Parámetros adicionales (Ztor, dip, etc.)
) -> tuple[float, float]: # (mean_ln_acceleration, sigma_ln)
"""Retorna media y desviación estándar en log-natural."""
...
class BooreEtAl2014(GMPE):
"""Boore, Stewart, Seyhan & Atkinson (2014) — NGA-West2"""
name = "BooreEtAl2014"
tectonic_types = ["crustal_active"]
# Coeficientes tabla publicada (período-dependiente)
COEFFS = {
"PGA": {"e0": 0.4473, "e1": 0.4856, "e2": 0.2459, ...},
"SA(0.1)": {"e0": 0.9445, "e1": 1.0145, "e2": 0.4328, ...},
...
}
def get_mean_and_sigma(self, magnitude, distance, vs30, imt="PGA", **kw):
c = self.COEFFS[imt]
# Implementación directa de ecuación del paper
f_mag = c["e0"] + c["e1"] * (magnitude - 4.5) + ...
f_dist = (c["c1"] + c["c2"] * (magnitude - 4.5)) * np.log(...)
f_site = c["c"] * np.log(vs30 / 760) + ...
mean_ln = f_mag + f_dist + f_site
sigma_ln = np.sqrt(c["tau"]**2 + c["phi"]**2)
return mean_ln, sigma_ln
8.3 — Componentes Adicionales Tier 3
| Componente | Descripción | Estimación |
|---|---|---|
| Logic Tree | Combinación ponderada de GMPEs + modelos de fuentes. Captura incertidumbre epistémica. | 2-3 días |
| UHS Calculator | Uniform Hazard Spectra: hazard curves para múltiples períodos → interpolar a probabilidad fija. | 1-2 días |
| Disaggregation | Descomposición contribución M-R-ε. Mismo cálculo pero guardando contribuciones parciales. | 1 semana |
| Hazard Maps | Grid de sitios → hazard curve por punto → interpolación espacial → raster/contornos. | 1 semana |
| Distance Calculator | Rjb, Rrup, Rhypo, Rx desde geometría 3D de fallas. Crítico para GMPEs. | 3-4 días |
8.4 — Estrategia de Validación
Tolerancia ≤5% en PGA y SA para 3 zonas piloto. Carlos genera ground truth con R-CRISIS. OpenQuake como tercer benchmark independiente en container Linux.
| Zona Piloto | Régimen | Validación cubre |
|---|---|---|
| Manila, Filipinas | Subducción activa | GMPEs subducción (Zhao, Atkinson-Boore), fallas thrust, catálogo denso |
| Lima, Perú | Subducción + crustal | Mix de fuentes, inslab events, GMPEs LATAM (García) |
| Madrid, España | Moderada / estable | GMPEs Europa (Akkar-Bommer, Cauzzi), catálogo escaso, background-dominated |
9 AI & LLM Integration
Usos de Claude API
| Función | Modelo | Input | Output | Tier |
|---|---|---|---|---|
| Quality Report | Sonnet | Validation checks + stats | Narrativa de confianza en lenguaje natural | 1, 2, 3 |
| Resumen Ejecutivo | Sonnet | SSM completo + G-R params | 1-2 páginas resumen para no-técnicos | 2, 3 |
| Contexto Tectónico | Sonnet | Coordenadas + datos extraídos | Descripción del régimen tectónico de la zona | 1, 2, 3 |
| Recomendaciones | Sonnet | Quality score + gaps | Acciones sugeridas si calidad baja | 2, 3 |
| Parámetro Advisor | Sonnet | Coordenadas + contexto | Sugerencia de radio, Mc, GMPEs por región | Interno |
QUALITY_REPORT_PROMPT = """
Eres un experto en sismología. Analiza los resultados de un Seismic Source Model
y genera un Quality Report en español profesional.
## Datos del Proyecto
- Ubicación: {location_name} ({lat}, {lon})
- Radio: {radius_km} km
- Terremotos encontrados: {n_earthquakes} (M≥{min_mag})
- Fallas activas: {n_faults}
- Mc (magnitud completitud): {mc}
## Resultados del Análisis
- Valor b (Gutenberg-Richter): {b_value} ± {b_uncertainty}
- R² del ajuste: {r_squared}
- Eventos post-declustering: {n_mainshocks}
- Eventos asignados a fallas: {n_fault_events} ({pct_fault}%)
- Eventos background: {n_bg_events} ({pct_bg}%)
## Validation Checks
{validation_results}
Genera:
1. Valoración general de la calidad (HIGH/MEDIUM/LOW con justificación)
2. Principales fortalezas de los datos
3. Gaps o limitaciones identificadas
4. Recomendaciones si la calidad es MEDIUM o LOW
Máximo 400 palabras. Tono técnico pero accesible.
"""
ML Clásico (sin LLM)
Scikit-learn para tareas que no requieren generación de texto:
| Tarea | Algoritmo | Propósito |
|---|---|---|
| Anomaly Detection | Isolation Forest | Detectar eventos con magnitud/profundidad inconsistente |
| Cluster Analysis | DBSCAN | Identificar swarms sísmicos (complementa declustering) |
| Tectonic Classification | Random Forest | Clasificar régimen tectónico por features geográficas |
10 Productos — Definición de Tiers
- Mapa fallas activas en radio
- Catálogo sísmico M≥5 filtrado
- Tabla propiedades por falla
- Histograma de magnitudes
- Mapa interactivo web
- Quality score automático
- Contexto tectónico AI
- PDF informe básico (8-12 pp)
Intervención humana: Ninguna
Coste marginal: ~€0
- Todo de Tier 1
- Declustering (Gardner-Knopoff)
- Homogeneización magnitudes → Mw
- Análisis completitud (Mc)
- Separación falla/background
- Regresión G-R con CI 95%
- Export NRML + PSHA-ready inputs
- Resumen ejecutivo AI
- PDF técnico (30-50 pp)
Review: QA opcional
Coste marginal: ~€50 (compute + Claude)
- Todo de Tier 2
- PSHA completo (motor propio Python)
- Hazard curves + hazard maps
- Uniform Hazard Spectra (UHS)
- Disaggregación M-R-ε
- Logic tree multi-GMPE
- Análisis de sensibilidad
- PML para aseguradoras
- Review técnico Carlos (validación)
Review: Carlos valida
Coste: ~$5 compute + Carlos
11 Infraestructura & Deploy
Fase 0-2 (PoC → MVP)
| Servicio | Proveedor | Plan | Coste/mes |
|---|---|---|---|
| Backend (FastAPI) | Railway | Pro | ~$20 |
| PostgreSQL + PostGIS | Railway (addon) | Managed | ~$15 |
| Redis | Railway (addon) | Managed | ~$10 |
| Frontend (Next.js) | Vercel | Pro | ~$20 |
| Temporal.io | Temporal Cloud | Free tier (25K actions) | $0 |
| Storage (PDFs) | Cloudflare R2 | Pay-per-use | ~$5 |
| Auth | Supabase | Free tier | $0 |
| AI | Anthropic | API pay-per-use | ~$30 |
| Maps | Mapbox | Free tier (50K loads) | $0 |
| Total Fase 0-2 | ~$100/mes | ||
Fase 3+ (Producción con motor PSHA propio)
| Servicio | Proveedor | Config | Coste/mes |
|---|---|---|---|
| PSHA Workers | AWS ECS Fargate | 1 vCPU, 2GB × auto-scale | ~$15 (estimado 40h/mes uso) |
| Backend API | AWS ECS Fargate | 0.5 vCPU, 1GB | ~$30 |
| RDS PostgreSQL | AWS RDS | db.t3.medium + PostGIS | ~$50 |
| Resto igual | — | — | ~$65 |
| Total Fase 3+ | ~$160/mes | ||
12 Roadmap de Implementación
(lat, lon, radius) → USGS API + GAF-DB parse. Mapa Folium con ambas capas. Zona piloto: Manila.
Entregable: Jupyter Notebook funcional. Demo para Carlos: "30 segundos vs horas".
Criterio de Éxito — PoC (Semana 3)
Input: (14.5995, 120.9842, 300) (Manila). En <60 segundos produce:
| # | Output | Validación |
|---|---|---|
| 1 | Mapa interactivo con fallas + terremotos M≥5 | Visual: datos correctos para zona |
| 2 | Tabla de fallas con dip, rake, slip rate | ≥5 fallas identificadas (Manila Trench, Philippine Fault...) |
| 3 | Catálogo sísmico filtrado y deduplicado | ≥200 eventos M≥5 en radio 300km |
| 4 | Histograma de magnitudes + timeline | Distribución G-R visible |
| 5 | Quality score automático | Carlos dice: "datos correctos para empezar" |
13 Riesgos & Mitigaciones
| Riesgo | Impacto | Prob. | Mitigación |
|---|---|---|---|
| Precisión del pipeline Declustering o separación falla/BG difiere de Carlos |
Alto | Media | Benchmark riguroso contra ground truth de Carlos. 3 zonas piloto (Manila, Lima, Madrid). Parámetros configurables por override manual. |
| Precisión GMPEs propias Implementación de coeficientes difiere de papers publicados |
Alto | Media | Validación unitaria GMPE por GMPE contra tablas publicadas en papers. Triple benchmark (nuestro motor vs R-CRISIS Carlos vs OpenQuake). Test suite con valores de referencia conocidos. Tolerancia ≤5%. |
| APIs externas cambian USGS cambia API, GEM reestructura repos |
Medio | Baja | Cache local de datos. Versionado de datasets. Fetchers con interfaz abstracta — swap sin cambiar pipeline. |
| Complejidad motor PSHA Tiempo de desarrollo o edge cases no previstos |
Medio | Media | Empezar con 5 GMPEs core (NGA-West2), expandir incrementalmente. OpenQuake como fallback temporal si el motor propio no está listo. Fase 3 tiene 8 semanas de buffer. |
| Calidad datos regionales Zonas con poco catálogo histórico |
Medio | Media | Quality scorer detecta automáticamente (N < 30 → flag). AI genera advertencia. Tier 1 sigue siendo útil incluso con datos limitados. |
| Disponibilidad Carlos Dependency en un solo domain expert |
Medio | Baja | Documentar toda su metodología en Fase 0. Calibrar pipeline para que Tier 1-2 funcionen sin intervención. Tier 3 requiere expert — buscar segundo advisor a medio plazo. |
→ Próximos Pasos Inmediatos
Esta semana (Appgile)
| # | Tarea | Owner | Horas est. |
|---|---|---|---|
| 1 | Setup monorepo (Turborepo + FastAPI + Next.js skeleton) | Dev | 4h |
| 2 | Implementar USGSFetcher — query API + parse GeoJSON | Dev | 6h |
| 3 | Implementar GAFDBFetcher — clone repo + spatial query | Dev | 6h |
| 4 | Mapa Folium: overlay fallas + terremotos para Manila | Dev | 4h |
| 5 | Jupyter Notebook PoC demo completo | Dev | 4h |
Próxima reunión con Carlos
| # | Agenda | Duración |
|---|---|---|
| 1 | Demo PoC Manila — validar que datos extraídos son correctos | 30 min |
| 2 | Sesión metodología: ¿cómo decide radio, Mc, GMPEs por región? | 2-3h |
| 3 | Benchmark: Carlos ejecuta Manila + Lima + Madrid en R-CRISIS → ground truth CSV | Tarea para Carlos |
| 4 | Validar lista de GMPEs priorizadas para motor propio | 30 min |
| 5 | NDA | 15 min |