237 lines
8.0 KiB
Python
237 lines
8.0 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""
|
||
|
|
Weather Intelligence Pipeline v2
|
||
|
|
Road Binding → Risk Profiles → Event Generation → Mission Engine
|
||
|
|
"""
|
||
|
|
import sys
|
||
|
|
import json
|
||
|
|
import logging
|
||
|
|
from datetime import datetime
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
sys.path.insert(0, '/home/bernt/.openclaw/workspace/life-weather')
|
||
|
|
|
||
|
|
from providers.weather_provider import WeatherProvider
|
||
|
|
from storage.observation_store import ObservationStore
|
||
|
|
from engine.risk_engine import RiskEngine
|
||
|
|
|
||
|
|
LOG_DIR = Path("/home/bernt/.openclaw/workspace/life-weather/logs")
|
||
|
|
LOG_DIR.mkdir(exist_ok=True)
|
||
|
|
|
||
|
|
logging.basicConfig(
|
||
|
|
level=logging.INFO,
|
||
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||
|
|
handlers=[
|
||
|
|
logging.FileHandler(LOG_DIR / "pipeline_v2.log"),
|
||
|
|
logging.StreamHandler()
|
||
|
|
]
|
||
|
|
)
|
||
|
|
logger = logging.getLogger(__name__)
|
||
|
|
|
||
|
|
class RoadBinding:
|
||
|
|
"""Steg 1: Koppla väg till väderobservation"""
|
||
|
|
|
||
|
|
def __init__(self, store):
|
||
|
|
self.store = store
|
||
|
|
|
||
|
|
def bind_road(self, road_id, weather_data):
|
||
|
|
"""Koppla väg till väderobservation"""
|
||
|
|
logger.info(f"Road {road_id}: Binding to weather observation")
|
||
|
|
|
||
|
|
observations = []
|
||
|
|
|
||
|
|
if weather_data.get("temperature"):
|
||
|
|
obs_id = self.store.save_observation(
|
||
|
|
road_id=road_id,
|
||
|
|
observation_type="temperature",
|
||
|
|
value=weather_data["temperature"]["value"],
|
||
|
|
unit="celsius",
|
||
|
|
timestamp=weather_data["timestamp"],
|
||
|
|
raw_data=weather_data["temperature"]
|
||
|
|
)
|
||
|
|
if obs_id:
|
||
|
|
observations.append(obs_id)
|
||
|
|
|
||
|
|
if weather_data.get("precipitation"):
|
||
|
|
obs_id = self.store.save_observation(
|
||
|
|
road_id=road_id,
|
||
|
|
observation_type="precipitation",
|
||
|
|
value=weather_data["precipitation"]["value"],
|
||
|
|
unit="mm",
|
||
|
|
timestamp=weather_data["timestamp"],
|
||
|
|
raw_data=weather_data["precipitation"]
|
||
|
|
)
|
||
|
|
if obs_id:
|
||
|
|
observations.append(obs_id)
|
||
|
|
|
||
|
|
if weather_data.get("wind"):
|
||
|
|
obs_id = self.store.save_observation(
|
||
|
|
road_id=road_id,
|
||
|
|
observation_type="wind",
|
||
|
|
value=weather_data["wind"]["value"],
|
||
|
|
unit="m/s",
|
||
|
|
timestamp=weather_data["timestamp"],
|
||
|
|
raw_data=weather_data["wind"]
|
||
|
|
)
|
||
|
|
if obs_id:
|
||
|
|
observations.append(obs_id)
|
||
|
|
|
||
|
|
return {
|
||
|
|
"road_id": road_id,
|
||
|
|
"observations": observations,
|
||
|
|
"timestamp": weather_data["timestamp"]
|
||
|
|
}
|
||
|
|
|
||
|
|
class RiskProfile:
|
||
|
|
"""Steg 2: Skapa riskprofil för väg"""
|
||
|
|
|
||
|
|
def __init__(self, engine):
|
||
|
|
self.engine = engine
|
||
|
|
|
||
|
|
def create_profile(self, road_id, road_type, weather_data):
|
||
|
|
"""Skapa riskprofil"""
|
||
|
|
logger.info(f"Road {road_id}: Creating risk profile")
|
||
|
|
|
||
|
|
temp = weather_data.get("temperature", {}).get("value") if weather_data.get("temperature") else None
|
||
|
|
precip = weather_data.get("precipitation", {}).get("value") if weather_data.get("precipitation") else None
|
||
|
|
wind = weather_data.get("wind", {}).get("value") if weather_data.get("wind") else None
|
||
|
|
|
||
|
|
risks = self.engine.calculate_risk(road_id, road_type, temp, precip, wind)
|
||
|
|
|
||
|
|
profile = {
|
||
|
|
"road_id": road_id,
|
||
|
|
"road_type": road_type,
|
||
|
|
"timestamp": weather_data["timestamp"],
|
||
|
|
"weather": {
|
||
|
|
"temperature": temp,
|
||
|
|
"precipitation": precip,
|
||
|
|
"wind": wind
|
||
|
|
},
|
||
|
|
"risks": [
|
||
|
|
{
|
||
|
|
"type": r.risk_type,
|
||
|
|
"severity": r.severity,
|
||
|
|
"score": r.score,
|
||
|
|
"description": r.description
|
||
|
|
}
|
||
|
|
for r in risks
|
||
|
|
],
|
||
|
|
"risk_count": len(risks),
|
||
|
|
"max_severity": max([r.severity for r in risks], key=lambda x: {"low": 1, "medium": 2, "high": 3, "critical": 4}.get(x, 0)) if risks else "none"
|
||
|
|
}
|
||
|
|
|
||
|
|
return profile
|
||
|
|
|
||
|
|
class EventGenerator:
|
||
|
|
"""Steg 3: Generera händelser från risker"""
|
||
|
|
|
||
|
|
def __init__(self):
|
||
|
|
self.events = []
|
||
|
|
|
||
|
|
def generate_events(self, risk_profile):
|
||
|
|
"""Generera händelser från riskprofil"""
|
||
|
|
road_id = risk_profile["road_id"]
|
||
|
|
risks = risk_profile["risks"]
|
||
|
|
|
||
|
|
events = []
|
||
|
|
for risk in risks:
|
||
|
|
if risk["severity"] in ["high", "critical"]:
|
||
|
|
event = {
|
||
|
|
"event_id": f"evt_{road_id}_{risk['type']}_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
|
||
|
|
"road_id": road_id,
|
||
|
|
"event_type": risk["type"],
|
||
|
|
"severity": risk["severity"],
|
||
|
|
"description": risk["description"],
|
||
|
|
"timestamp": risk_profile["timestamp"],
|
||
|
|
"status": "active",
|
||
|
|
"requires_verification": True
|
||
|
|
}
|
||
|
|
events.append(event)
|
||
|
|
logger.info(f"Road {road_id}: Generated event {event['event_id']}")
|
||
|
|
|
||
|
|
return events
|
||
|
|
|
||
|
|
class MissionEngine:
|
||
|
|
"""Steg 4: Generera missions från events"""
|
||
|
|
|
||
|
|
def __init__(self):
|
||
|
|
self.missions = []
|
||
|
|
|
||
|
|
def generate_missions(self, events):
|
||
|
|
"""Generera missions från events"""
|
||
|
|
missions = []
|
||
|
|
|
||
|
|
for event in events:
|
||
|
|
if event["requires_verification"]:
|
||
|
|
mission = {
|
||
|
|
"mission_id": f"mission_{event['event_id']}",
|
||
|
|
"event_id": event["event_id"],
|
||
|
|
"road_id": event["road_id"],
|
||
|
|
"mission_type": "verification",
|
||
|
|
"description": f"Verify {event['event_type']} on road {event['road_id']}",
|
||
|
|
"priority": event["severity"],
|
||
|
|
"status": "pending",
|
||
|
|
"created_at": event["timestamp"],
|
||
|
|
"target": "quixzoom" # Skicka till quiXzoom
|
||
|
|
}
|
||
|
|
missions.append(mission)
|
||
|
|
logger.info(f"Road {event['road_id']}: Generated mission {mission['mission_id']}")
|
||
|
|
|
||
|
|
return missions
|
||
|
|
|
||
|
|
class WeatherPipelineV2:
|
||
|
|
"""Huvudpipeline"""
|
||
|
|
|
||
|
|
def __init__(self):
|
||
|
|
self.provider = WeatherProvider()
|
||
|
|
self.store = ObservationStore()
|
||
|
|
self.engine = RiskEngine()
|
||
|
|
|
||
|
|
self.road_binding = RoadBinding(self.store)
|
||
|
|
self.risk_profile = RiskProfile(self.engine)
|
||
|
|
self.event_generator = EventGenerator()
|
||
|
|
self.mission_engine = MissionEngine()
|
||
|
|
|
||
|
|
def run_for_road(self, road_id, road_type="primary"):
|
||
|
|
"""Kör pipeline för en väg"""
|
||
|
|
logger.info(f"=== Pipeline for Road {road_id} ===")
|
||
|
|
|
||
|
|
# Steg 0: Hämta väderdata
|
||
|
|
weather = self.provider.get_current_weather()
|
||
|
|
if not weather.get("temperature"):
|
||
|
|
logger.error("No weather data available")
|
||
|
|
return None
|
||
|
|
|
||
|
|
# Steg 1: Road Binding
|
||
|
|
binding = self.road_binding.bind_road(road_id, weather)
|
||
|
|
|
||
|
|
# Steg 2: Risk Profile
|
||
|
|
profile = self.risk_profile.create_profile(road_id, road_type, weather)
|
||
|
|
|
||
|
|
# Steg 3: Event Generation
|
||
|
|
events = self.event_generator.generate_events(profile)
|
||
|
|
|
||
|
|
# Steg 4: Mission Engine
|
||
|
|
missions = self.mission_engine.generate_missions(events)
|
||
|
|
|
||
|
|
result = {
|
||
|
|
"road_id": road_id,
|
||
|
|
"timestamp": weather["timestamp"],
|
||
|
|
"binding": binding,
|
||
|
|
"profile": profile,
|
||
|
|
"events": events,
|
||
|
|
"missions": missions
|
||
|
|
}
|
||
|
|
|
||
|
|
logger.info(f"Road {road_id}: Pipeline complete - {len(events)} events, {len(missions)} missions")
|
||
|
|
return result
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
pipeline = WeatherPipelineV2()
|
||
|
|
|
||
|
|
# Kör för första vägen
|
||
|
|
result = pipeline.run_for_road(road_id=1, road_type="primary")
|
||
|
|
|
||
|
|
if result:
|
||
|
|
print(json.dumps(result, indent=2))
|