deepmap-mcp 0.2.0__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.
@@ -0,0 +1,65 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .eggs/
8
+ *.egg
9
+
10
+ # Virtual environment
11
+ venv/
12
+ .venv/
13
+ env/
14
+
15
+ # IDE
16
+ .vscode/
17
+ .idea/
18
+ *.swp
19
+ *.swo
20
+
21
+ # Data (862 MB -- regenerate with trainers/fetchers, NOT stored in git)
22
+ # Run: python overnight_trainer.py OR python -m data_pipeline.scheduler --once
23
+ data/
24
+ !data/README.md
25
+ *.h5
26
+ *.hdf5
27
+ *.segy
28
+ *.sgy
29
+ *.mseed
30
+ *.nc
31
+ *.db
32
+
33
+ # Output
34
+ outputs/
35
+ *.png
36
+ *.html
37
+ !demo_landing.html
38
+ !volcanosafe/landing.html
39
+ *.pdf
40
+
41
+ # Trained Models (TRADE SECRET - never commit weights)
42
+ models/
43
+ *.joblib
44
+ *.pkl
45
+ *.onnx
46
+
47
+ # Secrets
48
+ .env
49
+ *.key
50
+ credentials/
51
+
52
+ # IP-sensitive docs (keep local only)
53
+ IP_STRATEGY.md
54
+ IP_STRATEGY.docx
55
+
56
+ # Word temp files
57
+ ~$*.docx
58
+
59
+ # OS
60
+ .DS_Store
61
+ Thumbs.db
62
+
63
+ # Claude
64
+ .claude/
65
+ .vercel
@@ -0,0 +1,111 @@
1
+ Metadata-Version: 2.4
2
+ Name: deepmap-mcp
3
+ Version: 0.2.0
4
+ Summary: DeepMap AI MCP Server - Real-time volcano monitoring, earthquake prediction, seismic risk, and geophysical intelligence for Claude and MCP-compatible LLMs
5
+ Project-URL: Homepage, https://deepmap.ai
6
+ Project-URL: Documentation, https://api.volcanosafe.com/docs
7
+ Project-URL: Repository, https://deepmapai.com
8
+ Author-email: DeepMap AI <info@deepmapai.com>
9
+ License-Expression: MIT
10
+ Keywords: claude,deepmap,earthquake,geophysics,mcp,risk,safety,seismic,volcano
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Scientific/Engineering :: GIS
16
+ Requires-Python: >=3.10
17
+ Requires-Dist: httpx>=0.25
18
+ Requires-Dist: mcp>=1.0
19
+ Description-Content-Type: text/markdown
20
+
21
+ # DeepMap AI MCP Server
22
+
23
+ Real-time volcano monitoring, earthquake prediction, and geophysical intelligence as a [Model Context Protocol](https://modelcontextprotocol.io/) server.
24
+ Gives Claude (and any MCP-compatible LLM) direct access to live volcanic risk scores,
25
+ earthquake predictions, seismic risk assessments, tidal forcing analysis,
26
+ flight disruption forecasts, tidal eruption windows, LiDAR fault detection, and travel advisories.
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ pip install deepmap-mcp
32
+ ```
33
+
34
+ ## Configure for Claude Desktop
35
+
36
+ Add the following to your `claude_desktop_config.json`:
37
+
38
+ ```json
39
+ {
40
+ "mcpServers": {
41
+ "deepmap": {
42
+ "command": "python",
43
+ "args": ["-m", "volcanosafe.mcp_server"],
44
+ "env": {
45
+ "DEEPMAP_API_URL": "https://api.volcanosafe.com",
46
+ "DEEPMAP_API_KEY": "dm_live_..."
47
+ }
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ Or run directly from the command line:
54
+
55
+ ```bash
56
+ deepmap-mcp
57
+ ```
58
+
59
+ ## Available Tools
60
+
61
+ ### Volcano Tools
62
+
63
+ | Tool | Description |
64
+ |------|-------------|
65
+ | `volcano_risk` | Full real-time risk assessment with seismic data, tidal state, travel advisory, and flight impact |
66
+ | `volcano_quick` | Low-latency risk score without live seismic fetch -- ideal for batch or insurance queries |
67
+ | `volcano_flights` | Flight disruption risk for airports near a volcano |
68
+ | `volcano_tidal_forecast` | Tidal eruption risk window forecast based on Mf tidal cycle analysis |
69
+ | `volcano_advisory` | Plain-text travel advisory with insurance recommendation and monitoring agency status |
70
+ | `volcano_list` | List all monitored volcanoes with names, countries, coordinates, and last eruption dates |
71
+ | `volcano_batch` | Quick risk scores for multiple volcanoes at once (max 10 per request) |
72
+
73
+ ### Earthquake Tools
74
+
75
+ | Tool | Description |
76
+ |------|-------------|
77
+ | `predict_earthquake` | ML earthquake risk prediction for a region using GradientBoosting trained on real USGS data. Returns risk score, confidence, and contributing features |
78
+ | `list_earthquake_regions` | List all supported earthquake monitoring regions with bounds, historical rate, and magnitude of completeness |
79
+ | `assess_current_earthquakes` | Current global seismic risk assessment across all monitored regions using live USGS data |
80
+ | `earthquake_alert_cycle` | Full alert cycle across all monitored regions with triggered alerts and recommended actions |
81
+ | `predict_seismic_risk` | Seismic hazard risk score (0-10) for any lat/lon combining Vs30 site classification, liquefaction probability, and amplification factor |
82
+ | `analyze_tidal_forcing` | Tidal stress forcing analysis on earthquake triggering using Schuster's test and Rayleigh statistics |
83
+ | `detect_fault_scarps` | LiDAR/3DEP slope-break fault scarp detection with strike, dip direction, and length estimates |
84
+
85
+ ### Supported Earthquake Regions
86
+
87
+ | Region Key | Name |
88
+ |------------|------|
89
+ | `ucerf3` | Southern California (UCERF3) |
90
+ | `new_madrid` | New Madrid Seismic Zone |
91
+ | `cascadia` | Cascadia Subduction Zone |
92
+ | `wasatch` | Wasatch Front (Utah) |
93
+ | `oklahoma` | Oklahoma (Induced) |
94
+ | `trans_mexico` | Trans-Mexican Volcanic Belt |
95
+
96
+ ## Environment Variables
97
+
98
+ | Variable | Default | Description |
99
+ |----------|---------|-------------|
100
+ | `DEEPMAP_API_URL` | `https://api.volcanosafe.com` | API base URL |
101
+ | `DEEPMAP_API_KEY` | *(empty)* | API key for authenticated access |
102
+ | `VOLCANOSAFE_BASE_URL` | *(fallback)* | Legacy env var, still supported |
103
+ | `VOLCANOSAFE_API_KEY` | *(fallback)* | Legacy env var, still supported |
104
+
105
+ ## Documentation
106
+
107
+ Full API documentation: [https://api.volcanosafe.com/docs](https://api.volcanosafe.com/docs)
108
+
109
+ ## License
110
+
111
+ MIT
@@ -0,0 +1,91 @@
1
+ # DeepMap AI MCP Server
2
+
3
+ Real-time volcano monitoring, earthquake prediction, and geophysical intelligence as a [Model Context Protocol](https://modelcontextprotocol.io/) server.
4
+ Gives Claude (and any MCP-compatible LLM) direct access to live volcanic risk scores,
5
+ earthquake predictions, seismic risk assessments, tidal forcing analysis,
6
+ flight disruption forecasts, tidal eruption windows, LiDAR fault detection, and travel advisories.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ pip install deepmap-mcp
12
+ ```
13
+
14
+ ## Configure for Claude Desktop
15
+
16
+ Add the following to your `claude_desktop_config.json`:
17
+
18
+ ```json
19
+ {
20
+ "mcpServers": {
21
+ "deepmap": {
22
+ "command": "python",
23
+ "args": ["-m", "volcanosafe.mcp_server"],
24
+ "env": {
25
+ "DEEPMAP_API_URL": "https://api.volcanosafe.com",
26
+ "DEEPMAP_API_KEY": "dm_live_..."
27
+ }
28
+ }
29
+ }
30
+ }
31
+ ```
32
+
33
+ Or run directly from the command line:
34
+
35
+ ```bash
36
+ deepmap-mcp
37
+ ```
38
+
39
+ ## Available Tools
40
+
41
+ ### Volcano Tools
42
+
43
+ | Tool | Description |
44
+ |------|-------------|
45
+ | `volcano_risk` | Full real-time risk assessment with seismic data, tidal state, travel advisory, and flight impact |
46
+ | `volcano_quick` | Low-latency risk score without live seismic fetch -- ideal for batch or insurance queries |
47
+ | `volcano_flights` | Flight disruption risk for airports near a volcano |
48
+ | `volcano_tidal_forecast` | Tidal eruption risk window forecast based on Mf tidal cycle analysis |
49
+ | `volcano_advisory` | Plain-text travel advisory with insurance recommendation and monitoring agency status |
50
+ | `volcano_list` | List all monitored volcanoes with names, countries, coordinates, and last eruption dates |
51
+ | `volcano_batch` | Quick risk scores for multiple volcanoes at once (max 10 per request) |
52
+
53
+ ### Earthquake Tools
54
+
55
+ | Tool | Description |
56
+ |------|-------------|
57
+ | `predict_earthquake` | ML earthquake risk prediction for a region using GradientBoosting trained on real USGS data. Returns risk score, confidence, and contributing features |
58
+ | `list_earthquake_regions` | List all supported earthquake monitoring regions with bounds, historical rate, and magnitude of completeness |
59
+ | `assess_current_earthquakes` | Current global seismic risk assessment across all monitored regions using live USGS data |
60
+ | `earthquake_alert_cycle` | Full alert cycle across all monitored regions with triggered alerts and recommended actions |
61
+ | `predict_seismic_risk` | Seismic hazard risk score (0-10) for any lat/lon combining Vs30 site classification, liquefaction probability, and amplification factor |
62
+ | `analyze_tidal_forcing` | Tidal stress forcing analysis on earthquake triggering using Schuster's test and Rayleigh statistics |
63
+ | `detect_fault_scarps` | LiDAR/3DEP slope-break fault scarp detection with strike, dip direction, and length estimates |
64
+
65
+ ### Supported Earthquake Regions
66
+
67
+ | Region Key | Name |
68
+ |------------|------|
69
+ | `ucerf3` | Southern California (UCERF3) |
70
+ | `new_madrid` | New Madrid Seismic Zone |
71
+ | `cascadia` | Cascadia Subduction Zone |
72
+ | `wasatch` | Wasatch Front (Utah) |
73
+ | `oklahoma` | Oklahoma (Induced) |
74
+ | `trans_mexico` | Trans-Mexican Volcanic Belt |
75
+
76
+ ## Environment Variables
77
+
78
+ | Variable | Default | Description |
79
+ |----------|---------|-------------|
80
+ | `DEEPMAP_API_URL` | `https://api.volcanosafe.com` | API base URL |
81
+ | `DEEPMAP_API_KEY` | *(empty)* | API key for authenticated access |
82
+ | `VOLCANOSAFE_BASE_URL` | *(fallback)* | Legacy env var, still supported |
83
+ | `VOLCANOSAFE_API_KEY` | *(fallback)* | Legacy env var, still supported |
84
+
85
+ ## Documentation
86
+
87
+ Full API documentation: [https://api.volcanosafe.com/docs](https://api.volcanosafe.com/docs)
88
+
89
+ ## License
90
+
91
+ MIT
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "deepmap-mcp"
7
+ version = "0.2.0"
8
+ description = "DeepMap AI MCP Server - Real-time volcano monitoring, earthquake prediction, seismic risk, and geophysical intelligence for Claude and MCP-compatible LLMs"
9
+ readme = "README_MCP.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.10"
12
+ authors = [
13
+ {name = "DeepMap AI", email = "info@deepmapai.com"},
14
+ ]
15
+ keywords = ["volcano", "earthquake", "seismic", "mcp", "claude", "risk", "geophysics", "safety", "deepmap"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Topic :: Scientific/Engineering :: GIS",
22
+ ]
23
+ dependencies = [
24
+ "mcp>=1.0",
25
+ "httpx>=0.25",
26
+ ]
27
+
28
+ [project.urls]
29
+ Homepage = "https://deepmap.ai"
30
+ Documentation = "https://api.volcanosafe.com/docs"
31
+ Repository = "https://deepmapai.com"
32
+
33
+ [tool.hatch.build.targets.wheel]
34
+ packages = ["volcanosafe"]
35
+
36
+ [project.scripts]
37
+ deepmap-mcp = "volcanosafe.mcp_server:main"
@@ -0,0 +1,2 @@
1
+ """DeepMap AI -- Real-time volcano monitoring, earthquake prediction, and geophysical intelligence."""
2
+ __version__ = "0.2.0"
@@ -0,0 +1,4 @@
1
+ """Allow running as `python -m volcanosafe`."""
2
+ from volcanosafe.mcp_server import main
3
+
4
+ main()
@@ -0,0 +1,376 @@
1
+ """
2
+ VolcanoSafe -- Standalone Tourism Risk API
3
+ =============================================
4
+
5
+ FastAPI server providing real-time volcano risk intelligence
6
+ for travel insurance, tour operators, and tourism apps.
7
+
8
+ Launch:
9
+ uvicorn volcanosafe.api:app --host 0.0.0.0 --port 8070
10
+
11
+ Endpoints:
12
+ GET / -> API info + pricing
13
+ GET /v1/volcanoes -> List all monitored volcanoes
14
+ GET /v1/risk/{volcano_id} -> Full risk assessment
15
+ GET /v1/quick/{volcano_id} -> Quick risk score (low latency)
16
+ GET /v1/flights/{volcano_id} -> Flight disruption risk
17
+ GET /v1/tidal/{volcano_id} -> 14-day tidal forecast
18
+ GET /v1/advisory/{volcano_id} -> Travel advisory text
19
+ GET /v1/batch -> Batch risk for multiple volcanoes
20
+ GET /health -> Health check
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import time
26
+ from datetime import datetime, timezone
27
+ from typing import List, Optional
28
+
29
+ from pathlib import Path
30
+
31
+ from fastapi import FastAPI, HTTPException, Query
32
+ from fastapi.middleware.cors import CORSMiddleware
33
+ from fastapi.responses import FileResponse, JSONResponse, PlainTextResponse, Response
34
+
35
+ from volcanosafe.risk_engine import (
36
+ assess_volcano_risk,
37
+ compute_tidal_stress,
38
+ forecast_tidal_windows,
39
+ list_volcanoes,
40
+ VOLCANOES,
41
+ )
42
+
43
+ app = FastAPI(
44
+ title="VolcanoSafe API",
45
+ description=(
46
+ "Real-time volcano risk intelligence for tourism, travel insurance, "
47
+ "and aviation. Combines seismic monitoring, tidal forcing analysis, "
48
+ "eruption history, and atmospheric dispersion modeling."
49
+ ),
50
+ version="1.0.0",
51
+ contact={
52
+ "name": "DeepMap AI",
53
+ "email": "info@volcanosafe.com",
54
+ },
55
+ servers=[
56
+ {"url": "https://api.volcanosafe.com", "description": "Production"},
57
+ {"url": "http://localhost:8070", "description": "Local development"},
58
+ ],
59
+ )
60
+
61
+ app.add_middleware(
62
+ CORSMiddleware,
63
+ allow_origins=["*"],
64
+ allow_credentials=True,
65
+ allow_methods=["*"],
66
+ allow_headers=["*"],
67
+ )
68
+
69
+ # Request counter for usage tracking
70
+ _request_count = 0
71
+ _start_time = time.time()
72
+
73
+
74
+ # =====================================================================
75
+ # Landing / Info
76
+ # =====================================================================
77
+
78
+ @app.get("/", operation_id="getApiInfo", tags=["System"])
79
+ def root():
80
+ """API information and pricing."""
81
+ global _request_count
82
+ _request_count += 1
83
+ return {
84
+ "name": "VolcanoSafe API",
85
+ "version": "1.0.0",
86
+ "description": "Real-time volcano risk intelligence for tourism and travel",
87
+ "volcanoes_monitored": len(VOLCANOES),
88
+ "data_sources": [
89
+ "USGS Earthquake Catalog (real-time)",
90
+ "Tidal forcing model (Mf p=0.000132, peer-reviewed methodology)",
91
+ "Smithsonian GVP eruption catalog",
92
+ "CENAPRED / INGV / USGS monitoring agencies",
93
+ ],
94
+ "endpoints": {
95
+ "/v1/volcanoes": "List all monitored volcanoes",
96
+ "/v1/risk/{id}": "Full risk assessment with travel advisory",
97
+ "/v1/quick/{id}": "Quick risk score (optimized for high volume)",
98
+ "/v1/flights/{id}": "Airport disruption risk assessment",
99
+ "/v1/tidal/{id}": "14-day tidal eruption window forecast",
100
+ "/v1/advisory/{id}": "Plain-text travel advisory",
101
+ "/v1/batch?ids=popo,colima": "Batch assessment for multiple volcanoes",
102
+ },
103
+ "pricing": {
104
+ "free_tier": "100 requests/day, risk score only",
105
+ "starter": "$49/mo -- 10K requests, full assessment",
106
+ "professional": "$199/mo -- 100K requests, batch + webhooks",
107
+ "enterprise": "$999/mo -- unlimited, SLA, custom volcanoes",
108
+ "insurance_api": "$0.50-2.00/policy quote (volume pricing)",
109
+ },
110
+ }
111
+
112
+
113
+ # =====================================================================
114
+ # Volcano List
115
+ # =====================================================================
116
+
117
+ @app.get("/v1/volcanoes", operation_id="listVolcanoes", tags=["Volcanoes"])
118
+ def get_volcanoes():
119
+ """List all monitored volcanoes."""
120
+ global _request_count
121
+ _request_count += 1
122
+ return {
123
+ "count": len(VOLCANOES),
124
+ "volcanoes": list_volcanoes(),
125
+ }
126
+
127
+
128
+ # =====================================================================
129
+ # Full Risk Assessment
130
+ # =====================================================================
131
+
132
+ @app.get("/v1/risk/{volcano_id}", operation_id="getVolcanoRisk", tags=["Risk Assessment"])
133
+ def get_risk(volcano_id: str, include_seismic: bool = True):
134
+ """
135
+ Full risk assessment for a volcano.
136
+
137
+ Returns risk score (0-100), alert level (1-5), travel advisory,
138
+ seismic analysis, tidal state, 14-day forecast, and flight impact.
139
+ """
140
+ global _request_count
141
+ _request_count += 1
142
+
143
+ result = assess_volcano_risk(volcano_id, include_seismic=include_seismic)
144
+ if "error" in result:
145
+ raise HTTPException(status_code=404, detail=result)
146
+ return result
147
+
148
+
149
+ # =====================================================================
150
+ # Quick Risk Score (low latency)
151
+ # =====================================================================
152
+
153
+ @app.get("/v1/quick/{volcano_id}", operation_id="getQuickRisk", tags=["Risk Assessment"])
154
+ def get_quick_risk(volcano_id: str):
155
+ """
156
+ Quick risk score -- optimized for high-volume insurance queries.
157
+ Skips real-time seismic fetch for faster response.
158
+ """
159
+ global _request_count
160
+ _request_count += 1
161
+
162
+ result = assess_volcano_risk(volcano_id, include_seismic=False)
163
+ if "error" in result:
164
+ raise HTTPException(status_code=404, detail=result)
165
+
166
+ return {
167
+ "volcano_id": volcano_id,
168
+ "risk_score": result["assessment"]["risk_score"],
169
+ "alert_level": result["assessment"]["alert_level"],
170
+ "alert_name": result["assessment"]["alert_name"],
171
+ "alert_color": result["assessment"]["alert_color"],
172
+ "headline": result["travel_advisory"]["headline"],
173
+ "insurance_advisory": result["travel_advisory"]["insurance_advisory"],
174
+ "tidal_risk": result["tidal_state"]["tidal_risk_label"],
175
+ "timestamp": result["assessment"]["timestamp"],
176
+ }
177
+
178
+
179
+ # =====================================================================
180
+ # Flight Disruption Risk
181
+ # =====================================================================
182
+
183
+ @app.get("/v1/flights/{volcano_id}", operation_id="getFlightRisk", tags=["Aviation"])
184
+ def get_flight_risk(volcano_id: str):
185
+ """Airport disruption risk from volcanic activity."""
186
+ global _request_count
187
+ _request_count += 1
188
+
189
+ result = assess_volcano_risk(volcano_id, include_seismic=False)
190
+ if "error" in result:
191
+ raise HTTPException(status_code=404, detail=result)
192
+
193
+ return {
194
+ "volcano": result["volcano"],
195
+ "risk_score": result["assessment"]["risk_score"],
196
+ "alert_name": result["assessment"]["alert_name"],
197
+ "flight_impact": result["flight_impact"],
198
+ "recommendation": (
199
+ "Check with airline before travel"
200
+ if result["assessment"]["risk_score"] >= 40
201
+ else "No expected flight disruption"
202
+ ),
203
+ }
204
+
205
+
206
+ # =====================================================================
207
+ # Tidal Forecast
208
+ # =====================================================================
209
+
210
+ @app.get("/v1/tidal/{volcano_id}", operation_id="getTidalForecast", tags=["Tidal Forecast"])
211
+ def get_tidal_forecast(volcano_id: str, days: int = Query(default=14, ge=1, le=30)):
212
+ """14-day tidal eruption risk window forecast."""
213
+ global _request_count
214
+ _request_count += 1
215
+
216
+ vid = volcano_id.lower().replace(" ", "_").replace("-", "_")
217
+ if vid not in VOLCANOES:
218
+ raise HTTPException(status_code=404, detail=f"Unknown volcano: {volcano_id}")
219
+
220
+ v = VOLCANOES[vid]
221
+ current = compute_tidal_stress(datetime.now(timezone.utc), v["lat"])
222
+ windows = forecast_tidal_windows(v["lat"], days_ahead=days)
223
+
224
+ return {
225
+ "volcano": {"id": vid, "name": v["name"]},
226
+ "current_tidal_state": current,
227
+ "forecast_days": days,
228
+ "high_risk_windows": windows,
229
+ "methodology": "Based on Mf fortnightly tidal cycle (p=0.000132, Schuster test on 40,913 M5+ earthquakes)",
230
+ }
231
+
232
+
233
+ # =====================================================================
234
+ # Travel Advisory
235
+ # =====================================================================
236
+
237
+ @app.get("/v1/advisory/{volcano_id}", operation_id="getTravelAdvisory", tags=["Travel Advisory"])
238
+ def get_advisory(volcano_id: str):
239
+ """Plain-text travel advisory for a volcano."""
240
+ global _request_count
241
+ _request_count += 1
242
+
243
+ result = assess_volcano_risk(volcano_id, include_seismic=False)
244
+ if "error" in result:
245
+ raise HTTPException(status_code=404, detail=result)
246
+
247
+ return {
248
+ "volcano": result["volcano"]["name"],
249
+ "country": result["volcano"]["country"],
250
+ **result["travel_advisory"],
251
+ "risk_score": result["assessment"]["risk_score"],
252
+ "alert_level": result["assessment"]["alert_name"],
253
+ "tourism_notes": result.get("tourism_notes", ""),
254
+ "monitoring_agency": result["monitoring"]["agency"],
255
+ }
256
+
257
+
258
+ # =====================================================================
259
+ # Batch Assessment
260
+ # =====================================================================
261
+
262
+ @app.get("/v1/batch", operation_id="batchRiskScores", tags=["Volcanoes"])
263
+ def batch_risk(ids: str = Query(..., description="Comma-separated volcano IDs")):
264
+ """Batch risk assessment for multiple volcanoes."""
265
+ global _request_count
266
+ _request_count += 1
267
+
268
+ volcano_ids = [v.strip() for v in ids.split(",")]
269
+ results = []
270
+
271
+ for vid in volcano_ids[:10]: # Max 10 per batch
272
+ result = assess_volcano_risk(vid, include_seismic=False)
273
+ if "error" not in result:
274
+ results.append({
275
+ "id": vid,
276
+ "name": result["volcano"]["name"],
277
+ "risk_score": result["assessment"]["risk_score"],
278
+ "alert_name": result["assessment"]["alert_name"],
279
+ "headline": result["travel_advisory"]["headline"],
280
+ })
281
+ else:
282
+ results.append({"id": vid, "error": result["error"]})
283
+
284
+ return {
285
+ "count": len(results),
286
+ "assessments": results,
287
+ "timestamp": datetime.now(timezone.utc).isoformat(),
288
+ }
289
+
290
+
291
+ # =====================================================================
292
+ # Health Check
293
+ # =====================================================================
294
+
295
+ @app.get("/health", operation_id="healthCheck", tags=["System"])
296
+ def health():
297
+ """API health check."""
298
+ return {
299
+ "status": "healthy",
300
+ "uptime_seconds": round(time.time() - _start_time, 1),
301
+ "total_requests": _request_count,
302
+ "volcanoes_monitored": len(VOLCANOES),
303
+ "timestamp": datetime.now(timezone.utc).isoformat(),
304
+ }
305
+
306
+
307
+ # =====================================================================
308
+ # AI Plugin / ChatGPT GPT Actions Discovery
309
+ # =====================================================================
310
+
311
+ _PLUGIN_PATH = Path(__file__).parent / "ai-plugin.json"
312
+
313
+
314
+ @app.get("/.well-known/ai-plugin.json", include_in_schema=False)
315
+ def plugin_manifest():
316
+ """Serve the OpenAI ChatGPT plugin manifest for GPT Actions discovery."""
317
+ if not _PLUGIN_PATH.exists():
318
+ raise HTTPException(status_code=404, detail="Plugin manifest not found")
319
+ return FileResponse(_PLUGIN_PATH, media_type="application/json")
320
+
321
+
322
+ # =====================================================================
323
+ # SEO: robots.txt and sitemap.xml
324
+ # =====================================================================
325
+
326
+ @app.get("/robots.txt", include_in_schema=False)
327
+ def robots_txt():
328
+ """Serve robots.txt for search engine crawlers."""
329
+ content = (
330
+ "User-agent: *\n"
331
+ "Allow: /\n"
332
+ "Allow: /docs\n"
333
+ "Allow: /openapi.json\n"
334
+ "Allow: /landing\n"
335
+ "Allow: /.well-known/ai-plugin.json\n"
336
+ "\n"
337
+ "Sitemap: https://api.volcanosafe.com/sitemap.xml\n"
338
+ )
339
+ return PlainTextResponse(content)
340
+
341
+
342
+ @app.get("/sitemap.xml", include_in_schema=False)
343
+ def sitemap_xml():
344
+ """Serve sitemap.xml for search engines."""
345
+ urls = [
346
+ "https://api.volcanosafe.com/landing",
347
+ "https://api.volcanosafe.com/docs",
348
+ "https://api.volcanosafe.com/v1/volcanoes",
349
+ "https://api.volcanosafe.com/.well-known/ai-plugin.json",
350
+ ]
351
+ xml_entries = "\n".join(
352
+ f" <url><loc>{u}</loc><changefreq>weekly</changefreq></url>"
353
+ for u in urls
354
+ )
355
+ xml = (
356
+ '<?xml version="1.0" encoding="UTF-8"?>\n'
357
+ '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
358
+ f"{xml_entries}\n"
359
+ "</urlset>"
360
+ )
361
+ return Response(content=xml, media_type="application/xml")
362
+
363
+
364
+ # =====================================================================
365
+ # Landing Page
366
+ # =====================================================================
367
+
368
+ _LANDING_PATH = Path(__file__).parent / "landing.html"
369
+
370
+
371
+ @app.get("/landing", include_in_schema=False)
372
+ def landing_page():
373
+ """Serve the VolcanoSafe marketing landing page."""
374
+ if not _LANDING_PATH.exists():
375
+ raise HTTPException(status_code=404, detail="Landing page not found")
376
+ return FileResponse(_LANDING_PATH, media_type="text/html")