diff --git a/src/static/dashboard.html b/src/static/dashboard.html
index 5c25717..95e4110 100644
--- a/src/static/dashboard.html
+++ b/src/static/dashboard.html
@@ -3,7 +3,18 @@
- Ping River Live Monitor
+ Ping River Live Monitor · Chiang Mai Water Levels & Flood Forecast
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/static/llms.txt b/src/static/llms.txt
new file mode 100644
index 0000000..20124a4
--- /dev/null
+++ b/src/static/llms.txt
@@ -0,0 +1,34 @@
+# Ping River Live Monitor
+
+> Live water levels, discharge, rainfall and machine-learning flood forecasts for
+> the Ping River basin in Northern Thailand, centered on Chiang Mai (gauge P.1,
+> Nawarat Bridge). Open-source, hourly data since 2018, operated by B4L
+> (https://buildfor.life).
+
+Flood context: Chiang Mai city flooding begins when P.1 reaches 3.70 m
+(official inundation stage 1); the record peak was 5.30 m on 2024-10-05.
+
+## Data
+
+- Hourly water level (m) and discharge (m³/s) from 16 Royal Irrigation
+ Department (RID) telemetry gauges in the Ping basin, 2018-present.
+- Hourly rainfall and water level for 400+ ThaiWater/HII stations (Ping basin),
+ including historical water levels back to 2019.
+- ML flood-risk forecasts (probability of warning/danger level within 6/12/24 h)
+ per station, plus Chiang Mai inundation-stage probabilities.
+
+## API (no key required)
+
+- https://water.buildfor.life/docs : interactive OpenAPI reference
+- /stations : station metadata (code, names, coordinates)
+- /measurements/latest?limit=N : latest readings
+- /measurements/history/{station_code}?hours=N or ?start=YYYY-MM-DD&end=YYYY-MM-DD : history
+- /forecast : flood-risk forecasts for all stations
+- /api/stats : database coverage statistics
+- /api/hii/rainfall/latest : latest rainfall per ThaiWater/HII station
+- /api/hii/waterlevel/latest : latest water level per ThaiWater/HII station (m MSL)
+
+## Source
+
+- https://git.b4l.co.th/B4L/Northern-Thailand-Ping-River-Monitor : code, docs,
+ and the full data-source catalog (docs/DATA_SOURCES.md).
diff --git a/src/static/robots.txt b/src/static/robots.txt
new file mode 100644
index 0000000..f3abfad
--- /dev/null
+++ b/src/static/robots.txt
@@ -0,0 +1,6 @@
+User-agent: *
+Allow: /
+Disallow: /metrics
+Disallow: /scrape/
+
+Sitemap: https://water.buildfor.life/sitemap.xml
diff --git a/src/static/sitemap.xml b/src/static/sitemap.xml
new file mode 100644
index 0000000..6f5cbf5
--- /dev/null
+++ b/src/static/sitemap.xml
@@ -0,0 +1,13 @@
+
+
+
+ https://water.buildfor.life/
+ hourly
+ 1.0
+
+
+ https://water.buildfor.life/docs
+ monthly
+ 0.5
+
+
diff --git a/src/web_api.py b/src/web_api.py
index 0d32a0e..6c5b5e6 100644
--- a/src/web_api.py
+++ b/src/web_api.py
@@ -16,7 +16,7 @@ from typing import Any, Dict, List, Optional
import requests
from fastapi import BackgroundTasks, Depends, FastAPI, Header, HTTPException, Query
from fastapi.middleware.cors import CORSMiddleware
-from fastapi.responses import HTMLResponse
+from fastapi.responses import FileResponse, HTMLResponse
from fastapi.staticfiles import StaticFiles
from .config import Config
@@ -276,6 +276,27 @@ async def root():
return HTMLResponse(content=DASHBOARD_HTML)
+# Crawler/indexing files must live at the domain root (served from src/static)
+_STATIC_DIR = os.path.dirname(_DASHBOARD_HTML_PATH)
+
+
+@app.get("/robots.txt", include_in_schema=False)
+async def robots_txt():
+ return FileResponse(os.path.join(_STATIC_DIR, "robots.txt"), media_type="text/plain")
+
+
+@app.get("/llms.txt", include_in_schema=False)
+async def llms_txt():
+ return FileResponse(os.path.join(_STATIC_DIR, "llms.txt"), media_type="text/plain")
+
+
+@app.get("/sitemap.xml", include_in_schema=False)
+async def sitemap_xml():
+ return FileResponse(
+ os.path.join(_STATIC_DIR, "sitemap.xml"), media_type="application/xml"
+ )
+
+
@app.get("/health", response_model=HealthResponse)
async def get_health():
"""Get system health status"""
diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py
index 7896729..f9732f8 100644
--- a/tests/test_dashboard.py
+++ b/tests/test_dashboard.py
@@ -40,6 +40,26 @@ def test_dashboard_loads_additional_thaiwater_sensors():
assert "applyStationSearch" in html
+def test_dashboard_has_seo_and_indexing_files():
+ html = DASHBOARD_PATH.read_text(encoding="utf-8")
+ static_dir = DASHBOARD_PATH.parent
+
+ assert '' in html
+ assert 'property="og:title"' in html
+ assert 'rel="icon"' in html
+
+ robots = (static_dir / "robots.txt").read_text(encoding="utf-8")
+ assert "Sitemap: https://water.buildfor.life/sitemap.xml" in robots
+ assert "https://water.buildfor.life/" in (static_dir / "sitemap.xml").read_text(encoding="utf-8")
+ assert "Ping River Live Monitor" in (static_dir / "llms.txt").read_text(encoding="utf-8")
+
+ from src import web_api
+
+ routes = {route.path for route in web_api.app.routes}
+ assert {"/robots.txt", "/llms.txt", "/sitemap.xml"} <= routes
+
+
def test_dashboard_shows_hii_rainfall_layer():
html = DASHBOARD_PATH.read_text(encoding="utf-8")