Emeritus-21's picture
Update app/main.py
2d70700 verified
Raw
History Blame Contribute Delete
8.06 kB
"""DeltaMind FastAPI Application - Main Entry Point."""
import logging, json, asyncio
from pathlib import Path
from typing import Optional
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Query
from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from datetime import datetime
from app.core.config import settings, ensure_directories
from app.core.database import (init_database, init_chromadb, seed_knowledge_base,
get_recent_alerts, get_dashboard_stats, get_production_data, get_chat_history, insert_alert)
from app.llm_router import llm_router
from app.rag_engine import rag_engine
from app.data_sources.firms import firms_client
from app.data_sources.nosdra import nosdra_client
from app.data_sources.weather import weather_client
from app.data_sources.satellite import satellite_client
from app.models.anomaly import anomaly_detector
from app.models.predictive import predictive_model
from app.reports.generator import report_generator
from app.seed_data import seed_production
from app.services.notifications import send_critical_alert_email
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(name)s] %(levelname)s: %(message)s")
logger = logging.getLogger("deltamind")
app = FastAPI(title="DeltaMind", version="3.0.0",
description="AI-Powered Operational Intelligence - Renaissance Africa Energy")
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
scheduler = AsyncIOScheduler()
class ChatRequest(BaseModel):
query: str
session_id: Optional[str] = None
class AnomalyRequest(BaseModel):
oil_rate: float
pressure: float
water_cut: float
temperature: float
class PredictRequest(BaseModel):
id: str = "EQ-001"
type: str = "ESP"
vibration: float = 0.3
temperature: float = 70
runtime_hours: int = 4000
pressure: float = 150
class ReportRequest(BaseModel):
report_type: str = "daily"
partner: Optional[str] = None
agency: Optional[str] = None
@app.on_event("startup")
async def startup():
ensure_directories()
init_database()
init_chromadb()
seed_knowledge_base()
scheduler.add_job(firms_client.summary, 'interval', hours=6)
scheduler.add_job(weather_client.all_locations, 'interval', hours=1)
scheduler.start()
logger.info("DeltaMind started with background schedulers active")
frontend_path = Path(__file__).parent.parent / "frontend"
if frontend_path.exists():
app.mount("/static", StaticFiles(directory=str(frontend_path)), name="static")
@app.get("/", response_class=HTMLResponse)
async def root():
idx = frontend_path / "index.html"
if idx.exists():
# FIXED: Added encoding="utf-8" to prevent Windows charmap decoding errors
return HTMLResponse(idx.read_text(encoding="utf-8"))
return HTMLResponse("<h1>DeltaMind API</h1><p>Frontend not found.</p>")
@app.get("/api/health")
async def health():
return {"status": "ok", "version": settings.app_version,
"llm": llm_router.status(), "timestamp": datetime.now().isoformat()}
@app.get("/api/llm/status")
async def llm_status():
return llm_router.status()
@app.get("/api/dashboard")
async def dashboard():
stats = get_dashboard_stats()
flares_summary = firms_client.summary()
spill_summary = nosdra_client.summary()
corridors = satellite_client.corridor_status()
fleet = predictive_model.fleet_health()
weather = weather_client.all_locations()
return {"stats": stats, "flares": flares_summary, "spills": spill_summary,
"corridors": corridors, "fleet": fleet, "weather": weather}
@app.get("/api/alerts")
async def alerts(limit: int = 50):
return {"alerts": get_recent_alerts(limit)}
@app.get("/api/flares")
async def flares():
df = firms_client.get_fires(3)
processed = firms_client.process(df)
firms_client.store(processed)
return {"flares": processed, "count": len(processed)}
@app.get("/api/spills")
async def spills():
return {"spills": nosdra_client.get_spills()}
@app.get("/api/weather")
async def weather():
data = weather_client.all_locations()
risks = [weather_client.risk(w) for w in data]
return {"weather": data, "risks": risks}
@app.get("/api/satellite/corridors")
async def sat_corridors():
return {"corridors": satellite_client.corridor_status()}
@app.get("/api/production")
async def production(oml_id: Optional[str] = None, limit: int = 200):
return {"data": get_production_data(oml_id, limit)}
@app.post("/api/ai/chat")
async def chat(req: ChatRequest):
return await rag_engine.chat(req.query, req.session_id)
@app.get("/api/ai/chat/history")
async def chat_history(session_id: str):
return {"messages": get_chat_history(session_id)}
@app.post("/api/ai/anomaly")
async def detect_anomaly(req: AnomalyRequest):
return anomaly_detector.detect(req.dict())
@app.post("/api/ai/leak")
async def detect_leak(flow_in: float = Query(...), flow_out: float = Query(...), pressure_drop: float = Query(0)):
return anomaly_detector.pipeline_leak_detection(flow_in, flow_out, pressure_drop)
@app.post("/api/ai/predict")
async def predict_maintenance(req: PredictRequest):
return predictive_model.predict(req.dict())
@app.get("/api/ai/fleet")
async def fleet_health():
return predictive_model.fleet_health()
@app.post("/api/reports/generate")
async def generate_report(req: ReportRequest):
if req.report_type == "daily":
return await report_generator.daily_production()
elif req.report_type == "partner":
return await report_generator.partner_report(req.partner or "NNPC")
elif req.report_type == "regulatory":
return await report_generator.regulatory_filing(req.agency or "NUPRC")
return {"error": "Unknown report type"}
@app.post("/api/seed")
async def seed():
count = seed_production(30)
return {"seeded": count, "message": f"Seeded {count} production records"}
@app.post("/api/test-alert")
async def test_critical_alert():
"""Simulate a critical alert to demonstrate push notifications and AI email."""
alert_data = {
"alert_type": "security",
"severity": "critical",
"title": "Pipeline Pressure Drop",
"message": "SCADA detected a 15% pressure drop in Segment 4. Possible vandalism or leak.",
"source": "SCADA_AI",
"latitude": 5.5,
"longitude": 5.9,
"oml_id": "OML-18"
}
alert_id = insert_alert(alert_data)
alert_data["id"] = alert_id
alert_data["created_at"] = datetime.now().isoformat()
# Trigger AI Email Dispatch
await send_critical_alert_email(alert_data)
# Broadcast via WebSocket to all connected dashboards
await manager.broadcast({
"type": "critical_alert",
"data": alert_data
})
return {"status": "success", "message": "Alert created, email drafted, and push notification sent."}
class ConnectionManager:
def __init__(self):
self.active = []
async def connect(self, ws: WebSocket):
await ws.accept()
self.active.append(ws)
def disconnect(self, ws: WebSocket):
if ws in self.active: self.active.remove(ws)
async def broadcast(self, msg: dict):
for ws in self.active:
try: await ws.send_json(msg)
except: pass
manager = ConnectionManager()
@app.websocket("/ws/alerts")
async def ws_alerts(ws: WebSocket):
await manager.connect(ws)
try:
while True:
await asyncio.sleep(30)
await ws.send_json({"type": "ping", "message": "DeltaMind connection active"})
except WebSocketDisconnect:
manager.disconnect(ws)