Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/alphabeath_chilerisk.json
T
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
57 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "alphabeath/chilerisk", "files": {"docs/ARCHITECTURE.md": {"f935e34a521d0e8231a27c574fa0bd127f3eb6a1": {"t": 1785747217.8357275, "n": 5689, "h": "d010ebe7188effc3", "c": "# ChileRisk — Architecture\n\nVista de sistema. Detalle: [backend/docs/BACKEND.md](../backend/docs/BACKEND.md), [frontend/docs/FRONTEND.md](../frontend/docs/FRONTEND.md), [QUERY-DATE.md](./QUERY-DATE.md). Agentes: [../AGENTS.md](../AGENTS.md).\n\n---\n\n## System\n\n```\n┌─────────────┐ ┌──────────────────────┐ ┌─────────────────┐\n│ Frontend │ HTTP │ Backend (FastAPI) │ Async │ PostgreSQL │\n│ Next.js 16 │─────────▶│ + APScheduler │─────────▶│ 16 │\n└─────────────┘ └──────────┬───────────┘ └─────────────────┘\n │\n ┌───────────────┼───────────────┐\n ▼ ▼ ▼\n sismologia.cl Open-Meteo SERNAPRED AppSync Aire Chile (HTML) SERNAGEOMIN (HTML)\n (CSN scrape) (batch REST) (Cognito Identity + SigV4) alertas-volcanicas\n```\n\n---\n\n## Components\n\n| Layer | Tech | Purpose |\n|-------|------|---------|\n| Frontend | Next.js 16, React 19, MapLibre, @dnd-kit | Mapa, overlays, consulta por día |\n| Backend | FastAPI, SQLAlchemy 2.0 async | API, riesgo, integraciones, alertas unificadas |\n| Database | PostgreSQL 16 | Geo, scores live, snapshots diarios, eventos, SERNAPRED |\n| Scheduler | APScheduler | Risk refresh, CSN, meteo, SERNAPRED, Aire Chile GEC, SERNAGEOMIN |\n\n---\n\n## Data flow\n\n1. **Geografía** — seed idempotente: 16 regiones + 346 comunas (`regional.geojson`, `comunas.geojson`).\n2. **Sismos** — CSN (sismologia.cl) → `seismic_events`; impacto → `seismic_impacts` al insertar.\n3. **Clima** — Open-Meteo (lotes) → `climate_readings` + actualización `risk_scores`.\n4. **Riesgo live** — `risk_service.recompute_all_scores` cada N min (scheduler).\n5. **Riesgo histórico** — `daily_risk_service` materializa `daily_risk_scores` por `score_date` bajo demanda.\n6. **Alertas** — SERNAPRED sync → `senapred_alerts`; SERNAGEOMIN scrape → `sernageomin_volcanic_alerts`; evaluador → alertas ChileRisk; API unifica `/alerts/active`.\n7. **Aire Chile GEC** — scrape HTML → `airechile_daily`; API `/air-quality` (cobertura parcial PPDA).\n7. **Frontend** — `lib/api.ts` + React Query; fecha global en `ui-store` para mapa y listados.\n\nConsulta por día: [QUERY-DATE.md](./QUERY-DATE.md).\n\n---\n\n## Data sources\n\nAll data comes from real providers (CSN, Open-Meteo, SERNAPRED, Aire Chile, SERNAGEOMIN) when the corresponding `USE_REAL_*` flags are enabled. When disabled, the source contributes no data (no synthetic/mock fallbacks).\n\nVariables documented in [backend/docs/BACKEND.md](../backend/docs/BACKEND.md).\n\n---\n\n## Deployment\n\n`docker-compose.yml` en raíz:\n\n| Service | Port | Notas |\n|---------|------|-------|\n| frontend | 3000 | Healthcheck `127.0.0.1` (Alpine/IPv4) |\n| backend | 8000 (host `127.0.0.1`) | Healthcheck `127.0.0.1`; no público |\n| db | 5434 (host `127.0.0.1`) | `POSTGRES_*` vía env; no Internet |\n| adminer | 8080 (host `127.0.0.1`) | Solo profile `tools`; **no** en Dokploy |\n\n`make up` = `docker compose --profile tools up --build` (incluye Adminer). Frontend imagen: Next **standalone** + **bun**. Dockerfiles incluyen `HEALTHCHECK`. Plantilla env: [`.env.example`](../.env.example).\n\n### Dokploy (VPS) — `chilerisk.cl`\n\nApp tipo **Docker Compose** → este repo / `docker-compose.yml`:\n\n1. **DNS:** `A`/`AAAA` de `chilerisk.cl` (+ `www` si aplica) → IP del VPS.\n2. **Domains (Dokploy):** servicio **`frontend`**, puerto **3000**, HTTPS/Let’s Encrypt. El FE proxya la API (`/api/backend` → `backend:8000` en red Docker); no hace falta dominio público al backend.\n3. **No** actives profile `tools` (sin Adminer).\n4. **Environment** (pegar desde `.env.example` sección prod; mismo `AUTH_SECRET` FE+BE):\n - `AUTH_SECRET` ≥32 bytes (`openssl rand -base64 48`)\n - `AUTH_URL=https://chilerisk.cl`\n - `BACKEND_CORS_ORIGINS=[\"https://chilerisk.cl\",\"https://www.chilerisk.cl\"]`\n - `POSTGRES_PASSWORD` fuerte (y `POSTGRES_USER` / `POSTGRES_DB` si los cambias)\n - Opcional: `GOOGLE_*`, `RESEND_API_KEY`, `AUTH_EMAIL_FROM=noreply@chilerisk.cl`, `DEEPSEEK_API_KEY`\n5. **OAuth Google:** redirect `https://chilerisk.cl/api/auth/callback/google`.\n6. **Health:** IPv4 `127.0.0.1`; backend `start_period: 180s` (cold start con syncs; flood Open-Meteo va en background para no marcar unhealthy por 429).\n7. **Volumen** `pgdata` persistente; no publiques Postgres fuera del host.\n\n---\n\n## Monorepo structure\n\nPolyglot monorepo sin Turborepo:\n\n- **Ownership:** `AGENTS.md` por área; docs de stack en `backend/docs/` y `frontend/docs/`; cross-cutting en `docs/`.\n- **Docker:** contextos `./frontend` y `./backend` separados.\n- **Ignores:** `.gitignore` / `.dockerignore` por área; bun lock en frontend.\n- **Contrato:** Pydantic ↔ TypeScript manual ([DOC-MAINTENANCE.md](./DOC-MAINTENANCE.md)).\n- **Makefile:** atajos (`up`, `clean`, `dev-frontend`, `check-ignores`).\n\nNueva área (worker, etc.): mismo patrón + entrada en [README.md](./README.md) y ARCHITECTURE.\n\n---\n\n## Documentation map\n\n| Ubicación | Role |\n|-----------|------|\n| [docs/README.md](./README.md) | Índice monorepo (cross-cutting) |\n| [backend/docs/README.md](../backend/docs/README.md) | Índice backend |\n| [frontend/docs/README.md](../frontend/docs/README.md) | Índice frontend |\n| [HARNESS.md](./HARNESS.md) | Playbooks + `make verify` |\n| [DOC-MAINTENANCE.md](./DOC-MAINTENANCE.md) | Política de actualización |\n| [backend/docs/BACKEND.md](../backend/docs/BACKEND.md) | API, modelos, servicios |\n| [frontend/docs/FRONTEND.md](../frontend/docs/FRONTEND.md) | Componentes y hooks |\n| [backend/docs/ML-INTEGRATION.md](../backend/docs/ML-INTEGRATION.md) | ML futuro |\n\n---\n\n*Last updated: 2026-07-25*"}, "a3a61583a016977fbcdebd210bf08e0b9abfd73f": {"t": 1785771444.966782, "n": 5689, "h": "d010ebe7188effc3", "c": "# ChileRisk — Architecture\n\nVista de sistema. Detalle: [backend/docs/BACKEND.md](../backend/docs/BACKEND.md), [frontend/docs/FRONTEND.md](../frontend/docs/FRONTEND.md), [QUERY-DATE.md](./QUERY-DATE.md). Agentes: [../AGENTS.md](../AGENTS.md).\n\n---\n\n## System\n\n```\n┌─────────────┐ ┌──────────────────────┐ ┌─────────────────┐\n│ Frontend │ HTTP │ Backend (FastAPI) │ Async │ PostgreSQL │\n│ Next.js 16 │─────────▶│ + APScheduler │─────────▶│ 16 │\n└─────────────┘ └──────────┬───────────┘ └─────────────────┘\n │\n ┌───────────────┼───────────────┐\n ▼ ▼ ▼\n sismologia.cl Open-Meteo SERNAPRED AppSync Aire Chile (HTML) SERNAGEOMIN (HTML)\n (CSN scrape) (batch REST) (Cognito Identity + SigV4) alertas-volcanicas\n```\n\n---\n\n## Components\n\n| Layer | Tech | Purpose |\n|-------|------|---------|\n| Frontend | Next.js 16, React 19, MapLibre, @dnd-kit | Mapa, overlays, consulta por día |\n| Backend | FastAPI, SQLAlchemy 2.0 async | API, riesgo, integraciones, alertas unificadas |\n| Database | PostgreSQL 16 | Geo, scores live, snapshots diarios, eventos, SERNAPRED |\n| Scheduler | APScheduler | Risk refresh, CSN, meteo, SERNAPRED, Aire Chile GEC, SERNAGEOMIN |\n\n---\n\n## Data flow\n\n1. **Geografía** — seed idempotente: 16 regiones + 346 comunas (`regional.geojson`, `comunas.geojson`).\n2. **Sismos** — CSN (sismologia.cl) → `seismic_events`; impacto → `seismic_impacts` al insertar.\n3. **Clima** — Open-Meteo (lotes) → `climate_readings` + actualización `risk_scores`.\n4. **Riesgo live** — `risk_service.recompute_all_scores` cada N min (scheduler).\n5. **Riesgo histórico** — `daily_risk_service` materializa `daily_risk_scores` por `score_date` bajo demanda.\n6. **Alertas** — SERNAPRED sync → `senapred_alerts`; SERNAGEOMIN scrape → `sernageomin_volcanic_alerts`; evaluador → alertas ChileRisk; API unifica `/alerts/active`.\n7. **Aire Chile GEC** — scrape HTML → `airechile_daily`; API `/air-quality` (cobertura parcial PPDA).\n7. **Frontend** — `lib/api.ts` + React Query; fecha global en `ui-store` para mapa y listados.\n\nConsulta por día: [QUERY-DATE.md](./QUERY-DATE.md).\n\n---\n\n## Data sources\n\nAll data comes from real providers (CSN, Open-Meteo, SERNAPRED, Aire Chile, SERNAGEOMIN) when the corresponding `USE_REAL_*` flags are enabled. When disabled, the source contributes no data (no synthetic/mock fallbacks).\n\nVariables documented in [backend/docs/BACKEND.md](../backend/docs/BACKEND.md).\n\n---\n\n## Deployment\n\n`docker-compose.yml` en raíz:\n\n| Service | Port | Notas |\n|---------|------|-------|\n| frontend | 3000 | Healthcheck `127.0.0.1` (Alpine/IPv4) |\n| backend | 8000 (host `127.0.0.1`) | Healthcheck `127.0.0.1`; no público |\n| db | 5434 (host `127.0.0.1`) | `POSTGRES_*` vía env; no Internet |\n| adminer | 8080 (host `127.0.0.1`) | Solo profile `tools`; **no** en Dokploy |\n\n`make up` = `docker compose --profile tools up --build` (incluye Adminer). Frontend imagen: Next **standalone** + **bun**. Dockerfiles incluyen `HEALTHCHECK`. Plantilla env: [`.env.example`](../.env.example).\n\n### Dokploy (VPS) — `chilerisk.cl`\n\nApp tipo **Docker Compose** → este repo / `docker-compose.yml`:\n\n1. **DNS:** `A`/`AAAA` de `chilerisk.cl` (+ `www` si aplica) → IP del VPS.\n2. **Domains (Dokploy):** servicio **`frontend`**, puerto **3000**, HTTPS/Let’s Encrypt. El FE proxya la API (`/api/backend` → `backend:8000` en red Docker); no hace falta dominio público al backend.\n3. **No** actives profile `tools` (sin Adminer).\n4. **Environment** (pegar desde `.env.example` sección prod; mismo `AUTH_SECRET` FE+BE):\n - `AUTH_SECRET` ≥32 bytes (`openssl rand -base64 48`)\n - `AUTH_URL=https://chilerisk.cl`\n - `BACKEND_CORS_ORIGINS=[\"https://chilerisk.cl\",\"https://www.chilerisk.cl\"]`\n - `POSTGRES_PASSWORD` fuerte (y `POSTGRES_USER` / `POSTGRES_DB` si los cambias)\n - Opcional: `GOOGLE_*`, `RESEND_API_KEY`, `AUTH_EMAIL_FROM=noreply@chilerisk.cl`, `DEEPSEEK_API_KEY`\n5. **OAuth Google:** redirect `https://chilerisk.cl/api/auth/callback/google`.\n6. **Health:** IPv4 `127.0.0.1`; backend `start_period: 180s` (cold start con syncs; flood Open-Meteo va en background para no marcar unhealthy por 429).\n7. **Volumen** `pgdata` persistente; no publiques Postgres fuera del host.\n\n---\n\n## Monorepo structure\n\nPolyglot monorepo sin Turborepo:\n\n- **Ownership:** `AGENTS.md` por área; docs de stack en `backend/docs/` y `frontend/docs/`; cross-cutting en `docs/`.\n- **Docker:** contextos `./frontend` y `./backend` separados.\n- **Ignores:** `.gitignore` / `.dockerignore` por área; bun lock en frontend.\n- **Contrato:** Pydantic ↔ TypeScript manual ([DOC-MAINTENANCE.md](./DOC-MAINTENANCE.md)).\n- **Makefile:** atajos (`up`, `clean`, `dev-frontend`, `check-ignores`).\n\nNueva área (worker, etc.): mismo patrón + entrada en [README.md](./README.md) y ARCHITECTURE.\n\n---\n\n## Documentation map\n\n| Ubicación | Role |\n|-----------|------|\n| [docs/README.md](./README.md) | Índice monorepo (cross-cutting) |\n| [backend/docs/README.md](../backend/docs/README.md) | Índice backend |\n| [frontend/docs/README.md](../frontend/docs/README.md) | Índice frontend |\n| [HARNESS.md](./HARNESS.md) | Playbooks + `make verify` |\n| [DOC-MAINTENANCE.md](./DOC-MAINTENANCE.md) | Política de actualización |\n| [backend/docs/BACKEND.md](../backend/docs/BACKEND.md) | API, modelos, servicios |\n| [frontend/docs/FRONTEND.md](../frontend/docs/FRONTEND.md) | Componentes y hooks |\n| [backend/docs/ML-INTEGRATION.md](../backend/docs/ML-INTEGRATION.md) | ML futuro |\n\n---\n\n*Last updated: 2026-07-25*"}, "6a8393eec72c4e8165a5785d5562bc60413d3b72": {"t": 1785771446.457405, "n": 5621, "h": "89dff8e9f9a4c481", "c": "# ChileRisk — Architecture\n\nVista de sistema. Detalle: [backend/docs/BACKEND.md](../backend/docs/BACKEND.md), [frontend/docs/FRONTEND.md](../frontend/docs/FRONTEND.md), [QUERY-DATE.md](./QUERY-DATE.md). Agentes: [../AGENTS.md](../AGENTS.md).\n\n---\n\n## System\n\n```\n┌─────────────┐ ┌──────────────────────┐ ┌─────────────────┐\n│ Frontend │ HTTP │ Backend (FastAPI) │ Async │ PostgreSQL │\n│ Next.js 16 │─────────▶│ + APScheduler │─────────▶│ 16 │\n└─────────────┘ └──────────┬───────────┘ └─────────────────┘\n │\n ┌───────────────┼───────────────┐\n ▼ ▼ ▼\n sismologia.cl Open-Meteo SERNAPRED AppSync Aire Chile (HTML) SERNAGEOMIN (HTML)\n (CSN scrape) (batch REST) (Cognito Identity + SigV4) alertas-volcanicas\n```\n\n---\n\n## Components\n\n| Layer | Tech | Purpose |\n|-------|------|---------|\n| Frontend | Next.js 16, React 19, MapLibre, @dnd-kit | Mapa, overlays, consulta por día |\n| Backend | FastAPI, SQLAlchemy 2.0 async | API, riesgo, integraciones, alertas unificadas |\n| Database | PostgreSQL 16 | Geo, scores live, snapshots diarios, eventos, SERNAPRED |\n| Scheduler | APScheduler | Risk refresh, CSN, meteo, SERNAPRED, Aire Chile GEC, SERNAGEOMIN |\n\n---\n\n## Data flow\n\n1. **Geografía** — seed idempotente: 16 regiones + 346 comunas (`regional.geojson`, `comunas.geojson`).\n2. **Sismos** — CSN (sismologia.cl) → `seismic_events`; impacto → `seismic_impacts` al insertar.\n3. **Clima** — Open-Meteo (lotes) → `climate_readings` + actualización `risk_scores`.\n4. **Riesgo live** — `risk_service.recompute_all_scores` cada N min (scheduler).\n5. **Riesgo histórico** — `daily_risk_service` materializa `daily_risk_scores` por `score_date` bajo demanda.\n6. **Alertas** — SERNAPRED sync → `senapred_alerts`; SERNAGEOMIN scrape → `sernageomin_volcanic_alerts`; evaluador → alertas ChileRisk; API unifica `/alerts/active`.\n7. **Aire Chile GEC** — scrape HTML → `airechile_daily`; API `/air-quality` (cobertura parcial PPDA).\n7. **Frontend** — `lib/api.ts` + React Query; fecha global en `ui-store` para mapa y listados.\n\nConsulta por día: [QUERY-DATE.md](./QUERY-DATE.md).\n\n---\n\n## Data sources\n\nAll data comes from real providers (CSN, Open-Meteo, SERNAPRED, Aire Chile, SERNAGEOMIN) when the corresponding `USE_REAL_*` flags are enabled. When disabled, the source contributes no data (no synthetic/mock fallbacks).\n\nVariables documented in [backend/docs/BACKEND.md](../backend/docs/BACKEND.md).\n\n---\n\n## Deployment\n\n`docker-compose.yml` en raíz:\n\n| Service | Port | Notas |\n|---------|------|-------|\n| frontend | 3000 | Healthcheck `127.0.0.1` (Alpine/IPv4) |\n| backend | 8000 (host `127.0.0.1`) | Healthcheck `127.0.0.1`; no público |\n| db | 5434 (host `127.0.0.1`) | `POSTGRES_*` vía env; no Internet |\n| adminer | 8080 (host `127.0.0.1`) | Solo profile `tools`; **no** en Dokploy |\n\n`make up` = `docker compose --profile tools up --build` (incluye Adminer). Frontend imagen: Next **standalone** + **bun**. Dockerfiles incluyen `HEALTHCHECK`. Plantilla env: [`.env.example`](../.env.example).\n\n### Dokploy (VPS) — `chilerisk.cl`\n\nApp tipo **Docker Compose** → este repo / `docker-compose.yml`:\n\n1. **DNS:** `A`/`AAAA` de `chilerisk.cl` (+ `www` si aplica) → IP del VPS.\n2. **Domains (Dokploy):** servicio **`frontend`**, puerto **3000**, HTTPS/Let’s Encrypt. El FE proxya la API (`/api/backend` → `backend:8000` en red Docker); no hace falta dominio público al backend.\n3. **No** actives profile `tools` (sin Adminer).\n4. **Environment** (pegar desde `.env.example` sección prod; mismo `AUTH_SECRET` FE+BE):\n - `AUTH_SECRET` ≥32 bytes (`openssl rand -base64 48`)\n - `AUTH_URL=https://chilerisk.cl`\n - `BACKEND_CORS_ORIGINS=[\"https://chilerisk.cl\",\"https://www.chilerisk.cl\"]`\n - `POSTGRES_PASSWORD` fuerte (y `POSTGRES_USER` / `POSTGRES_DB` si los cambias)\n - Opcional: `GOOGLE_*`, `RESEND_API_KEY`, `AUTH_EMAIL_FROM=noreply@chilerisk.cl`, `DEEPSEEK_API_KEY`\n5. **OAuth Google:** redirect `https://chilerisk.cl/api/auth/callback/google`.\n6. **Health:** IPv4 `127.0.0.1`; backend `start_period: 180s` (cold start con syncs).\n7. **Volumen** `pgdata` persistente; no publiques Postgres fuera del host.\n\n---\n\n## Monorepo structure\n\nPolyglot monorepo sin Turborepo:\n\n- **Ownership:** `AGENTS.md` por área; docs de stack en `backend/docs/` y `frontend/docs/`; cross-cutting en `docs/`.\n- **Docker:** contextos `./frontend` y `./backend` separados.\n- **Ignores:** `.gitignore` / `.dockerignore` por área; bun lock en frontend.\n- **Contrato:** Pydantic ↔ TypeScript manual ([DOC-MAINTENANCE.md](./DOC-MAINTENANCE.md)).\n- **Makefile:** atajos (`up`, `clean`, `dev-frontend`, `check-ignores`).\n\nNueva área (worker, etc.): mismo patrón + entrada en [README.md](./README.md) y ARCHITECTURE.\n\n---\n\n## Documentation map\n\n| Ubicación | Role |\n|-----------|------|\n| [docs/README.md](./README.md) | Índice monorepo (cross-cutting) |\n| [backend/docs/README.md](../backend/docs/README.md) | Índice backend |\n| [frontend/docs/README.md](../frontend/docs/README.md) | Índice frontend |\n| [HARNESS.md](./HARNESS.md) | Playbooks + `make verify` |\n| [DOC-MAINTENANCE.md](./DOC-MAINTENANCE.md) | Política de actualización |\n| [backend/docs/BACKEND.md](../backend/docs/BACKEND.md) | API, modelos, servicios |\n| [frontend/docs/FRONTEND.md](../frontend/docs/FRONTEND.md) | Componentes y hooks |\n| [backend/docs/ML-INTEGRATION.md](../backend/docs/ML-INTEGRATION.md) | ML futuro |\n\n---\n\n*Last updated: 2026-07-25*"}, "7fc01550d038015258f24a870c3a32b1a24e343a": {"t": 1785771447.7186701, "n": 5414, "h": "77c533d8702eb524", "c": "# ChileRisk — Architecture\n\nVista de sistema. Detalle: [backend/docs/BACKEND.md](../backend/docs/BACKEND.md), [frontend/docs/FRONTEND.md](../frontend/docs/FRONTEND.md), [QUERY-DATE.md](./QUERY-DATE.md). Agentes: [../AGENTS.md](../AGENTS.md).\n\n---\n\n## System\n\n```\n┌─────────────┐ ┌──────────────────────┐ ┌─────────────────┐\n│ Frontend │ HTTP │ Backend (FastAPI) │ Async │ PostgreSQL │\n│ Next.js 16 │─────────▶│ + APScheduler │─────────▶│ 16 │\n└─────────────┘ └──────────┬───────────┘ └─────────────────┘\n │\n ┌───────────────┼───────────────┐\n ▼ ▼ ▼\n sismologia.cl Open-Meteo SERNAPRED AppSync Aire Chile (HTML) SERNAGEOMIN (HTML)\n (CSN scrape) (batch REST) (Cognito Identity + SigV4) alertas-volcanicas\n```\n\n---\n\n## Components\n\n| Layer | Tech | Purpose |\n|-------|------|---------|\n| Frontend | Next.js 16, React 19, MapLibre, @dnd-kit | Mapa, overlays, consulta por día |\n| Backend | FastAPI, SQLAlchemy 2.0 async | API, riesgo, integraciones, alertas unificadas |\n| Database | PostgreSQL 16 | Geo, scores live, snapshots diarios, eventos, SERNAPRED |\n| Scheduler | APScheduler | Risk refresh, CSN, meteo, SERNAPRED, Aire Chile GEC, SERNAGEOMIN |\n\n---\n\n## Data flow\n\n1. **Geografía** — seed idempotente: 16 regiones + 346 comunas (`regional.geojson`, `comunas.geojson`).\n2. **Sismos** — CSN (sismologia.cl) → `seismic_events`; impacto → `seismic_impacts` al insertar.\n3. **Clima** — Open-Meteo (lotes) → `climate_readings` + actualización `risk_scores`.\n4. **Riesgo live** — `risk_service.recompute_all_scores` cada N min (scheduler).\n5. **Riesgo histórico** — `daily_risk_service` materializa `daily_risk_scores` por `score_date` bajo demanda.\n6. **Alertas** — SERNAPRED sync → `senapred_alerts`; SERNAGEOMIN scrape → `sernageomin_volcanic_alerts`; evaluador → alertas ChileRisk; API unifica `/alerts/active`.\n7. **Aire Chile GEC** — scrape HTML → `airechile_daily`; API `/air-quality` (cobertura parcial PPDA).\n7. **Frontend** — `lib/api.ts` + React Query; fecha global en `ui-store` para mapa y listados.\n\nConsulta por día: [QUERY-DATE.md](./QUERY-DATE.md).\n\n---\n\n## Data sources\n\nAll data comes from real providers (CSN, Open-Meteo, SERNAPRED, Aire Chile, SERNAGEOMIN) when the corresponding `USE_REAL_*` flags are enabled. When disabled, the source contributes no data (no synthetic/mock fallbacks).\n\nVariables documented in [backend/docs/BACKEND.md](../backend/docs/BACKEND.md).\n\n---\n\n## Deployment\n\n`docker-compose.yml` en raíz:\n\n| Service | Port | Notas |\n|---------|------|-------|\n| frontend | 3000 | Healthcheck `127.0.0.1` (Alpine/IPv4) |\n| backend | 8000 | Healthcheck `127.0.0.1` |\n| db | 5434 (host `127.0.0.1` only) | No exponer a Internet |\n| adminer | 8080 | Solo profile `tools` (local); **no** en Dokploy/prod |\n\n`make up` = `docker compose --profile tools up --build` (incluye Adminer). Frontend imagen: Next **standalone** + **bun**. Dockerfiles incluyen `HEALTHCHECK` (deploy Dockerfile-only en Dokploy).\n\n### Dokploy (VPS)\n\nApp tipo **Docker Compose** apuntando a este repo/`docker-compose.yml`:\n\n1. **No** actives profile `tools` → Adminer no arranca ni abre el puerto 8080.\n2. Env obligatorias (mismo valor FE+BE donde aplique):\n - `AUTH_SECRET` ≥32 bytes (`openssl rand -base64 48`)\n - `AUTH_URL` = URL pública del frontend (https://…)\n - `BACKEND_CORS_ORIGINS` = JSON con el origen del frontend (ej. `[\"https://tudominio.cl\"]`)\n - OAuth/email si aplica: `GOOGLE_*`, `RESEND_API_KEY`, `AUTH_EMAIL_FROM`\n3. Dominios Traefik/Dokploy: frontend → servicio `frontend:3000`; API pública solo si la necesitas en `backend:8000` (el FE ya habla con `backend` por red interna via `BACKEND_INTERNAL_URL`).\n4. Health: probes usan IPv4 (`127.0.0.1`). Backend `start_period: 180s` — el cold start sincroniza fuentes antes de escuchar. Si el FE queda `unhealthy` con app OK, revisa `localhost`→`::1`.\n5. Postgres: volumen `pgdata`; no publiques `5432`/`5434` fuera del host.\n\n---\n\n## Monorepo structure\n\nPolyglot monorepo sin Turborepo:\n\n- **Ownership:** `AGENTS.md` por área; docs de stack en `backend/docs/` y `frontend/docs/`; cross-cutting en `docs/`.\n- **Docker:** contextos `./frontend` y `./backend` separados.\n- **Ignores:** `.gitignore` / `.dockerignore` por área; bun lock en frontend.\n- **Contrato:** Pydantic ↔ TypeScript manual ([DOC-MAINTENANCE.md](./DOC-MAINTENANCE.md)).\n- **Makefile:** atajos (`up`, `clean`, `dev-frontend`, `check-ignores`).\n\nNueva área (worker, etc.): mismo patrón + entrada en [README.md](./README.md) y ARCHITECTURE.\n\n---\n\n## Documentation map\n\n| Ubicación | Role |\n|-----------|------|\n| [docs/README.md](./README.md) | Índice monorepo (cross-cutting) |\n| [backend/docs/README.md](../backend/docs/README.md) | Índice backend |\n| [frontend/docs/README.md](../frontend/docs/README.md) | Índice frontend |\n| [HARNESS.md](./HARNESS.md) | Playbooks + `make verify` |\n| [DOC-MAINTENANCE.md](./DOC-MAINTENANCE.md) | Política de actualización |\n| [backend/docs/BACKEND.md](../backend/docs/BACKEND.md) | API, modelos, servicios |\n| [frontend/docs/FRONTEND.md](../frontend/docs/FRONTEND.md) | Componentes y hooks |\n| [backend/docs/ML-INTEGRATION.md](../backend/docs/ML-INTEGRATION.md) | ML futuro |\n\n---\n\n*Last updated: 2026-07-25*"}, "ad46309135dcbd4336290967d5a158a1c5192dbe": {"t": 1785771449.9359922, "n": 4147, "h": "98fc54ac3129d748", "c": "# ChileRisk — Architecture\n\nVista de sistema. Detalle: [backend/docs/BACKEND.md](../backend/docs/BACKEND.md), [frontend/docs/FRONTEND.md](../frontend/docs/FRONTEND.md), [QUERY-DATE.md](./QUERY-DATE.md). Agentes: [../AGENTS.md](../AGENTS.md).\n\n---\n\n## System\n\n```\n┌─────────────┐ ┌──────────────────────┐ ┌─────────────────┐\n│ Frontend │ HTTP │ Backend (FastAPI) │ Async │ PostgreSQL │\n│ Next.js 16 │─────────▶│ + APScheduler │─────────▶│ 16 │\n└─────────────┘ └──────────┬───────────┘ └─────────────────┘\n │\n ┌───────────────┼───────────────┐\n ▼ ▼ ▼\n sismologia.cl Open-Meteo SERNAPRED AppSync Aire Chile (HTML) SERNAGEOMIN (HTML)\n (CSN scrape) (batch REST) (Cognito Identity + SigV4) alertas-volcanicas\n```\n\n---\n\n## Components\n\n| Layer | Tech | Purpose |\n|-------|------|---------|\n| Frontend | Next.js 16, React 19, MapLibre, @dnd-kit | Mapa, overlays, consulta por día |\n| Backend | FastAPI, SQLAlchemy 2.0 async | API, riesgo, integraciones, alertas unificadas |\n| Database | PostgreSQL 16 | Geo, scores live, snapshots diarios, eventos, SERNAPRED |\n| Scheduler | APScheduler | Risk refresh, CSN, meteo, SERNAPRED, Aire Chile GEC, SERNAGEOMIN |\n\n---\n\n## Data flow\n\n1. **Geografía** — seed idempotente: 16 regiones + 346 comunas (`regional.geojson`, `comunas.geojson`).\n2. **Sismos** — CSN (sismologia.cl) → `seismic_events`; impacto → `seismic_impacts` al insertar.\n3. **Clima** — Open-Meteo (lotes) → `climate_readings` + actualización `risk_scores`.\n4. **Riesgo live** — `risk_service.recompute_all_scores` cada N min (scheduler).\n5. **Riesgo histórico** — `daily_risk_service` materializa `daily_risk_scores` por `score_date` bajo demanda.\n6. **Alertas** — SERNAPRED sync → `senapred_alerts`; SERNAGEOMIN scrape → `sernageomin_volcanic_alerts`; evaluador → alertas ChileRisk; API unifica `/alerts/active`.\n7. **Aire Chile GEC** — scrape HTML → `airechile_daily`; API `/air-quality` (cobertura parcial PPDA).\n7. **Frontend** — `lib/api.ts` + React Query; fecha global en `ui-store` para mapa y listados.\n\nConsulta por día: [QUERY-DATE.md](./QUERY-DATE.md).\n\n---\n\n## Data sources\n\nAll data comes from real providers (CSN, Open-Meteo, SERNAPRED, Aire Chile, SERNAGEOMIN) when the corresponding `USE_REAL_*` flags are enabled. When disabled, the source contributes no data (no synthetic/mock fallbacks).\n\nVariables documented in [backend/docs/BACKEND.md](../backend/docs/BACKEND.md).\n\n---\n\n## Deployment\n\n`docker-compose.yml` en raíz:\n\n| Service | Port |\n|---------|------|\n| frontend | 3000 |\n| backend | 8000 |\n| db | 5434 (host) |\n| adminer | 8080 |\n\n`make up` = build + run. Frontend imagen: Next **standalone** + **bun**.\n\n---\n\n## Monorepo structure\n\nPolyglot monorepo sin Turborepo:\n\n- **Ownership:** `AGENTS.md` por área; docs de stack en `backend/docs/` y `frontend/docs/`; cross-cutting en `docs/`.\n- **Docker:** contextos `./frontend` y `./backend` separados.\n- **Ignores:** `.gitignore` / `.dockerignore` por área; bun lock en frontend.\n- **Contrato:** Pydantic ↔ TypeScript manual ([DOC-MAINTENANCE.md](./DOC-MAINTENANCE.md)).\n- **Makefile:** atajos (`up`, `clean`, `dev-frontend`, `check-ignores`).\n\nNueva área (worker, etc.): mismo patrón + entrada en [README.md](./README.md) y ARCHITECTURE.\n\n---\n\n## Documentation map\n\n| Ubicación | Role |\n|-----------|------|\n| [docs/README.md](./README.md) | Índice monorepo (cross-cutting) |\n| [backend/docs/README.md](../backend/docs/README.md) | Índice backend |\n| [frontend/docs/README.md](../frontend/docs/README.md) | Índice frontend |\n| [HARNESS.md](./HARNESS.md) | Playbooks + `make verify` |\n| [DOC-MAINTENANCE.md](./DOC-MAINTENANCE.md) | Política de actualización |\n| [backend/docs/BACKEND.md](../backend/docs/BACKEND.md) | API, modelos, servicios |\n| [frontend/docs/FRONTEND.md](../frontend/docs/FRONTEND.md) | Componentes y hooks |\n| [backend/docs/ML-INTEGRATION.md](../backend/docs/ML-INTEGRATION.md) | ML futuro |\n\n---\n\n*Last updated: 2026-07-24*"}, "ea1956053955475e825f192071ccdef1f7785757": {"t": 1785771451.1637223, "n": 4015, "h": "963288b19724437c", "c": "# ChileRisk — Architecture\n\nVista de sistema. Detalle: [backend/docs/BACKEND.md](../backend/docs/BACKEND.md), [frontend/docs/FRONTEND.md](../frontend/docs/FRONTEND.md), [QUERY-DATE.md](./QUERY-DATE.md). Agentes: [../AGENTS.md](../AGENTS.md).\n\n---\n\n## System\n\n```\n┌─────────────┐ ┌──────────────────────┐ ┌─────────────────┐\n│ Frontend │ HTTP │ Backend (FastAPI) │ Async │ PostgreSQL │\n│ Next.js 16 │─────────▶│ + APScheduler │─────────▶│ 16 │\n└─────────────┘ └──────────┬───────────┘ └─────────────────┘\n │\n ┌───────────────┼───────────────┐\n ▼ ▼ ▼\n sismologia.cl Open-Meteo SERNAPRED AppSync Aire Chile (HTML)\n (CSN scrape) (batch REST) (Cognito Identity + SigV4)\n```\n\n---\n\n## Components\n\n| Layer | Tech | Purpose |\n|-------|------|---------|\n| Frontend | Next.js 16, React 19, MapLibre, @dnd-kit | Mapa, overlays, consulta por día |\n| Backend | FastAPI, SQLAlchemy 2.0 async | API, riesgo, integraciones, alertas unificadas |\n| Database | PostgreSQL 16 | Geo, scores live, snapshots diarios, eventos, SERNAPRED |\n| Scheduler | APScheduler | Risk refresh, CSN, meteo, SERNAPRED, Aire Chile GEC |\n\n---\n\n## Data flow\n\n1. **Geografía** — seed idempotente: 16 regiones + 346 comunas (`regional.geojson`, `comunas.geojson`).\n2. **Sismos** — CSN (sismologia.cl) → `seismic_events`; impacto → `seismic_impacts` al insertar.\n3. **Clima** — Open-Meteo (lotes) → `climate_readings` + actualización `risk_scores`.\n4. **Riesgo live** — `risk_service.recompute_all_scores` cada N min (scheduler).\n5. **Riesgo histórico** — `daily_risk_service` materializa `daily_risk_scores` por `score_date` bajo demanda.\n6. **Alertas** — SERNAPRED sync → `senapred_alerts`; evaluador → alertas ChileRisk; API unifica `/alerts/active`.\n7. **Aire Chile GEC** — scrape HTML → `airechile_daily`; API `/air-quality` (cobertura parcial PPDA).\n7. **Frontend** — `lib/api.ts` + React Query; fecha global en `ui-store` para mapa y listados.\n\nConsulta por día: [QUERY-DATE.md](./QUERY-DATE.md).\n\n---\n\n## Data sources\n\nAll data comes from real providers (CSN, Open-Meteo, SERNAPRED, Aire Chile) when the corresponding `USE_REAL_*` flags are enabled. When disabled, the source contributes no data (no synthetic/mock fallbacks).\n\nVariables documented in [backend/docs/BACKEND.md](../backend/docs/BACKEND.md).\n\n---\n\n## Deployment\n\n`docker-compose.yml` en raíz:\n\n| Service | Port |\n|---------|------|\n| frontend | 3000 |\n| backend | 8000 |\n| db | 5434 (host) |\n| adminer | 8080 |\n\n`make up` = build + run. Frontend imagen: Next **standalone** + **bun**.\n\n---\n\n## Monorepo structure\n\nPolyglot monorepo sin Turborepo:\n\n- **Ownership:** `AGENTS.md` por área; docs de stack en `backend/docs/` y `frontend/docs/`; cross-cutting en `docs/`.\n- **Docker:** contextos `./frontend` y `./backend` separados.\n- **Ignores:** `.gitignore` / `.dockerignore` por área; bun lock en frontend.\n- **Contrato:** Pydantic ↔ TypeScript manual ([DOC-MAINTENANCE.md](./DOC-MAINTENANCE.md)).\n- **Makefile:** atajos (`up`, `clean`, `dev-frontend`, `check-ignores`).\n\nNueva área (worker, etc.): mismo patrón + entrada en [README.md](./README.md) y ARCHITECTURE.\n\n---\n\n## Documentation map\n\n| Ubicación | Role |\n|-----------|------|\n| [docs/README.md](./README.md) | Índice monorepo (cross-cutting) |\n| [backend/docs/README.md](../backend/docs/README.md) | Índice backend |\n| [frontend/docs/README.md](../frontend/docs/README.md) | Índice frontend |\n| [HARNESS.md](./HARNESS.md) | Playbooks + `make verify` |\n| [DOC-MAINTENANCE.md](./DOC-MAINTENANCE.md) | Política de actualización |\n| [backend/docs/BACKEND.md](../backend/docs/BACKEND.md) | API, modelos, servicios |\n| [frontend/docs/FRONTEND.md](../frontend/docs/FRONTEND.md) | Componentes y hooks |\n| [backend/docs/ML-INTEGRATION.md](../backend/docs/ML-INTEGRATION.md) | ML futuro |\n\n---\n\n*Last updated: 2026-06-17*"}, "f08477139fa6885641eb7c4c7af6c02d3cc8181b": {"t": 1785771452.4943414, "n": 3865, "h": "49354b36991363e8", "c": "# ChileRisk — Architecture\n\nVista de sistema. Detalle: [backend/docs/BACKEND.md](../backend/docs/BACKEND.md), [frontend/docs/FRONTEND.md](../frontend/docs/FRONTEND.md), [QUERY-DATE.md](./QUERY-DATE.md). Agentes: [../AGENTS.md](../AGENTS.md).\n\n---\n\n## System\n\n```\n┌─────────────┐ ┌──────────────────────┐ ┌─────────────────┐\n│ Frontend │ HTTP │ Backend (FastAPI) │ Async │ PostgreSQL │\n│ Next.js 16 │─────────▶│ + APScheduler │─────────▶│ 16 │\n└─────────────┘ └──────────┬───────────┘ └─────────────────┘\n │\n ┌───────────────┼───────────────┐\n ▼ ▼ ▼\n sismologia.cl Open-Meteo SERNAPRED AppSync\n (CSN scrape) (batch REST) (Cognito Identity + SigV4)\n```\n\n---\n\n## Components\n\n| Layer | Tech | Purpose |\n|-------|------|---------|\n| Frontend | Next.js 16, React 19, MapLibre, @dnd-kit | Mapa, overlays, consulta por día |\n| Backend | FastAPI, SQLAlchemy 2.0 async | API, riesgo, integraciones, alertas unificadas |\n| Database | PostgreSQL 16 | Geo, scores live, snapshots diarios, eventos, SERNAPRED |\n| Scheduler | APScheduler | Risk refresh, CSN, meteo, SERNAPRED |\n\n---\n\n## Data flow\n\n1. **Geografía** — seed idempotente: 16 regiones + 346 comunas (`regional.geojson`, `comunas.geojson`).\n2. **Sismos** — CSN (sismologia.cl) → `seismic_events`; impacto → `seismic_impacts` al insertar.\n3. **Clima** — Open-Meteo (lotes) → `climate_readings` + actualización `risk_scores`.\n4. **Riesgo live** — `risk_service.recompute_all_scores` cada N min (scheduler).\n5. **Riesgo histórico** — `daily_risk_service` materializa `daily_risk_scores` por `score_date` bajo demanda.\n6. **Alertas** — SERNAPRED sync → `senapred_alerts`; evaluador → alertas ChileRisk; API unifica `/alerts/active`.\n7. **Frontend** — `lib/api.ts` + React Query; fecha global en `ui-store` para mapa y listados.\n\nConsulta por día: [QUERY-DATE.md](./QUERY-DATE.md).\n\n---\n\n## Data sources\n\nAll data comes from real providers (CSN, Open-Meteo, SERNAPRED) when the corresponding `USE_REAL_*` flags are enabled. When disabled, the source contributes no data (no synthetic/mock fallbacks).\n\nVariables documented in [backend/docs/BACKEND.md](../backend/docs/BACKEND.md).\n\n---\n\n## Deployment\n\n`docker-compose.yml` en raíz:\n\n| Service | Port |\n|---------|------|\n| frontend | 3000 |\n| backend | 8000 |\n| db | 5434 (host) |\n| adminer | 8080 |\n\n`make up` = build + run. Frontend imagen: Next **standalone** + **bun**.\n\n---\n\n## Monorepo structure\n\nPolyglot monorepo sin Turborepo:\n\n- **Ownership:** `AGENTS.md` por área; docs de stack en `backend/docs/` y `frontend/docs/`; cross-cutting en `docs/`.\n- **Docker:** contextos `./frontend` y `./backend` separados.\n- **Ignores:** `.gitignore` / `.dockerignore` por área; bun lock en frontend.\n- **Contrato:** Pydantic ↔ TypeScript manual ([DOC-MAINTENANCE.md](./DOC-MAINTENANCE.md)).\n- **Makefile:** atajos (`up`, `clean`, `dev-frontend`, `check-ignores`).\n\nNueva área (worker, etc.): mismo patrón + entrada en [README.md](./README.md) y ARCHITECTURE.\n\n---\n\n## Documentation map\n\n| Ubicación | Role |\n|-----------|------|\n| [docs/README.md](./README.md) | Índice monorepo (cross-cutting) |\n| [backend/docs/README.md](../backend/docs/README.md) | Índice backend |\n| [frontend/docs/README.md](../frontend/docs/README.md) | Índice frontend |\n| [HARNESS.md](./HARNESS.md) | Playbooks + `make verify` |\n| [DOC-MAINTENANCE.md](./DOC-MAINTENANCE.md) | Política de actualización |\n| [backend/docs/BACKEND.md](../backend/docs/BACKEND.md) | API, modelos, servicios |\n| [frontend/docs/FRONTEND.md](../frontend/docs/FRONTEND.md) | Componentes y hooks |\n| [backend/docs/ML-INTEGRATION.md](../backend/docs/ML-INTEGRATION.md) | ML futuro |\n\n---\n\n*Last updated: 2026-06-17*"}, "a898379e7a15f203dac1a5f0abea31432d92f1ff": {"t": 1785771454.0225472, "n": 4016, "h": "cc87b86d9cce4157", "c": "# ChileRisk — Architecture\n\nVista de sistema. Detalle: [backend/docs/BACKEND.md](../backend/docs/BACKEND.md), [frontend/docs/FRONTEND.md](../frontend/docs/FRONTEND.md), [QUERY-DATE.md](./QUERY-DATE.md). Agentes: [../AGENTS.md](../AGENTS.md).\n\n---\n\n## System\n\n```\n┌─────────────┐ ┌──────────────────────┐ ┌─────────────────┐\n│ Frontend │ HTTP │ Backend (FastAPI) │ Async │ PostgreSQL │\n│ Next.js 16 │─────────▶│ + APScheduler │─────────▶│ 16 │\n└─────────────┘ └──────────┬───────────┘ └─────────────────┘\n │\n ┌───────────────┼───────────────┐\n ▼ ▼ ▼\n sismologia.cl Open-Meteo SERNAPRED AppSync\n (CSN scrape) (batch REST) (Cognito Identity + SigV4)\n```\n\n---\n\n## Components\n\n| Layer | Tech | Purpose |\n|-------|------|---------|\n| Frontend | Next.js 16, React 19, MapLibre, @dnd-kit | Mapa, overlays, consulta por día |\n| Backend | FastAPI, SQLAlchemy 2.0 async | API, riesgo, integraciones, alertas unificadas |\n| Database | PostgreSQL 16 | Geo, scores live, snapshots diarios, eventos, SERNAPRED |\n| Scheduler | APScheduler | Risk refresh, CSN, meteo, SERNAPRED |\n\n---\n\n## Data flow\n\n1. **Geografía** — seed idempotente: 16 regiones + 346 comunas (`regional.geojson`, `comunas.geojson`).\n2. **Sismos** — mock o CSN → `seismic_events`; impacto → `seismic_impacts` al insertar.\n3. **Clima** — mock o Open-Meteo (lotes) → `climate_readings` + actualización `risk_scores`.\n4. **Riesgo live** — `risk_service.recompute_all_scores` cada N min (scheduler).\n5. **Riesgo histórico** — `daily_risk_service` materializa `daily_risk_scores` por `score_date` bajo demanda.\n6. **Alertas** — SERNAPRED sync → `senapred_alerts`; evaluador → alertas ChileRisk; API unifica `/alerts/active`.\n7. **Frontend** — `lib/api.ts` + React Query; fecha global en `ui-store` para mapa y listados.\n\nConsulta por día: [QUERY-DATE.md](./QUERY-DATE.md).\n\n---\n\n## Hybrid mode\n\n| `USE_REAL_CSN` | `USE_REAL_METEO` | `USE_REAL_SENAPRED` | Sismos | Clima | Alertas |\n|----------------|------------------|---------------------|--------|-------|---------|\n| false | false | false | Mock | Mock | Vacío / mock |\n| true | false | true | CSN | Mock | SERNAPRED |\n| true | true | true | CSN | Open-Meteo | SERNAPRED |\n\nVariables en root `.env`. Matriz startup: [backend/docs/BACKEND.md](../backend/docs/BACKEND.md).\n\n---\n\n## Deployment\n\n`docker-compose.yml` en raíz:\n\n| Service | Port |\n|---------|------|\n| frontend | 3000 |\n| backend | 8000 |\n| db | 5434 (host) |\n| adminer | 8080 |\n\n`make up` = build + run. Frontend imagen: Next **standalone** + **bun**.\n\n---\n\n## Monorepo structure\n\nPolyglot monorepo sin Turborepo:\n\n- **Ownership:** `AGENTS.md` por área; docs de stack en `backend/docs/` y `frontend/docs/`; cross-cutting en `docs/`.\n- **Docker:** contextos `./frontend` y `./backend` separados.\n- **Ignores:** `.gitignore` / `.dockerignore` por área; bun lock en frontend.\n- **Contrato:** Pydantic ↔ TypeScript manual ([DOC-MAINTENANCE.md](./DOC-MAINTENANCE.md)).\n- **Makefile:** atajos (`up`, `clean`, `dev-frontend`, `check-ignores`).\n\nNueva área (worker, etc.): mismo patrón + entrada en [README.md](./README.md) y ARCHITECTURE.\n\n---\n\n## Documentation map\n\n| Ubicación | Role |\n|-----------|------|\n| [docs/README.md](./README.md) | Índice monorepo (cross-cutting) |\n| [backend/docs/README.md](../backend/docs/README.md) | Índice backend |\n| [frontend/docs/README.md](../frontend/docs/README.md) | Índice frontend |\n| [HARNESS.md](./HARNESS.md) | Playbooks + `make verify` |\n| [DOC-MAINTENANCE.md](./DOC-MAINTENANCE.md) | Política de actualización |\n| [backend/docs/BACKEND.md](../backend/docs/BACKEND.md) | API, modelos, servicios |\n| [frontend/docs/FRONTEND.md](../frontend/docs/FRONTEND.md) | Componentes y hooks |\n| [backend/docs/ML-INTEGRATION.md](../backend/docs/ML-INTEGRATION.md) | ML futuro |\n\n---\n\n*Last updated: 2026-06-05*"}, "b989ae0d8685535286d8ce322dc95b7d10b3fa49": {"t": 1785771455.5860834, "n": 5331, "h": "8531c009f51d6c36", "c": "# ChileRisk — Architecture\n\nHigh-level view of the system. For implementation details see `backend/AGENTS.md` and `frontend/AGENTS.md`.\n\n---\n\n## System\n\n```\n┌─────────────┐ ┌──────────────────────┐ ┌─────────────────┐\n│ Frontend │ HTTP │ Backend (FastAPI) │ Async │ PostgreSQL │\n│ (Next.js) │─────────▶│ + Scheduler │─────────▶│ (risk data) │\n└─────────────┘ └──────────┬───────────┘ └─────────────────┘\n │\n │ External APIs (hybrid mode)\n ▼\n ┌─────────────────────┐\n │ CSN / sismologia.cl │\n │ Open-Meteo │\n └─────────────────────┘\n```\n\n---\n\n## Components\n\n| Layer | Tech | Purpose |\n|-------|------|---------|\n| Frontend | Next.js 16, React 19, MapLibre GL | Interactive map + risk visualization |\n| Backend | FastAPI, SQLAlchemy 2.0 async | API + risk computation + data integration |\n| Database | PostgreSQL 16 | Regions, comunas, risk scores, seismic events |\n| Scheduler | APScheduler | Background jobs (risk refresh, real data sync) |\n| External | CSN, Open-Meteo | Real seismic + climate data (when enabled) |\n\n---\n\n## Data Flow\n\n1. **Geography** (seeded once): 16 regions + 346 comunas with centroids\n2. **Seismic events**: mock generators OR real CSN scraper → `seismic_events` table\n3. **Climate data**: mock generators OR Open-Meteo API → `risk_scores` table\n4. **Risk engine**: recomputes all comuna scores every 15 min using Haversine + attenuation model\n5. **API**: serves aggregated risk data to frontend\n\n---\n\n## Hybrid Mode\n\n| `USE_REAL_CSN` | `USE_REAL_METEO` | Seismic source | Climate source |\n|----------------|------------------|----------------|----------------|\n| false | false | Mock | Mock |\n| true | false | CSN/sismologia.cl | Mock |\n| false | true | Mock | Open-Meteo |\n| true | true | CSN/sismologia.cl | Open-Meteo |\n\nConfigure via root `.env` file. Fallback to mocks if external APIs fail.\n\n---\n\n## Deployment\n\nSingle `docker-compose.yml` at project root:\n- `frontend` (Next.js standalone) → port 3000\n- `backend` (FastAPI + uvicorn) → port 8000\n- `db` (PostgreSQL 16) → port 5434 (host)\n- `adminer` (DB visual tool) → port 8080\n\n---\n\n## Monorepo Structure & Boundaries\n\nChileRisk is a **polyglot monorepo** (`frontend/` Next.js + `backend/` FastAPI + shared orchestration) without heavy monorepo tooling (no pnpm workspaces or Turborepo yet). Structure is enforced by a combination of:\n\n- **Directory ownership + AGENTS.md routing** (root + `frontend/AGENTS.md` + `backend/AGENTS.md`): \"all work happens exclusively inside your area\". Cross-area or root-file changes require explicit approval.\n- **Isolated Docker build contexts** (`build: ./frontend` and `build: ./backend` in compose). The root tree is *not* sent to the images.\n- **Ignore hygiene** (as of 2026-06):\n - `backend/.gitignore` — complete Python ignores (`__pycache__/`, `*.py[cod]`, envs, build artifacts, local sqlite, caches...).\n - `backend/.dockerignore` — prevents the above (and .git, .env*, logs, tests, etc.) from entering the image build context.\n - `frontend/.gitignore` + `frontend/.dockerignore` — enforce **bun** as the single package manager (package-lock.json / pnpm / yarn locks are ignored).\n - Root `.gitignore` — cross-cutting (OS, .env, TrueRisk, Docker override, top-level logs, defense-in-depth Python caches + alt package manager locks).\n- **Root `Makefile`** — convenient monorepo entrypoint (`make up`, `make clean`, `make dev-frontend`, `make check-ignores`, `make help`, etc.). Delegates to docker compose or `cd <area> && ...`. Does not replace the per-area commands.\n- **Explicit contract sync**: Backend Pydantic schemas (`backend/app/schemas/`) and frontend TypeScript types (`frontend/lib/types.ts`) must be kept in lockstep manually (documented in root AGENTS.md). No automatic codegen today.\n\n### Why this shape?\n\n- Docker isolation + per-area manifests already gave good boundaries.\n- The original pain point (persistent `__pycache__` / `.pyc` appearing in `git status` and Docker contexts) was caused by non-recursive patterns in the old root-only Python ignore section + missing `backend/.dockerignore`.\n- Adding the per-area ignore files + cleaning the root `.gitignore` + adding `Makefile` + `ENV PYTHONDONTWRITEBYTECODE=1` (and later standardizing the frontend on bun) makes the structure self-documenting and resilient.\n\n### Adding something new\n\n- A new HTTP resource or Python service → `backend/` only (see `backend/AGENTS.md`).\n- A new UI component/hook/page → `frontend/` only (see `frontend/AGENTS.md`).\n- A cross-cutting change (new env var, API shape, root tooling, docs) → plan it and obtain approval.\n- Future extra area (e.g. a worker) → follow the same pattern (own Dockerfile, own .gitignore/.dockerignore, entry in compose, update ARCHITECTURE + AGENTS).\n\nSee also:\n- `Makefile` (root targets)\n- `backend/.gitignore`, `backend/.dockerignore`\n- `frontend/.gitignore`, `frontend/.dockerignore`\n- `root AGENTS.md` (prohibitions and routing table)\n- `backend/AGENTS.md` / `frontend/AGENTS.md` (detailed decision rules)\n\n---\n\n*Last updated: 2026-06 (monorepo hygiene + structure section added)*\n"}, "b7386989558c1dc6a8dff153cae1ea0ba5aa3e52": {"t": 1785771457.2889721, "n": 2377, "h": "b783ee8b6842427f", "c": "# ChileRisk — Architecture\n\nHigh-level view of the system. For implementation details see `backend/AGENTS.md` and `frontend/AGENTS.md`.\n\n---\n\n## System\n\n```\n┌─────────────┐ ┌──────────────────────┐ ┌─────────────────┐\n│ Frontend │ HTTP │ Backend (FastAPI) │ Async │ PostgreSQL │\n│ (Next.js) │─────────▶│ + Scheduler │─────────▶│ (risk data) │\n└─────────────┘ └──────────┬───────────┘ └─────────────────┘\n │\n │ External APIs (hybrid mode)\n ▼\n ┌─────────────────────┐\n │ CSN / sismologia.cl │\n │ Open-Meteo │\n └─────────────────────┘\n```\n\n---\n\n## Components\n\n| Layer | Tech | Purpose |\n|-------|------|---------|\n| Frontend | Next.js 16, React 19, MapLibre GL | Interactive map + risk visualization |\n| Backend | FastAPI, SQLAlchemy 2.0 async | API + risk computation + data integration |\n| Database | PostgreSQL 16 | Regions, comunas, risk scores, seismic events |\n| Scheduler | APScheduler | Background jobs (risk refresh, real data sync) |\n| External | CSN, Open-Meteo | Real seismic + climate data (when enabled) |\n\n---\n\n## Data Flow\n\n1. **Geography** (seeded once): 16 regions + 346 comunas with centroids\n2. **Seismic events**: mock generators OR real CSN scraper → `seismic_events` table\n3. **Climate data**: mock generators OR Open-Meteo API → `risk_scores` table\n4. **Risk engine**: recomputes all comuna scores every 15 min using Haversine + attenuation model\n5. **API**: serves aggregated risk data to frontend\n\n---\n\n## Hybrid Mode\n\n| `USE_REAL_CSN` | `USE_REAL_METEO` | Seismic source | Climate source |\n|----------------|------------------|----------------|----------------|\n| false | false | Mock | Mock |\n| true | false | CSN/sismologia.cl | Mock |\n| false | true | Mock | Open-Meteo |\n| true | true | CSN/sismologia.cl | Open-Meteo |\n\nConfigure via root `.env` file. Fallback to mocks if external APIs fail.\n\n---\n\n## Deployment\n\nSingle `docker-compose.yml` at project root:\n- `frontend` (Next.js standalone) → port 3000\n- `backend` (FastAPI + uvicorn) → port 8000\n- `db` (PostgreSQL 16) → port 5434 (host)\n- `adminer` (DB visual tool) → port 8080\n\n---\n\n*Last updated: 2026-05-29*\n"}}, "backend/app/services/dashboard_service.py": {"30681b4014428b11bb1e1893055ae930388993bd": {"t": 1786068240.9288933, "n": 5588, "h": "ae603c9611ff22cd", "c": "\"\"\"AI-generated citizen dashboard summary (DeepSeek).\"\"\"\n\nimport asyncio\nimport json\nimport logging\nfrom datetime import datetime, timedelta, timezone\nfrom typing import Any\n\nfrom cachetools import TTLCache\nfrom fastapi import HTTPException\nfrom sqlalchemy import select\nfrom sqlalchemy.ext.asyncio import AsyncSession\n\nfrom app.config import settings\nfrom app.models.seismic_event import SeismicEvent\nfrom app.schemas.dashboard import DashboardSummaryOut\nfrom app.services.airechile_service import get_airechile_by_comuna\nfrom app.services.alert_service import list_active_alerts\nfrom app.services.daily_risk_service import get_national_risk_for_date\nfrom app.services.deepseek_client import get_deepseek_client\nfrom app.services.query_date_window import today_chile\nfrom app.services.risk_service import get_latest_risk_for_comuna\nfrom app.services.user_profile_service import get_user_profile\n\nlogger = logging.getLogger(\"chilerisk.dashboard\")\n\n# 15 min — aligned with the scheduler risk refresh cadence.\n_summary_cache: TTLCache = TTLCache(maxsize=400, ttl=900)\n\n_SYSTEM_PROMPT = (\n \"Eres el resumidor de ChileRisk, una app de monitoreo de amenazas naturales en Chile. \"\n \"Escribe un resumen breve (máx. 120 palabras), en español, tono ciudadano claro y calmado, \"\n \"2 párrafos: (1) panorama nacional, (2) situación de la comuna del usuario si se entrega. \"\n \"Usa SOLO los datos JSON entregados; no inventes cifras ni alertas. No uses markdown.\"\n)\n\n_LLM_TIMEOUT_SECONDS = 20\n\n\nasync def get_dashboard_summary(session: AsyncSession, user_id: str) -> DashboardSummaryOut:\n profile = await get_user_profile(session, user_id)\n home_comuna_code = profile.home_comuna_code if profile else None\n home_comuna_name = profile.home_comuna_name if profile else None\n\n cache_key: int | str = home_comuna_code if home_comuna_code is not None else \"national\"\n if cache_key in _summary_cache:\n hit = _summary_cache[cache_key]\n return DashboardSummaryOut(\n summary=hit[\"summary\"],\n generated_at=hit[\"generated_at\"],\n cached=True,\n comuna_name=home_comuna_name,\n )\n\n today = today_chile()\n\n national = await get_national_risk_for_date(session, today)\n top_regions = sorted(national, key=lambda r: r[\"composite_score\"], reverse=True)[:3]\n\n # Already severity-sorted by the service (roja → informativa).\n top_alerts = (await list_active_alerts(session, query_date=today))[:5]\n\n since = datetime.now(timezone.utc) - timedelta(hours=24)\n events = (\n (await session.execute(select(SeismicEvent).where(SeismicEvent.occurred_at >= since)))\n .scalars()\n .all()\n )\n max_event = max(events, key=lambda e: e.magnitude, default=None)\n max_location = None\n if max_event is not None:\n raw = max_event.raw_data or {}\n loc = raw.get(\"location\")\n if isinstance(loc, str) and loc.strip():\n max_location = loc\n\n context: dict[str, Any] = {\n \"nacional\": {\n \"top_regiones\": [\n {\n \"nombre\": r[\"name\"],\n \"severidad\": r[\"severity\"],\n \"amenaza_dominante\": r[\"dominant_hazard\"],\n \"puntaje\": r[\"composite_score\"],\n }\n for r in top_regions\n ]\n },\n \"alertas\": [\n {\"nivel\": a.level, \"titulo\": a.title, \"region\": a.region_name} for a in top_alerts\n ],\n \"sismos_24h\": {\n \"total\": len(events),\n \"magnitud_maxima\": max_event.magnitude if max_event else None,\n \"ubicacion_magnitud_maxima\": max_location,\n },\n }\n\n if home_comuna_code is not None:\n comuna_section: dict[str, Any] = {\"nombre\": home_comuna_name}\n risk = await get_latest_risk_for_comuna(session, home_comuna_code)\n if risk is not None:\n comuna_section[\"puntaje\"] = round(risk.composite_score, 1)\n comuna_section[\"severidad\"] = risk.severity\n comuna_section[\"amenaza_dominante\"] = risk.dominant_hazard\n aire = await get_airechile_by_comuna(\n session, cod_comuna=home_comuna_code, condition_date=today\n )\n if aire is not None:\n comuna_section[\"calidad_aire\"] = aire.level\n context[\"comuna\"] = comuna_section\n\n if not settings.deepseek_api_key.strip():\n raise HTTPException(status_code=503, detail=\"Resumen IA no disponible\")\n\n try:\n client = get_deepseek_client()\n response = await asyncio.wait_for(\n client.chat.completions.create(\n model=settings.deepseek_model,\n messages=[\n {\"role\": \"system\", \"content\": _SYSTEM_PROMPT},\n {\"role\": \"user\", \"content\": json.dumps(context, ensure_ascii=False)},\n ],\n temperature=0.3,\n ),\n timeout=_LLM_TIMEOUT_SECONDS,\n )\n summary_text = (response.choices[0].message.content or \"\").strip()\n if not summary_text:\n raise ValueError(\"empty_summary\")\n except Exception as exc:\n logger.warning(\"Dashboard summary LLM call failed: %s\", exc)\n raise HTTPException(status_code=503, detail=\"Resumen IA no disponible\") from exc\n\n generated_at = datetime.now(timezone.utc)\n _summary_cache[cache_key] = {\"summary\": summary_text, \"generated_at\": generated_at}\n return DashboardSummaryOut(\n summary=summary_text,\n generated_at=generated_at,\n cached=False,\n comuna_name=home_comuna_name,\n )\n"}}}}