bae705aa97
- 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
278 lines
9.0 KiB
Python
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))
|