Files
microdao-daarion/services/city-service/models_city.py

622 lines
22 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
1|"""
2|Pydantic Models для City Backend
3|"""
4|
5|from pydantic import BaseModel, Field
6|from typing import Optional, List, Dict, Any
7|from datetime import datetime
8|
9|
10|# =============================================================================
11|# City Rooms
12|# =============================================================================
13|
14|class CityRoomBase(BaseModel):
15| slug: str
16| name: str
17| description: Optional[str] = None
18|
19|
20|class CityRoomCreate(CityRoomBase):
21| pass
22|
23|
24|class CityRoomRead(CityRoomBase):
25| id: str
26| is_default: bool
27| created_at: datetime
28| created_by: Optional[str] = None
29| members_online: int = 0
30| last_event: Optional[str] = None
31| # Branding
32| logo_url: Optional[str] = None
33| banner_url: Optional[str] = None
34| # Context
35| microdao_id: Optional[str] = None
36| microdao_name: Optional[str] = None
37| microdao_slug: Optional[str] = None
38| microdao_logo_url: Optional[str] = None
39| # Matrix integration
40| matrix_room_id: Optional[str] = None
41| matrix_room_alias: Optional[str] = None
42|
43|
44|# =============================================================================
45|# City Room Messages
46|# =============================================================================
47|
48|class CityRoomMessageBase(BaseModel):
49| body: str = Field(..., min_length=1, max_length=10000)
50|
51|
52|class CityRoomMessageCreate(CityRoomMessageBase):
53| pass
54|
55|
56|class CityRoomMessageRead(CityRoomMessageBase):
57| id: str
58| room_id: str
59| author_user_id: Optional[str] = None
60| author_agent_id: Optional[str] = None
61| username: Optional[str] = "Anonymous" # Для frontend
62| created_at: datetime
63|
64|
65|# =============================================================================
66|# City Room Detail (з повідомленнями)
67|# =============================================================================
68|
69|class CityRoomDetail(CityRoomRead):
70| messages: List[CityRoomMessageRead] = []
71| online_members: List[str] = [] # user_ids
72|
73|
74|# =============================================================================
75|# City Feed Events
76|# =============================================================================
77|
78|class CityFeedEventRead(BaseModel):
79| id: str
80| kind: str # 'room_message', 'agent_reply', 'system', 'dao_event'
81| room_id: Optional[str] = None
82| user_id: Optional[str] = None
83| agent_id: Optional[str] = None
84| payload: dict
85| created_at: datetime
86|
87|
88|# =============================================================================
89|# Presence
90|# =============================================================================
91|
92|class PresenceUpdate(BaseModel):
93| user_id: str
94| status: str # 'online', 'offline', 'away'
95| last_seen: Optional[datetime] = None
96|
97|
98|class PresenceBulkUpdate(BaseModel):
99| users: List[PresenceUpdate]
100|
101|
102|# =============================================================================
103|# WebSocket Messages
104|# =============================================================================
105|
106|class WSRoomMessage(BaseModel):
107| event: str # 'room.message', 'room.join', 'room.leave'
108| room_id: Optional[str] = None
109| user_id: Optional[str] = None
110| message: Optional[CityRoomMessageRead] = None
111|
112|
113|class WSPresenceMessage(BaseModel):
114| event: str # 'presence.heartbeat', 'presence.update'
115| user_id: str
116| status: Optional[str] = None
117|
118|
119|# =============================================================================
120|# City Map (2D Map)
121|# =============================================================================
122|
123|class CityMapRoom(BaseModel):
124| """Room representation on 2D city map"""
125| id: str
126| slug: str
127| name: str
128| description: Optional[str] = None
129| room_type: str = "public"
130| zone: str = "central"
131| icon: Optional[str] = None
132| color: Optional[str] = None
133| # Map coordinates
134| x: int = 0
135| y: int = 0
136| w: int = 1
137| h: int = 1
138| # Matrix integration
139| matrix_room_id: Optional[str] = None
140|
141|
142|class CityMapConfig(BaseModel):
143| """Global city map configuration"""
144| grid_width: int = 6
145| grid_height: int = 3
146| cell_size: int = 100
147| background_url: Optional[str] = None
148|
149|
150|class CityMapResponse(BaseModel):
151| """Full city map response"""
152| config: CityMapConfig
153| rooms: List[CityMapRoom]
154|
155|
156|# =============================================================================
157|# Branding & Assets
158|# =============================================================================
159|
160|class BrandingUpdatePayload(BaseModel):
161| logo_url: Optional[str] = None
162| banner_url: Optional[str] = None
163|
164|
165|class AssetUploadResponse(BaseModel):
166| original_url: str
167| processed_url: str
168| thumb_url: Optional[str] = None
169|
170|
171|# =============================================================================
172|# Agents (for Agent Presence)
173|# =============================================================================
174|
175|class AgentRead(BaseModel):
176| """Agent representation"""
177| id: str
178| display_name: str
179| kind: str = "assistant" # assistant, civic, oracle, builder
180| avatar_url: Optional[str] = None
181| color: str = "cyan"
182| status: str = "offline" # online, offline, busy
183| current_room_id: Optional[str] = None
184| capabilities: List[str] = []
185|
186|
187|class AgentPresence(BaseModel):
188| """Agent presence in a room"""
189| agent_id: str
190| display_name: str
191| kind: str
192| status: str
193| room_id: Optional[str] = None
194| color: Optional[str] = None
195| node_id: Optional[str] = None
196| district: Optional[str] = None
197| model: Optional[str] = None
198| role: Optional[str] = None
199| avatar_url: Optional[str] = None
200|
201|
202|# =============================================================================
203|# Citizens
204|# =============================================================================
205|
206|class CityPresenceRoomView(BaseModel):
207| room_id: Optional[str] = None
208| slug: Optional[str] = None
209| name: Optional[str] = None
210|
211|
212|class CityPresenceView(BaseModel):
213| primary_room_slug: Optional[str] = None
214| rooms: List[CityPresenceRoomView] = []
215|
216|
217|class HomeNodeView(BaseModel):
218| """Home node information for agent/citizen"""
219| id: Optional[str] = None
220| name: Optional[str] = None
221| hostname: Optional[str] = None
222| roles: List[str] = []
223| environment: Optional[str] = None
224|
225|
226|class NodeAgentSummary(BaseModel):
227| """Summary of a node agent (Guardian or Steward)"""
228| id: str
229| name: Optional[str] = None
230| kind: Optional[str] = None
231| slug: Optional[str] = None
232|
233|
234|class NodeMicrodaoSummary(BaseModel):
235| """Summary of a MicroDAO hosted on a node (via orchestrator)"""
236| id: str
237| slug: str
238| name: str
239| rooms_count: int = 0
240|
241|
242|class NodeMetrics(BaseModel):
243| """Node metrics for Node Directory cards"""
244| cpu_model: Optional[str] = None
245| cpu_cores: int = 0
246| cpu_usage: float = 0.0
247| gpu_model: Optional[str] = None
248| gpu_vram_total: int = 0
249| gpu_vram_used: int = 0
250| ram_total: int = 0
251| ram_used: int = 0
252| disk_total: int = 0
253| disk_used: int = 0
254| agent_count_router: int = 0
255| agent_count_system: int = 0
256| dagi_router_url: Optional[str] = None
257| swapper_healthy: bool = False
258| swapper_models_loaded: int = 0
259| swapper_models_total: int = 0
260|
261|
262|class NodeProfile(BaseModel):
263| """Node profile for Node Directory"""
264| node_id: str
265| name: str
266| hostname: Optional[str] = None
267| roles: List[str] = []
268| environment: str = "unknown"
269| status: str = "offline"
270| gpu_info: Optional[str] = None
271| agents_total: int = 0
272| agents_online: int = 0
273| last_heartbeat: Optional[str] = None
274| guardian_agent_id: Optional[str] = None
275| steward_agent_id: Optional[str] = None
276| guardian_agent: Optional[NodeAgentSummary] = None
277| steward_agent: Optional[NodeAgentSummary] = None
278| microdaos: List[NodeMicrodaoSummary] = []
279| metrics: Optional[NodeMetrics] = None
280|
281|
282|class ModelBindings(BaseModel):
283| """Agent model bindings for AI capabilities"""
284| primary_model: Optional[str] = None # e.g., "qwen3:8b"
285| supported_kinds: List[str] = [] # e.g., ["text", "vision", "audio"]
286|
287|
288|class UsageStats(BaseModel):
289| """Agent usage statistics"""
290| tokens_total_24h: Optional[int] = None
291| calls_total_24h: Optional[int] = None
292| last_active: Optional[str] = None
293|
294|
295|class MicrodaoBadge(BaseModel):
296| """MicroDAO badge for agent display"""
297| id: str
298| name: str
299| slug: Optional[str] = None
300| role: Optional[str] = None # orchestrator, member, etc.
301| is_public: bool = True
302| is_platform: bool = False
303| logo_url: Optional[str] = None
304| banner_url: Optional[str] = None
305|
306|
307|class AgentCrewInfo(BaseModel):
308| """Information about agent's CrewAI team"""
309| has_crew_team: bool
310| crew_team_key: Optional[str] = None
311| matrix_room_id: Optional[str] = None
312|
313|
314|class AgentSummary(BaseModel):
315| """Unified Agent summary for Agent Console and Citizens"""
316| id: str
317| slug: Optional[str] = None
318| display_name: str
319| title: Optional[str] = None # public_title
320| tagline: Optional[str] = None # public_tagline
321| kind: str = "assistant"
322| avatar_url: Optional[str] = None
323| status: str = "offline"
324|
325| # Node info
326| node_id: Optional[str] = None
327| node_label: Optional[str] = None # "НОДА1" / "НОДА2"
328| home_node: Optional[HomeNodeView] = None
329|
330| # Governance & DAIS (A1, A2)
331| gov_level: Optional[str] = None # personal, core_team, orchestrator, district_lead, city_governance
332| dais_identity_id: Optional[str] = None # DAIS identity reference
333|
334| # Visibility & roles
335| visibility_scope: str = "city" # global, microdao, private
336| is_listed_in_directory: bool = True
337| is_system: bool = False
338| is_public: bool = False
339| is_orchestrator: bool = False # Can create/manage microDAOs
340|
341| # MicroDAO (A3)
342| primary_microdao_id: Optional[str] = None
343| primary_microdao_name: Optional[str] = None
344| primary_microdao_slug: Optional[str] = None
345| home_microdao_id: Optional[str] = None # Owner microDAO
346| home_microdao_name: Optional[str] = None
347| home_microdao_slug: Optional[str] = None
348| district: Optional[str] = None
349| microdaos: List[MicrodaoBadge] = []
350| microdao_memberships: List[Dict[str, Any]] = [] # backward compatibility
351|
352| # Skills
353| public_skills: List[str] = []
354|
355| # CrewAI
356| crew_info: Optional[AgentCrewInfo] = None
357|
358| # Future: model bindings and usage stats
359| model_bindings: Optional[ModelBindings] = None
360| usage_stats: Optional[UsageStats] = None
361|
362|
363|class PublicCitizenSummary(BaseModel):
364| slug: str
365| display_name: str
366| public_title: Optional[str] = None
367| public_tagline: Optional[str] = None
368| avatar_url: Optional[str] = None
369| kind: Optional[str] = None
370| district: Optional[str] = None
371| primary_room_slug: Optional[str] = None
372| public_skills: List[str] = []
373| online_status: Optional[str] = "unknown"
374| status: Optional[str] = None # backward compatibility
375| # Home node info
376| home_node: Optional[HomeNodeView] = None
377| node_id: Optional[str] = None
378|
379| # TASK 037A: Alignment
380| home_microdao_slug: Optional[str] = None
381| home_microdao_name: Optional[str] = None
382| primary_city_room: Optional["CityRoomSummary"] = None
383|
384|
385|class PublicCitizenProfile(BaseModel):
386| slug: str
387| display_name: str
388| kind: Optional[str] = None
389| public_title: Optional[str] = None
390| public_tagline: Optional[str] = None
391| district: Optional[str] = None
392| avatar_url: Optional[str] = None
393| status: Optional[str] = None
394| node_id: Optional[str] = None
395| public_skills: List[str] = []
396| city_presence: Optional[CityPresenceView] = None
397| dais_public: Dict[str, Any]
398| interaction: Dict[str, Any]
399| metrics_public: Dict[str, Any]
400| admin_panel_url: Optional[str] = None
401| microdao: Optional[Dict[str, Any]] = None
402| # Home node info
403| home_node: Optional[HomeNodeView] = None
404|
405|
406|class CitizenInteractionInfo(BaseModel):
407| slug: str
408| display_name: str
409| primary_room_slug: Optional[str] = None
410| primary_room_id: Optional[str] = None
411| primary_room_name: Optional[str] = None
412| matrix_user_id: Optional[str] = None
413| district: Optional[str] = None
414| microdao_slug: Optional[str] = None
415| microdao_name: Optional[str] = None
416|
417|
418|class CitizenAskRequest(BaseModel):
419| question: str
420| context: Optional[str] = None
421|
422|
423|class CitizenAskResponse(BaseModel):
424| answer: str
425| agent_display_name: str
426| agent_id: str
427|
428|
429|# =============================================================================
430|# MicroDAO
431|# =============================================================================
432|
433|class MicrodaoCitizenView(BaseModel):
434| slug: str
435| display_name: str
436| public_title: Optional[str] = None
437| public_tagline: Optional[str] = None
438| avatar_url: Optional[str] = None
439| district: Optional[str] = None
440| primary_room_slug: Optional[str] = None
441|
442|
443|class MicrodaoSummary(BaseModel):
444| """MicroDAO summary for list view"""
445| id: str
446| slug: str
447| name: str
448| description: Optional[str] = None
449| district: Optional[str] = None
450|
451| # Visibility & type
452| is_public: bool = True
453| is_platform: bool = False # Is a platform/district
454| is_active: bool = True
455|
456| # Orchestrator
457| orchestrator_agent_id: Optional[str] = None
458| orchestrator_agent_name: Optional[str] = None
459|
460| # Hierarchy
461| parent_microdao_id: Optional[str] = None
462| parent_microdao_slug: Optional[str] = None
463|
464| # Stats
465| logo_url: Optional[str] = None
466| banner_url: Optional[str] = None
467| member_count: int = 0 # alias for agents_count
468| agents_count: int = 0 # backward compatibility
469| room_count: int = 0 # alias for rooms_count
470| rooms_count: int = 0 # backward compatibility
471| channels_count: int = 0
472|
473|
474|class MicrodaoChannelView(BaseModel):
475| """Channel/integration view for MicroDAO"""
476| kind: str # 'matrix' | 'telegram' | 'city_room' | 'crew'
477| ref_id: str
478| display_name: Optional[str] = None
479| is_primary: bool
480|
481|
482|class MicrodaoAgentView(BaseModel):
483| """Agent view within MicroDAO"""
484| agent_id: str
485| display_name: str
486| role: Optional[str] = None
487| is_core: bool
488|
489|
490|class CityRoomSummary(BaseModel):
491| """Summary of a city room for chat embedding and multi-room support"""
492| id: str
493| slug: str
494| name: str
495| matrix_room_id: Optional[str] = None
496| microdao_id: Optional[str] = None
497| microdao_slug: Optional[str] = None
498| room_role: Optional[str] = None # 'primary', 'lobby', 'team', 'research', 'security', 'governance', 'orchestrator_team'
499| is_public: bool = True
500| sort_order: int = 100
501| logo_url: Optional[str] = None
502| banner_url: Optional[str] = None
503|
504|
505|class MicrodaoRoomsList(BaseModel):
506| """List of rooms belonging to a MicroDAO"""
507| microdao_id: str
508| microdao_slug: str
509| rooms: List[CityRoomSummary] = []
510|
511|
512|class MicrodaoRoomUpdate(BaseModel):
513| """Update request for MicroDAO room settings"""
514| room_role: Optional[str] = None
515| is_public: Optional[bool] = None
516| sort_order: Optional[int] = None
517| set_primary: Optional[bool] = None # if true, mark as primary
518|
519|
520|class AttachExistingRoomRequest(BaseModel):
521| """Request to attach an existing city room to a MicroDAO"""
522| room_id: str
523| room_role: Optional[str] = None
524| is_public: bool = True
525| sort_order: int = 100
526|
527|
528|class MicrodaoDetail(BaseModel):
529| """Full MicroDAO detail view"""
530| id: str
531| slug: str
532| name: str
533| description: Optional[str] = None
534| district: Optional[str] = None
535|
536| # Visibility & type
537| is_public: bool = True
538| is_platform: bool = False
539| is_active: bool = True
540|
541| # Orchestrator
542| orchestrator_agent_id: Optional[str] = None
543| orchestrator_display_name: Optional[str] = None
544|
545| # Hierarchy
546| parent_microdao_id: Optional[str] = None
547| parent_microdao_slug: Optional[str] = None
548| child_microdaos: List["MicrodaoSummary"] = []
549|
550| # Content
551| logo_url: Optional[str] = None
552| banner_url: Optional[str] = None
553| agents: List[MicrodaoAgentView] = []
554| channels: List[MicrodaoChannelView] = []
555|
556| # Multi-room support
557| rooms: List[CityRoomSummary] = []
558| public_citizens: List[MicrodaoCitizenView] = []
559|
560| # Primary city room for chat
561| primary_city_room: Optional[CityRoomSummary] = None
562|
563|
564|class AgentMicrodaoMembership(BaseModel):
565| microdao_id: str
566| microdao_slug: str
567| microdao_name: str
568| logo_url: Optional[str] = None
569| role: Optional[str] = None
570| is_core: bool = False
571|
572|
573|class MicrodaoOption(BaseModel):
574| id: str
575| slug: str
576| name: str
577| district: Optional[str] = None
578| is_active: bool = True
579|
580|
581|# =============================================================================
582|# Visibility Updates (Task 029)
583|# =============================================================================
584|
585|class AgentVisibilityUpdate(BaseModel):
586| """Update agent visibility settings"""
587| is_public: bool
588| visibility_scope: Optional[str] = None # 'global' | 'microdao' | 'private'
589|
590|
591|class MicrodaoVisibilityUpdate(BaseModel):
592| """Update MicroDAO visibility settings"""
593| is_public: bool
594| is_platform: Optional[bool] = None # Upgrade to platform/district
595|
596|
597|class MicrodaoCreateRequest(BaseModel):
598| """Request to create MicroDAO from agent (orchestrator flow)"""
599| name: str
600| slug: str
601| description: Optional[str] = None
602| make_platform: bool = False # If true -> is_platform = true
603| is_public: bool = True
604| parent_microdao_id: Optional[str] = None
605|
606|
607|class SwapperModel(BaseModel):
608| """Model info from Swapper service"""
609| name: str
610| loaded: bool
611| type: Optional[str] = None
612| vram_gb: Optional[float] = None
613|
614|
615|class NodeSwapperDetail(BaseModel):
616| """Detailed Swapper info for Node Cabinet"""
617| node_id: str
618| healthy: bool
619| models_loaded: int
620| models_total: int
621| models: List[SwapperModel] = []