landvex: Fixar och tester klara för alla komponenter
- Datafabrik: Dockerfile fix, agentorkestrering fungerar - Vision: Identify-modell, FAISS, OCR alla testade - API: Alla 7 integrationstester passerade - Upplösare: Entitetsupplösning verifierad
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
"""
|
||||
LIFE Live Data API
|
||||
Exponerar realtidsdata från multi-source pipeline
|
||||
"""
|
||||
from fastapi import APIRouter
|
||||
import sqlite3
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
router = APIRouter(prefix="/live", tags=["live_data"])
|
||||
|
||||
DB_PATH = "/home/bernt/.openclaw/workspace/rivp-pilot-1/rivp.db"
|
||||
|
||||
def get_db():
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
@router.get("/public/status")
|
||||
async def get_live_status():
|
||||
"""Get current system status"""
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
|
||||
# Total counts
|
||||
c.execute("SELECT COUNT(*) FROM roads")
|
||||
total_roads = c.fetchone()[0]
|
||||
|
||||
c.execute("SELECT COUNT(*) FROM observations")
|
||||
total_obs = c.fetchone()[0]
|
||||
|
||||
# Today's observations
|
||||
c.execute("SELECT COUNT(*) FROM observations WHERE detected_date = date('now')")
|
||||
today_obs = c.fetchone()[0]
|
||||
|
||||
# By source
|
||||
c.execute("SELECT source, COUNT(*) as count FROM observations GROUP BY source ORDER BY count DESC")
|
||||
sources = {row[0]: row[1] for row in c.fetchall()}
|
||||
|
||||
# Recent activity (last hour)
|
||||
c.execute("""
|
||||
SELECT COUNT(*) FROM observations
|
||||
WHERE datetime(created_at) > datetime('now', '-1 hour')
|
||||
""")
|
||||
last_hour = c.fetchone()[0]
|
||||
|
||||
# Reality Latency calculation
|
||||
c.execute("""
|
||||
SELECT AVG(
|
||||
julianday('now') - julianday(detected_date)
|
||||
) * 24 as avg_latency_hours
|
||||
FROM observations
|
||||
WHERE detected_date >= date('now', '-7 days')
|
||||
""")
|
||||
avg_latency = c.fetchone()[0] or 0
|
||||
|
||||
conn.close()
|
||||
|
||||
return {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"system_status": "operational",
|
||||
"data_freshness": {
|
||||
"total_roads": total_roads,
|
||||
"total_observations": total_obs,
|
||||
"observations_today": today_obs,
|
||||
"observations_last_hour": last_hour,
|
||||
"reality_latency_hours": round(avg_latency, 1)
|
||||
},
|
||||
"sources": sources,
|
||||
"pipeline_status": {
|
||||
"trafikverket": "active",
|
||||
"smhi": "active",
|
||||
"quixzoom": "active",
|
||||
"satellite": "active"
|
||||
}
|
||||
}
|
||||
|
||||
@router.get("/public/observations/stream")
|
||||
async def get_observation_stream(limit: int = 50):
|
||||
"""Get recent observations as a stream"""
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
|
||||
c.execute("""
|
||||
SELECT o.*, r.name as road_name, r.county, r.road_number
|
||||
FROM observations o
|
||||
JOIN roads r ON o.road_id = r.id
|
||||
ORDER BY o.created_at DESC
|
||||
LIMIT ?
|
||||
""", (limit,))
|
||||
|
||||
observations = [dict(row) for row in c.fetchall()]
|
||||
conn.close()
|
||||
|
||||
return {
|
||||
"count": len(observations),
|
||||
"observations": observations
|
||||
}
|
||||
|
||||
@router.get("/public/alerts")
|
||||
async def get_active_alerts():
|
||||
"""Get active alerts (critical/high severity)"""
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
|
||||
c.execute("""
|
||||
SELECT o.*, r.name as road_name, r.county
|
||||
FROM observations o
|
||||
JOIN roads r ON o.road_id = r.id
|
||||
WHERE o.severity IN ('critical', 'high')
|
||||
AND o.detected_date >= date('now', '-7 days')
|
||||
ORDER BY
|
||||
CASE o.severity
|
||||
WHEN 'critical' THEN 1
|
||||
WHEN 'high' THEN 2
|
||||
ELSE 3
|
||||
END,
|
||||
o.detected_date DESC
|
||||
""")
|
||||
|
||||
alerts = [dict(row) for row in c.fetchall()]
|
||||
conn.close()
|
||||
|
||||
return {
|
||||
"alert_count": len(alerts),
|
||||
"critical_count": sum(1 for a in alerts if a.get('severity') == 'critical'),
|
||||
"high_count": sum(1 for a in alerts if a.get('severity') == 'high'),
|
||||
"alerts": alerts
|
||||
}
|
||||
|
||||
@router.get("/public/coverage")
|
||||
async def get_coverage_map():
|
||||
"""Get coverage data for map visualization"""
|
||||
conn = get_db()
|
||||
c = conn.cursor()
|
||||
|
||||
# Get all observations with coordinates
|
||||
c.execute("""
|
||||
SELECT o.latitude, o.longitude, o.observation_type, o.severity, o.confidence,
|
||||
r.name as road_name, r.county
|
||||
FROM observations o
|
||||
JOIN roads r ON o.road_id = r.id
|
||||
WHERE o.latitude != 0 AND o.longitude != 0
|
||||
ORDER BY o.detected_date DESC
|
||||
LIMIT 500
|
||||
""")
|
||||
|
||||
points = []
|
||||
for row in c.fetchall():
|
||||
points.append({
|
||||
"lat": row[0],
|
||||
"lon": row[1],
|
||||
"type": row[2],
|
||||
"severity": row[3],
|
||||
"confidence": row[4],
|
||||
"road": row[5],
|
||||
"county": row[6]
|
||||
})
|
||||
|
||||
# Coverage by county
|
||||
c.execute("""
|
||||
SELECT r.county,
|
||||
COUNT(DISTINCT r.id) as roads,
|
||||
COUNT(o.id) as observations,
|
||||
AVG(o.confidence) as avg_confidence
|
||||
FROM roads r
|
||||
LEFT JOIN observations o ON r.id = o.road_id
|
||||
GROUP BY r.county
|
||||
ORDER BY observations DESC
|
||||
""")
|
||||
|
||||
coverage = []
|
||||
for row in c.fetchall():
|
||||
coverage.append({
|
||||
"county": row[0],
|
||||
"roads": row[1],
|
||||
"observations": row[2],
|
||||
"avg_confidence": round(row[3] or 0, 2)
|
||||
})
|
||||
|
||||
conn.close()
|
||||
|
||||
return {
|
||||
"total_points": len(points),
|
||||
"points": points,
|
||||
"coverage_by_county": coverage
|
||||
}
|
||||
Reference in New Issue
Block a user