From 039c9a3f9292f77a634808b67ede4c463105c04f Mon Sep 17 00:00:00 2001 From: mphele Date: Wed, 6 May 2026 01:12:20 +0200 Subject: [PATCH 1/3] feat(models): implement SignalMatrix and nested Pydantic schemas - Created strict Pydantic models for incoming Go telemetry and Nokia signals. - Enforces snake_case and deep validation on optional Nokia API payloads. Addresses Issue 1 --- app/models/signal_matrix.py | 79 +++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/app/models/signal_matrix.py b/app/models/signal_matrix.py index e69de29..5e1bd1b 100644 --- a/app/models/signal_matrix.py +++ b/app/models/signal_matrix.py @@ -0,0 +1,79 @@ +from pydantic import BaseModel +from typing import List, Optional + +class Telemetry(BaseModel): + accel_mag : int + rssi_dbm : int + rat: int + cell_id: int + lac : int + bat_mv: int + body_temp_c: float + flags: int + + +class DeviceLocation(BaseModel): + lat: float + lon:float + uncertainty_m: int + fetched_at: str + +class DeviceStatus(BaseModel): + reachability: str + last_seen_at: str + +class SimSwap(BaseModel): + swapped: bool + last_swap_at: Optional[str] + +class Connectivity(BaseModel): + rsrp_dbm: int + throughput_kbps: int + latency_ms: int + connection_type : str + +class Roaming(BaseModel): + is_roaming: bool + visited_plmn: Optional[str] + +class Congestion(BaseModel): + cell_load_pct:int + affected_cells: List[str] + estimated_duration_s: int + +class NokiaSignals(BaseModel): + device_location: Optional[DeviceLocation] + device_status:Optional[DeviceStatus] + sim_swap:Optional[SimSwap] + connectivity:Optional[Connectivity] + roaming:Optional[Roaming] + congestion:Optional[Congestion] + qod_active:bool + slicing_active:bool + +class Baseline(BaseModel): + avg_accel_mag: float + std_accel_mag:float + daily_range_m:float + avg_temp_c:float + avg_rssi_dbm:float + typical_cell_ids:List[int] + hourly_activity:List[float] + +class Context(BaseModel): + hour_utc:int + is_dry_season:bool + market_day:bool + minutes_since_geofence_departure: Optional[int] + +class SignalMatrix(BaseModel): + request_id:str + tag_id:str + animal_id:str + farm_id:str + timestamp:str + telemetry: Telemetry + nokia_signals:NokiaSignals + baseline:Baseline + context:Context + From c325b7fdef3ed7606b46d0abb1233da4457f7466 Mon Sep 17 00:00:00 2001 From: mphele Date: Wed, 6 May 2026 01:12:31 +0200 Subject: [PATCH 2/3] feat(models): implement ScoredEvent Pydantic schema for Go contract - Created the standardized output schema for the decision engine. Addresses Issue 1 --- app/models/scored_event.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/app/models/scored_event.py b/app/models/scored_event.py index e69de29..4428ed5 100644 --- a/app/models/scored_event.py +++ b/app/models/scored_event.py @@ -0,0 +1,25 @@ +from pydantic import BaseModel +from typing import List, Optional + +class ScoredEvent(BaseModel): + request_id: str + + event_type: str + + confidence: float + + signals_fired: List[str] + + suppressed: bool + + suppression_reason: Optional[str] + + alert_channels: List[str] + + message_template: str + + gemini_narrative:Optional[str] + + scored_at: str + + model_version: str \ No newline at end of file From 694f460601c0968b4b515fb1c9ebec477cc4b056 Mon Sep 17 00:00:00 2001 From: mphele Date: Wed, 6 May 2026 01:32:46 +0200 Subject: [PATCH 3/3] feat(api): scaffold FastAPI application and score endpoint - Initialized FastAPI with /health and /score routes. - Wired SignalMatrix input to ScoredEvent output with safe dummy returns. Resolves Issue 1 --- app/main.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/app/main.py b/app/main.py index e69de29..49f9f2c 100644 --- a/app/main.py +++ b/app/main.py @@ -0,0 +1,28 @@ +from fastapi import FastAPI +from app.models.signal_matrix import SignalMatrix +from app.models.scored_event import ScoredEvent +from datetime import datetime + +app = FastAPI(title="Uwatu Intelligence Layer") + +@app.get("/health") +async def health_check(): + return {"status": "ok", "service": "uwatu-intelligence"} + +@app.post("/score", response_model=ScoredEvent) +async def score_event(matrix: SignalMatrix): + return ScoredEvent( + request_id=matrix.request_id, + event_type="NORMAL_VARIATION", + confidence=0.99, + signals_fired=[], + suppressed=False, + suppression_reason=None, + alert_channels=[], + message_template="none", + gemini_narrative=None, + scored_at=datetime.utcnow().isoformat() + "Z", + model_version="1.0.0-draft" + ) + +