vigia-eew 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. vigia_eew-0.3.1/.gitignore +32 -0
  2. vigia_eew-0.3.1/ARCHITECTURE.md +187 -0
  3. vigia_eew-0.3.1/LICENSE +674 -0
  4. vigia_eew-0.3.1/PKG-INFO +935 -0
  5. vigia_eew-0.3.1/README.md +223 -0
  6. vigia_eew-0.3.1/docs/API-SPEC.md +286 -0
  7. vigia_eew-0.3.1/docs/DATA-MODEL.md +282 -0
  8. vigia_eew-0.3.1/docs/IMPLEMENTATION-PLAN.md +297 -0
  9. vigia_eew-0.3.1/docs/PRD.md +195 -0
  10. vigia_eew-0.3.1/docs/TECHNICAL-DESIGN.md +511 -0
  11. vigia_eew-0.3.1/docs/plans/2026-07-05-country-filter-design.md +62 -0
  12. vigia_eew-0.3.1/pyproject.toml +91 -0
  13. vigia_eew-0.3.1/src/vigia_eew/__init__.py +16 -0
  14. vigia_eew-0.3.1/src/vigia_eew/agent_state.py +42 -0
  15. vigia_eew-0.3.1/src/vigia_eew/app.py +426 -0
  16. vigia_eew-0.3.1/src/vigia_eew/assets/countries.geojson +1 -0
  17. vigia_eew-0.3.1/src/vigia_eew/assets/critical.wav +0 -0
  18. vigia_eew-0.3.1/src/vigia_eew/assets/info.wav +0 -0
  19. vigia_eew-0.3.1/src/vigia_eew/assets/tray_icon.png +0 -0
  20. vigia_eew-0.3.1/src/vigia_eew/assets/warning.wav +0 -0
  21. vigia_eew-0.3.1/src/vigia_eew/autostart/__init__.py +82 -0
  22. vigia_eew-0.3.1/src/vigia_eew/autostart/linux_systemd.py +98 -0
  23. vigia_eew-0.3.1/src/vigia_eew/autostart/macos_launchagent.py +84 -0
  24. vigia_eew-0.3.1/src/vigia_eew/autostart/windows_task.py +82 -0
  25. vigia_eew-0.3.1/src/vigia_eew/backoff.py +49 -0
  26. vigia_eew-0.3.1/src/vigia_eew/cli.py +115 -0
  27. vigia_eew-0.3.1/src/vigia_eew/config.py +246 -0
  28. vigia_eew-0.3.1/src/vigia_eew/config.toml.example +71 -0
  29. vigia_eew-0.3.1/src/vigia_eew/geo.py +35 -0
  30. vigia_eew-0.3.1/src/vigia_eew/geocode.py +103 -0
  31. vigia_eew-0.3.1/src/vigia_eew/geoloc.py +68 -0
  32. vigia_eew-0.3.1/src/vigia_eew/i18n.py +70 -0
  33. vigia_eew-0.3.1/src/vigia_eew/ingest/__init__.py +33 -0
  34. vigia_eew-0.3.1/src/vigia_eew/ingest/rest_usgs.py +162 -0
  35. vigia_eew-0.3.1/src/vigia_eew/ingest/ws_emsc.py +118 -0
  36. vigia_eew-0.3.1/src/vigia_eew/logging_conf.py +69 -0
  37. vigia_eew-0.3.1/src/vigia_eew/models.py +140 -0
  38. vigia_eew-0.3.1/src/vigia_eew/notify/__init__.py +11 -0
  39. vigia_eew-0.3.1/src/vigia_eew/notify/alert_window.py +172 -0
  40. vigia_eew-0.3.1/src/vigia_eew/notify/controller.py +109 -0
  41. vigia_eew-0.3.1/src/vigia_eew/notify/presentation.py +86 -0
  42. vigia_eew-0.3.1/src/vigia_eew/notify/queue.py +130 -0
  43. vigia_eew-0.3.1/src/vigia_eew/notify/sound.py +129 -0
  44. vigia_eew-0.3.1/src/vigia_eew/notify/toast.py +80 -0
  45. vigia_eew-0.3.1/src/vigia_eew/pipeline/__init__.py +8 -0
  46. vigia_eew-0.3.1/src/vigia_eew/pipeline/dedup.py +72 -0
  47. vigia_eew-0.3.1/src/vigia_eew/pipeline/filter.py +55 -0
  48. vigia_eew-0.3.1/src/vigia_eew/pipeline/normalize.py +140 -0
  49. vigia_eew-0.3.1/src/vigia_eew/pipeline/processor.py +80 -0
  50. vigia_eew-0.3.1/src/vigia_eew/simulation.py +48 -0
  51. vigia_eew-0.3.1/src/vigia_eew/state.py +130 -0
  52. vigia_eew-0.3.1/src/vigia_eew/subprocess_env.py +40 -0
  53. vigia_eew-0.3.1/src/vigia_eew/supervisor.py +134 -0
  54. vigia_eew-0.3.1/src/vigia_eew/tray.py +130 -0
  55. vigia_eew-0.3.1/src/vigia_eew/tui.py +174 -0
