from datetime import UTC, datetime from enum import StrEnum from typing import Any from pydantic import BaseModel, Field class WorkflowStage(StrEnum): UPLOADED = "uploaded" REGION_SELECTION = "region_selection" PLAN_REVIEW = "plan_review" BLOCKOUT = "blockout" STYLE_BRIEF = "style_brief" DIRECTION_SELECTION = "direction_selection" RENDER_REVIEW = "render_review" EDITING = "editing" COMPLETED = "completed" FAILED = "failed" class WorkflowCommand(StrEnum): START_INGESTION = "start_ingestion" SELECT_REGION = "select_region" CONFIRM_PLAN = "confirm_plan" BUILD_BLOCKOUT = "build_blockout" SUBMIT_STYLE_BRIEF = "submit_style_brief" SELECT_DIRECTION = "select_direction" REQUEST_RENDER = "request_render" APPLY_EDIT = "apply_edit" COMPLETE_PROJECT = "complete_project" RETRY = "retry" class Point2D(BaseModel): x: float y: float class PlanRegion(BaseModel): id: str name: str bounds: list[float] = Field(min_length=4, max_length=4) recommended: bool = False confidence: float = Field(ge=0, le=1) class PlanLayer(BaseModel): id: str label: str category: str visible: bool = True source_names: list[str] = Field(default_factory=list) class PlanIssue(BaseModel): id: str kind: str message: str confidence: float = Field(ge=0, le=1) resolved: bool = False class PlanState(BaseModel): source_name: str source_kind: str page_count: int = 1 vector_based: bool = False cad_layer_count: int = 0 regions: list[PlanRegion] = Field(default_factory=list) selected_region_id: str | None = None layers: list[PlanLayer] = Field(default_factory=list) issues: list[PlanIssue] = Field(default_factory=list) scale_mm_per_unit: float | None = None ceiling_height_mm: int = 2800 class CameraView(BaseModel): id: str label: str room: str status: str = "draft" class SceneState(BaseModel): blockout_asset_key: str | None = None cameras: list[CameraView] = Field(default_factory=list) locked_object_ids: list[str] = Field(default_factory=list) furniture_mode: str = "preserve_reference" class ColorToken(BaseModel): name: str hex: str role: str class StyleState(BaseModel): concept: str = "克制、自然、明亮的现代住宅" keywords: list[str] = Field(default_factory=lambda: ["轻盈", "自然材质", "留白"]) palette: list[ColorToken] = Field(default_factory=list) materials: list[str] = Field(default_factory=list) lighting: str = "低对比度的自然光与柔和间接照明" forms: str = "低矮、水平延展、少量圆角" density: str = "适度留白" avoid: list[str] = Field(default_factory=list) locked_decisions: list[str] = Field(default_factory=list) class ProjectSnapshot(BaseModel): project_id: str name: str stage: WorkflowStage revision: int = 1 plan: PlanState scene: SceneState = Field(default_factory=SceneState) style: StyleState = Field(default_factory=StyleState) available_commands: list[WorkflowCommand] = Field(default_factory=list) last_stable_stage: WorkflowStage | None = None failure_reason: str | None = None updated_at: datetime = Field(default_factory=lambda: datetime.now(UTC)) class CommandRequest(BaseModel): command: WorkflowCommand expected_revision: int = Field(ge=1) payload: dict[str, Any] = Field(default_factory=dict) class WorkflowStageDefinition(BaseModel): id: WorkflowStage label: str purpose: str human_confirmation: bool class WorkflowDefinition(BaseModel): stages: list[WorkflowStageDefinition] transitions: dict[str, list[str]]