from __future__ import annotations from dataclasses import dataclass, field from typing import Any SEMANTIC_SCENE_SCHEMA: dict[str, Any] = { "type": "object", "additionalProperties": False, "required": ["page", "intents", "widgets"], "properties": { "page": { "type": "string", "minLength": 1, }, "intents": { "type": "array", "items": { "type": "string", "minLength": 1, }, }, "widgets": { "type": "array", "items": { "type": "object", "additionalProperties": False, "required": ["element_id", "purpose"], "properties": { "element_id": { "type": "string", "minLength": 1, }, "purpose": { "type": "string", "minLength": 1, }, }, }, }, }, } @dataclass(frozen=True) class SemanticWidget: element_id: str purpose: str def to_dict(self) -> dict[str, str]: return { "element_id": self.element_id, "purpose": self.purpose, } @classmethod def from_dict(cls, data: dict[str, Any]) -> "SemanticWidget": if not isinstance(data, dict): raise TypeError("semantic widget must be a mapping") element_id = data.get("element_id") purpose = data.get("purpose") if not isinstance(element_id, str) or not element_id.strip(): raise ValueError("semantic widget element_id must be a non-empty string") if not isinstance(purpose, str) or not purpose.strip(): raise ValueError("semantic widget purpose must be a non-empty string") return cls(element_id=element_id, purpose=purpose) @dataclass(frozen=True) class SemanticScene: page: str intents: list[str] = field(default_factory=list) widgets: list[SemanticWidget] = field(default_factory=list) def to_dict(self) -> dict[str, Any]: return { "page": self.page, "intents": list(self.intents), "widgets": [widget.to_dict() for widget in self.widgets], } @classmethod def from_dict(cls, data: dict[str, Any]) -> "SemanticScene": if not isinstance(data, dict): raise TypeError("semantic scene must be a mapping") page = data.get("page") intents = data.get("intents") widgets = data.get("widgets") if not isinstance(page, str) or not page.strip(): raise ValueError("semantic scene page must be a non-empty string") if not isinstance(intents, list) or not all( isinstance(intent, str) and intent.strip() for intent in intents ): raise ValueError("semantic scene intents must be non-empty strings") if not isinstance(widgets, list): raise ValueError("semantic scene widgets must be a list") return cls( page=page, intents=list(intents), widgets=[SemanticWidget.from_dict(widget) for widget in widgets], )