Files
boc/iom/observation/observation_models.py
T
Bernt bae705aa97 ARCHITECTURE: NFC roadmap, edge AI, audit logging
- Add NFC ePassport roadmap (ICAO 9303, eIDAS)
- Add TensorFlow.js edge face detection (BlazeFace)
- Add structured audit logger (GDPR-compliant)
- Risk scoring support

Part of KYC Apple Native UX v1.1.0
2026-06-29 16:24:48 +00:00

278 lines
9.0 KiB
Python

"""
Observation Models - Layer 6 of IOM
Pydantic models for observations, findings, and media
"""
from datetime import datetime
from typing import List, Optional, Dict, Any
from enum import Enum
from pydantic import BaseModel, Field
class MediaType(str, Enum):
"""Types of media attachments"""
IMAGE = "image"
VIDEO = "video"
DEPTH_MAP = "depth_map"
THERMAL = "thermal"
LIDAR = "lidar"
class FindingType(str, Enum):
"""Types of findings/observations"""
DIRT_ACCUMULATION = "dirt_accumulation"
COLOR_CHANGE = "color_change"
SURFACE_DAMAGE = "surface_damage"
GRAFFITI = "graffiti"
CRACK = "crack"
DEFORMATION = "deformation"
MATERIAL_LOSS = "material_loss"
MISSING_PART = "missing_part"
BROKEN_PART = "broken_part"
LOOSE_PART = "loose_part"
PHYSICAL_BLOCK = "physical_block"
VEGETATION = "vegetation"
WATER_DAMAGE = "water_damage"
ICE_DAMAGE = "ice_damage"
CORROSION = "corrosion"
OTHER = "other"
class GeoTag(BaseModel):
"""Geographic location tag"""
lat: float = Field(..., ge=-90, le=90, description="Latitude")
lng: float = Field(..., ge=-180, le=180, description="Longitude")
elevation: Optional[float] = Field(None, description="Elevation in meters")
accuracy: Optional[float] = Field(None, description="GPS accuracy in meters")
class Config:
json_schema_extra = {
"example": {
"lat": 59.3293,
"lng": 18.0686,
"elevation": 12.5,
"accuracy": 3.2
}
}
class Media(BaseModel):
"""Media attachment (image, video, etc.)"""
type: MediaType
url: str = Field(..., description="URL to media file")
timestamp: datetime = Field(default_factory=datetime.utcnow)
geotag: Optional[GeoTag] = None
class Config:
json_schema_extra = {
"example": {
"type": "image",
"url": "https://storage.quixzoom.com/obs/img_2847.jpg",
"timestamp": "2026-06-26T09:15:03Z",
"geotag": {
"lat": 59.3293,
"lng": 18.0686,
"accuracy": 2.1
}
}
}
class Finding(BaseModel):
"""Individual finding within an observation"""
type: FindingType
code: str = Field(..., pattern=r"^[0-9]{4}$", description="Defect code from Layer 7")
description: str = Field(..., min_length=1, max_length=500)
measurement: Optional[str] = Field(None, description="Measurement (e.g., '3 mm', '15% coverage')")
location: Optional[str] = Field(None, description="Location on object (e.g., 'northeast_corner')")
confidence: float = Field(..., ge=0.0, le=1.0, description="AI confidence score")
class Config:
json_schema_extra = {
"example": {
"type": "dirt_accumulation",
"code": "2100",
"description": "Smuts på fönster",
"measurement": "30% coverage",
"confidence": 0.94
}
}
class AIAnalysis(BaseModel):
"""AI-generated analysis of observation"""
model: str = Field(..., description="AI model version")
overall_condition: int = Field(..., ge=1, le=5, description="Overall condition 1-5")
recommended_action: Optional[str] = Field(None, description="Recommended action")
next_observation_due: Optional[datetime] = None
class Config:
json_schema_extra = {
"example": {
"model": "infrastructure-v3.2",
"overall_condition": 3,
"recommended_action": "schedule_cleaning",
"next_observation_due": "2026-09-26"
}
}
class Weather(BaseModel):
"""Weather conditions during observation"""
temperature: Optional[float] = Field(None, description="Temperature in Celsius")
conditions: Optional[str] = Field(None, description="Weather conditions")
wind_speed: Optional[float] = Field(None, description="Wind speed in m/s")
precipitation: Optional[str] = Field(None, description="Precipitation type")
class Config:
json_schema_extra = {
"example": {
"temperature": 22,
"conditions": "sunny",
"wind_speed": 3.5
}
}
class Observation(BaseModel):
"""
Main observation model - Layer 6 of IOM
Core principle: The object never changes. Only observations change.
"""
id: Optional[str] = Field(None, pattern=r"^OBS-[0-9]{4}-[0-9]{7}$")
timestamp: datetime = Field(default_factory=datetime.utcnow)
object_goid: str = Field(..., pattern=r"^[A-Z]{3}(-[A-Z]{3})+-[0-9]+$", description="GOID of observed object")
observer: str = Field(..., description="Observer ID (zoomer:id or sensor:id)")
# Findings
findings: List[Finding] = Field(default_factory=list)
# Media
media: List[Media] = Field(default_factory=list)
# Context
weather: Optional[Weather] = None
# AI Analysis
ai_analysis: Optional[AIAnalysis] = None
# Metadata
created_at: datetime = Field(default_factory=datetime.utcnow)
updated_at: Optional[datetime] = None
class Config:
json_schema_extra = {
"example": {
"id": "OBS-2026-0012847",
"timestamp": "2026-06-26T09:15:00Z",
"object_goid": "BYG-FAC-WIN-GLA-2847",
"observer": "zoomer:anna_k",
"findings": [
{
"type": "dirt_accumulation",
"code": "2100",
"description": "Smuts på fönster",
"measurement": "30% coverage",
"confidence": 0.94
}
],
"media": [
{
"type": "image",
"url": "https://storage.quixzoom.com/obs/img_2847.jpg",
"timestamp": "2026-06-26T09:15:03Z",
"geotag": {
"lat": 59.3293,
"lng": 18.0686,
"accuracy": 2.1
}
}
],
"ai_analysis": {
"model": "infrastructure-v3.2",
"overall_condition": 3,
"recommended_action": "schedule_cleaning"
}
}
}
class ObservationSummary(BaseModel):
"""Summary of observations for an object"""
object_goid: str
observation_count: int
latest_condition: Optional[int] = None
latest_observation_date: Optional[datetime] = None
finding_types: List[str] = Field(default_factory=list)
risk_trend: Optional[str] = None # "improving", "stable", "degrading"
class Config:
json_schema_extra = {
"example": {
"object_goid": "BYG-FAC-WIN-GLA-2847",
"observation_count": 5,
"latest_condition": 3,
"latest_observation_date": "2026-06-26T09:15:00Z",
"finding_types": ["dirt_accumulation", "surface_damage"],
"risk_trend": "stable"
}
}
class ObservationTrend(BaseModel):
"""Trend analysis for an object over time"""
object_goid: str
period_months: int
data_points: List[Dict[str, Any]]
class Config:
json_schema_extra = {
"example": {
"object_goid": "BYG-FAC-WIN-GLA-2847",
"period_months": 6,
"data_points": [
{"month": "2026-01", "condition": 2, "observations": 1},
{"month": "2026-02", "condition": 2, "observations": 0},
{"month": "2026-03", "condition": 3, "observations": 1},
{"month": "2026-04", "condition": 3, "observations": 0},
{"month": "2026-05", "condition": 3, "observations": 1},
{"month": "2026-06", "condition": 3, "observations": 2}
]
}
}
if __name__ == '__main__':
# Example usage
obs = Observation(
id="OBS-2026-0012847",
timestamp=datetime(2026, 6, 26, 9, 15, 0),
object_goid="BYG-FAC-WIN-GLA-2847",
observer="zoomer:anna_k",
findings=[
Finding(
type=FindingType.DIRT_ACCUMULATION,
code="2100",
description="Smuts på fönster",
measurement="30% coverage",
confidence=0.94
)
],
media=[
Media(
type=MediaType.IMAGE,
url="https://storage.quixzoom.com/obs/img_2847.jpg",
geotag=GeoTag(lat=59.3293, lng=18.0686, accuracy=2.1)
)
],
ai_analysis=AIAnalysis(
model="infrastructure-v3.2",
overall_condition=3,
recommended_action="schedule_cleaning"
)
)
print("Observation created:")
print(obs.model_dump_json(indent=2))