feat: Add presence heartbeat for Matrix online status
- matrix-gateway: POST /internal/matrix/presence/online endpoint - usePresenceHeartbeat hook with activity tracking - Auto away after 5 min inactivity - Offline on page close/visibility change - Integrated in MatrixChatRoom component
This commit is contained in:
247
services/living-map-service/models.py
Normal file
247
services/living-map-service/models.py
Normal file
@@ -0,0 +1,247 @@
|
||||
"""
|
||||
Living Map Service Models
|
||||
Phase 9: Full Stack Living Map
|
||||
"""
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, List, Dict, Any, Literal
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
# ============================================================================
|
||||
# Layer Types
|
||||
# ============================================================================
|
||||
|
||||
LayerType = Literal["city", "space", "nodes", "agents"]
|
||||
EntityType = Literal["city", "space", "node", "agent", "dao", "microdao", "channel"]
|
||||
EntityStatus = Literal["active", "inactive", "online", "offline", "warning", "error", "idle"]
|
||||
|
||||
# ============================================================================
|
||||
# City Layer Models
|
||||
# ============================================================================
|
||||
|
||||
class CityItem(BaseModel):
|
||||
"""MicroDAO in City layer"""
|
||||
id: str
|
||||
slug: str
|
||||
name: str
|
||||
status: EntityStatus
|
||||
agents: int = 0
|
||||
nodes: int = 0
|
||||
members: int = 0
|
||||
description: Optional[str] = None
|
||||
|
||||
class CityLayer(BaseModel):
|
||||
"""City layer data"""
|
||||
microdaos_total: int = 0
|
||||
active_users: int = 0
|
||||
active_agents: int = 0
|
||||
health: str = "green"
|
||||
items: List[CityItem] = []
|
||||
|
||||
# ============================================================================
|
||||
# Space Layer Models
|
||||
# ============================================================================
|
||||
|
||||
class PlanetItem(BaseModel):
|
||||
"""DAO planet in Space layer"""
|
||||
id: str
|
||||
name: str
|
||||
type: str = "dao"
|
||||
status: EntityStatus
|
||||
orbits: List[str] = [] # node IDs
|
||||
treasury_value: Optional[Decimal] = None
|
||||
active_proposals: int = 0
|
||||
|
||||
class NodeInSpace(BaseModel):
|
||||
"""Node in Space layer (orbital view)"""
|
||||
id: str
|
||||
name: str
|
||||
status: EntityStatus
|
||||
cpu: float = 0.0
|
||||
gpu: float = 0.0
|
||||
memory: float = 0.0
|
||||
alerts: List[str] = []
|
||||
|
||||
class SpaceLayer(BaseModel):
|
||||
"""Space layer data"""
|
||||
planets: List[PlanetItem] = []
|
||||
nodes: List[NodeInSpace] = []
|
||||
|
||||
# ============================================================================
|
||||
# Nodes Layer Models
|
||||
# ============================================================================
|
||||
|
||||
class NodeMetrics(BaseModel):
|
||||
"""Node hardware metrics"""
|
||||
cpu: float = 0.0
|
||||
gpu: float = 0.0
|
||||
ram: float = 0.0
|
||||
disk: float = 0.0
|
||||
net_in: int = 0
|
||||
net_out: int = 0
|
||||
temperature: Optional[float] = None
|
||||
|
||||
class NodeItem(BaseModel):
|
||||
"""Node in Nodes layer"""
|
||||
id: str
|
||||
name: str
|
||||
microdao_id: Optional[str] = None
|
||||
status: EntityStatus
|
||||
metrics: NodeMetrics
|
||||
alerts: List[str] = []
|
||||
uptime_seconds: Optional[int] = None
|
||||
last_seen: Optional[datetime] = None
|
||||
|
||||
class NodesLayer(BaseModel):
|
||||
"""Nodes layer data"""
|
||||
items: List[NodeItem] = []
|
||||
total_cpu: float = 0.0
|
||||
total_gpu: float = 0.0
|
||||
total_ram: float = 0.0
|
||||
|
||||
# ============================================================================
|
||||
# Agents Layer Models
|
||||
# ============================================================================
|
||||
|
||||
class AgentUsage(BaseModel):
|
||||
"""Agent usage statistics"""
|
||||
llm_calls_24h: int = 0
|
||||
tokens_24h: int = 0
|
||||
messages_24h: int = 0
|
||||
avg_response_time_ms: Optional[float] = None
|
||||
|
||||
class AgentItem(BaseModel):
|
||||
"""Agent in Agents layer"""
|
||||
id: str
|
||||
name: str
|
||||
kind: str
|
||||
microdao_id: Optional[str] = None
|
||||
status: EntityStatus
|
||||
usage: AgentUsage
|
||||
model: Optional[str] = None
|
||||
last_active: Optional[datetime] = None
|
||||
|
||||
class AgentsLayer(BaseModel):
|
||||
"""Agents layer data"""
|
||||
items: List[AgentItem] = []
|
||||
total_agents: int = 0
|
||||
online_agents: int = 0
|
||||
total_llm_calls_24h: int = 0
|
||||
|
||||
# ============================================================================
|
||||
# Snapshot Models
|
||||
# ============================================================================
|
||||
|
||||
class SnapshotMeta(BaseModel):
|
||||
"""Metadata for snapshot"""
|
||||
source_services: List[str] = []
|
||||
generated_at: datetime
|
||||
version: str = "1.0"
|
||||
request_id: Optional[str] = None
|
||||
|
||||
class LivingMapSnapshot(BaseModel):
|
||||
"""Complete Living Map state"""
|
||||
generated_at: datetime
|
||||
layers: Dict[str, Any] # Flexible for different layer structures
|
||||
meta: SnapshotMeta
|
||||
|
||||
class LivingMapSnapshotTyped(BaseModel):
|
||||
"""Typed version of Living Map snapshot"""
|
||||
generated_at: datetime
|
||||
layers: Dict[str, Any] = Field(default_factory=dict)
|
||||
meta: SnapshotMeta
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
# ============================================================================
|
||||
# Entity Models
|
||||
# ============================================================================
|
||||
|
||||
class EntitySummary(BaseModel):
|
||||
"""Minimal entity info for lists"""
|
||||
id: str
|
||||
type: EntityType
|
||||
label: str
|
||||
status: EntityStatus
|
||||
layer: LayerType
|
||||
|
||||
class EntityDetail(BaseModel):
|
||||
"""Detailed entity information"""
|
||||
id: str
|
||||
type: EntityType
|
||||
layer: LayerType
|
||||
data: Dict[str, Any]
|
||||
|
||||
# ============================================================================
|
||||
# History / Event Models
|
||||
# ============================================================================
|
||||
|
||||
class HistoryItem(BaseModel):
|
||||
"""Single history event"""
|
||||
id: str
|
||||
timestamp: datetime
|
||||
event_type: str
|
||||
payload: Dict[str, Any]
|
||||
source_service: Optional[str] = None
|
||||
entity_id: Optional[str] = None
|
||||
entity_type: Optional[str] = None
|
||||
|
||||
class HistoryResponse(BaseModel):
|
||||
"""History query response"""
|
||||
items: List[HistoryItem]
|
||||
total: int
|
||||
has_more: bool = False
|
||||
|
||||
# ============================================================================
|
||||
# WebSocket Models
|
||||
# ============================================================================
|
||||
|
||||
class WSMessageType(BaseModel):
|
||||
"""WebSocket message type"""
|
||||
kind: Literal["snapshot", "event", "ping", "error"]
|
||||
|
||||
class WSSnapshotMessage(BaseModel):
|
||||
"""WebSocket snapshot message"""
|
||||
kind: Literal["snapshot"] = "snapshot"
|
||||
data: LivingMapSnapshot
|
||||
|
||||
class WSEventMessage(BaseModel):
|
||||
"""WebSocket event message"""
|
||||
kind: Literal["event"] = "event"
|
||||
event_type: str
|
||||
timestamp: datetime
|
||||
payload: Dict[str, Any]
|
||||
|
||||
class WSPingMessage(BaseModel):
|
||||
"""WebSocket ping message"""
|
||||
kind: Literal["ping"] = "ping"
|
||||
timestamp: datetime
|
||||
|
||||
class WSErrorMessage(BaseModel):
|
||||
"""WebSocket error message"""
|
||||
kind: Literal["error"] = "error"
|
||||
error: str
|
||||
timestamp: datetime
|
||||
|
||||
# ============================================================================
|
||||
# Query Parameters
|
||||
# ============================================================================
|
||||
|
||||
class HistoryQueryParams(BaseModel):
|
||||
"""Query parameters for history endpoint"""
|
||||
since: Optional[datetime] = None
|
||||
until: Optional[datetime] = None
|
||||
event_type: Optional[str] = None
|
||||
entity_id: Optional[str] = None
|
||||
limit: int = Field(default=200, ge=1, le=1000)
|
||||
offset: int = Field(default=0, ge=0)
|
||||
|
||||
class EntitiesQueryParams(BaseModel):
|
||||
"""Query parameters for entities endpoint"""
|
||||
type: Optional[EntityType] = None
|
||||
layer: Optional[LayerType] = None
|
||||
status: Optional[EntityStatus] = None
|
||||
limit: int = Field(default=100, ge=1, le=500)
|
||||
offset: int = Field(default=0, ge=0)
|
||||
|
||||
Reference in New Issue
Block a user