@@ -0,0 +1,32 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ *.spec.bak
9
+
10
+ # Entornos
11
+ .venv/
12
+ venv/
13
+ env/
14
+
15
+ # Herramientas
16
+ .pytest_cache/
17
+ .mypy_cache/
18
+ .ruff_cache/
19
+ .coverage
20
+ htmlcov/
21
+
22
+ # uv
23
+ uv.lock
24
+
25
+ # IDE
26
+ .idea/
27
+ .vscode/
28
+
29
+ # Runtime
30
+ *.log
31
+ state.json
32
+ config.toml
@@ -0,0 +1,187 @@
1
+ # ARCHITECTURE — Vigía-eew
2
+
3
+ | Field | Value |
4
+ |---|---|
5
+ | Document | System architecture and diagrams |
6
+ | Version | 1.0 (draft for review) |
7
+ | Status | 🟡 Pending approval |
8
+ | Related | `docs/PRD.md`, `docs/API-SPEC.md`, `docs/TECHNICAL-DESIGN.md`, `docs/DATA-MODEL.md`, `docs/IMPLEMENTATION-PLAN.md` |
9
+
10
+ > Diagrams are in **Mermaid** (renderable text on GitHub and most Markdown viewers).
11
+
12
+ ---
13
+
14
+ ## 1. Overview
15
+
16
+ Vigía is a **single asyncio process per machine** with no single point of failure (RNF-02). It
17
+ receives earthquakes via **push** (WebSocket EMSC, primary channel) and reconciles them with a
18
+ **low-frequency backup** (USGS polling every 60 s). A *pipeline* normalizes, filters by zone, and
19
+ deduplicates them; new and relevant events trigger an **undismissable desktop alert** (overlay
20
+ window + toast + sound). Critical state is **persisted** to survive restarts.
21
+
22
+ ## 2. Components
23
+
24
+ | Component | Role | RF |
25
+ |---|---|---|
26
+ | **WSIngestor (EMSC)** | WebSocket connection, 15 s keepalive, backoff reconnection, emits raw messages | RF-01..RF-04 |
27
+ | **RESTReconciler (USGS)** | 60 s polling with persisted cursor; safety net | RF-05, RF-06 |
28
+ | **Normalizer** | Raw→`SeismicEvent`; haversine; severity | RF-07, RF-08, RF-13 |
29
+ | **GeoFilter** | Discards events outside radius or below minimum magnitude | RF-12 |
30
+ | **Deduplicator** | Inter-source heuristic; persisted ids; handles `update` | RF-09..RF-11 |
31
+ | **Notifier (toast)** | Native informational toast (`desktop-notifier`) | RF-14 |
32
+ | **AlertWindow (overlay)** | Topmost Tkinter window, with focus, undismissable | RF-15..RF-19 |
33
+ | **AlertQueue + bridge** | Event queue; asyncio↔Tk bridge | RF-20 |
34
+ | **Sound** | Audio by severity | RF-17 |
35
+ | **StateStore** | Atomic JSON persistence (ids, cursor) | RF-06, RF-10 |
36
+ | **Settings** | Loads/validates `config.toml` (pydantic) | RF-24 |
37
+ | **Supervisor** | Orchestrates asyncio tasks; restarts on failure | RNF-03, RNF-04 |
38
+ | **Autostart** | systemd / LaunchAgent / scheduled task | RF-22, RF-23 |
39
+ | **CLI (`vigia-eew`)** | Startup, `--simulate`, autostart | RF-21, RF-26 |
40
+
41
+ ## 3. Architecture diagram — data flow
42
+
43
+ ```mermaid
44
+ flowchart LR
45
+ subgraph Fuentes["External sources"]
46
+ EMSC["EMSC WebSocket<br/>(primary push)"]
47
+ USGS["USGS FDSN REST<br/>(60 s backup)"]
48
+ end
49
+
50
+ subgraph Ingesta["Ingestion layer (asyncio)"]
51
+ WS["WSIngestor<br/>keepalive + reconnection"]
52
+ REST["RESTReconciler<br/>persisted cursor"]
53
+ end
54
+
55
+ Q(["raw_queue<br/>(asyncio.Queue)"])
56
+
57
+ subgraph Pipeline["Pipeline"]
58
+ NORM["Normalizer<br/>haversine + severity"]
59
+ FILT["GeoFilter<br/>radius + min magnitude"]
60
+ DEDUP["Deduplicator<br/>heuristic + ids"]
61
+ end
62
+
63
+ subgraph Estado["Persistence"]
64
+ STATE[("StateStore<br/>state.json<br/>ids + cursor")]
65
+ end
66
+
67
+ subgraph Notif["Notification"]
68
+ AQ(["AlertQueue<br/>+ Tk bridge"])
69
+ TOAST["Notifier (toast)"]
70
+ WIN["AlertWindow<br/>topmost · sound · ACKNOWLEDGED"]
71
+ end
72
+
73
+ CFG["Settings<br/>config.toml"]
74
+
75
+ EMSC -->|create/update| WS
76
+ USGS -->|GeoJSON| REST
77
+ WS --> Q
78
+ REST --> Q
79
+ Q --> NORM --> FILT --> DEDUP
80
+ DEDUP -->|new + relevant| AQ
81
+ AQ --> TOAST
82
+ AQ --> WIN
83
+ DEDUP <-->|alerted ids| STATE
84
+ REST <-->|cursor| STATE
85
+ CFG -.config.-> NORM
86
+ CFG -.config.-> FILT
87
+ CFG -.config.-> DEDUP
88
+ CFG -.config.-> WIN
89
+ ```
90
+
91
+ ## 4. Sequence diagram — from EMSC to the alert window
92
+
93
+ ```mermaid
94
+ sequenceDiagram
95
+ autonumber
96
+ participant EMSC as EMSC WebSocket
97
+ participant WS as WSIngestor
98
+ participant PIPE as Pipeline (normalize/filter/dedup)
99
+ participant ST as StateStore
100
+ participant UI as AlertQueue + AlertWindow
101
+ participant USR as User
102
+
103
+ EMSC->>WS: message {action:"create", data: Feature}
104
+ WS->>PIPE: raw message (raw_queue)
105
+ PIPE->>PIPE: normalizes (haversine, severity)
106
+ PIPE->>PIPE: filters (radius, min magnitude)
107
+ PIPE->>ST: id already alerted? duplicate?
108
+ ST-->>PIPE: no (new event)
109
+ PIPE->>ST: registers alerted id
110
+ PIPE->>UI: enqueues SeismicEvent
111
+ UI->>UI: shows topmost window + takes focus + sound
112
+ UI-->>USR: MAGNITUDE, place, distance, depth, local time, source
113
+ Note over EMSC,UI: If an {action:"update"} arrives for the same unid,<br/>the displayed event is updated WITHOUT a new alert (RF-11)
114
+ USR->>UI: presses "ACKNOWLEDGED"
115
+ UI->>ST: registers acknowledgment (audit)
116
+ UI->>UI: closes window and shows the next one in the queue
117
+ ```
118
+
119
+ ## 5. State diagram — WebSocket connection
120
+
121
+ ```mermaid
122
+ stateDiagram-v2
123
+ [*] --> Connecting
124
+ Connecting --> Connected: handshake OK
125
+ Connecting --> Backoff: connection error
126
+
127
+ Connected --> Ping: every ~15 s
128
+ Ping --> Connected: pong received
129
+ Ping --> Down: ping_timeout (no pong)
130
+
131
+ Connected --> Receiving: incoming message
132
+ Receiving --> Connected: processed
133
+
134
+ Connected --> Down: socket close/EOF
135
+ Down --> Backoff: schedule retry
136
+
137
+ Backoff --> Connecting: exponential wait + jitter (max backoff_max_s)
138
+
139
+ Connected --> Closing: SIGINT/SIGTERM
140
+ Closing --> [*]
141
+ ```
142
+
143
+ ## 6. What happens if… (resilience scenarios)
144
+
145
+ | Scenario | Expected behavior | Mechanism / RF |
146
+ |---|---|---|
147
+ | **The WS goes down** | Close/ping_timeout is detected → `Down` state → exponential `Backoff` with jitter → perpetual reconnection. The process **does not die**. | RF-03, RNF-03; §5 |
148
+ | **The WS silently stops receiving** | The **keepalive (15 s ping)** detects the loss via `ping_timeout` and forces reconnection. | RF-02 |
149
+ | **REST fails (429/5xx/timeout)** | `Retry-After` is honored (429); the cycle is skipped and retried after 60 s; the **cursor is kept**; no abort. | RF-05; Technical Design §8 |
150
+ | **An `update` arrives** | Same `unid` already seen → the displayed/queued event is **updated** (e.g. magnitude) **without** triggering a new alert. | RF-11, CU-3 |
151
+ | **Two sources report the same earthquake** | The heuristic (≤100 km, ≤90 s, ≤0.5 mag) recognizes it as a duplicate → **a single** alert. | RF-09, CU-4 |
152
+ | **The agent restarts with pending alerts** | `StateStore` remembers `alerted_ids` → already-acknowledged ones **are not re-alerted**; `usgs_cursor` avoids reprocessing history. | RF-06, RF-10, CU-10 |
153
+ | **Invalid JSON / unexpected schema** | pydantic validation discards the item and logs it; the flow continues. | RNF-03 |
154
+ | **Total network loss** | Both ingestion paths keep retrying; once the network returns, USGS **reconciles** what was missed during the outage. | RF-05, OBJ-3 |
155
+ | **OS "do not disturb"** | The toast may be silenced, but the **topmost, focused overlay window** guarantees the alert. | RF-15, RF-16, RNF-05 |
156
+ | **UI fails** | Isolated from the pipeline (decoupled bridge); ingestion keeps running; showing the alert is retried. | ADR-006 |
157
+
158
+ ## 7. Deployment
159
+
160
+ Each machine runs its own agent (no SPOF). OS-specific autostart (systemd `--user`, LaunchAgent,
161
+ scheduled task) keeps the process alive after login.
162
+
163
+ ```mermaid
164
+ flowchart TB
165
+ subgraph PCs["N independent machines (no single point of failure)"]
166
+ A["Vigía Agent<br/>(Linux · systemd --user)"]
167
+ B["Vigía Agent<br/>(Windows · scheduled task)"]
168
+ C["Vigía Agent<br/>(macOS · LaunchAgent)"]
169
+ end
170
+ EMSC["EMSC WS"] --> A & B & C
171
+ USGS["USGS FDSN"] --> A & B & C
172
+ ```
173
+
174
+ ## 8. Future evolution — central relay (not v1)
175
+
176
+ ADR-008 documents the migration to a **FastAPI relay** that consumes EMSC/USGS once and does
177
+ *fan-out* over WebSocket to many Vigía clients, **reusing the internal contract** (`SeismicEvent`)
178
+ as the payload so as not to break the data model.
179
+
180
+ ```mermaid
181
+ flowchart LR
182
+ EMSC["EMSC WS"] --> RELAY["FastAPI Relay<br/>(central dedup)"]
183
+ USGS["USGS FDSN"] --> RELAY
184
+ RELAY -->|fan-out WS<br/>SeismicEvent| C1["Vigía client 1"]
185
+ RELAY -->|fan-out WS| C2["Vigía client 2"]
186
+ RELAY -->|fan-out WS| C3["Vigía client N"]
187
+ ```