128 lines
5.6 KiB
Python
128 lines
5.6 KiB
Python
"""大屏规划协议,描述设计方案,不直接执行动作"""
|
|
|
|
from typing import Literal, Self
|
|
|
|
from pydantic import Field, model_validator
|
|
|
|
from app.schemas.base import ExtensibleModel, JsonObject, NonBlankString
|
|
from app.schemas.screen import Layout
|
|
|
|
|
|
class PlanRegion(ExtensibleModel):
|
|
region_id: NonBlankString = Field(description="规划内的区域编号,不是前端组件 ID")
|
|
name: NonBlankString = Field(description="区域名称,例如顶部指标区")
|
|
purpose: NonBlankString = Field(description="区域用途和展示重点")
|
|
layout_description: NonBlankString = Field(
|
|
description="区域位置、排列和空间分配说明"
|
|
)
|
|
layout: Layout | None = Field(
|
|
default=None, description="可选的建议位置和尺寸,画布绝对坐标,单位 px"
|
|
)
|
|
|
|
|
|
class PlanComponent(ExtensibleModel):
|
|
plan_id: NonBlankString = Field(
|
|
description="规划内的组件编号,用于关联资源,不作为新增组件的实际 ID"
|
|
)
|
|
disposition: Literal["keep", "modify", "add", "remove"] = Field(
|
|
description="保留、调整、新增或移除,只表达规划意图,不代表执行能力已经支持"
|
|
)
|
|
target_id: NonBlankString | None = Field(
|
|
default=None, description="已有组件 ID,新增时必须为空"
|
|
)
|
|
region_id: NonBlankString | None = Field(
|
|
default=None, description="所属规划区域,可为空,例如移除组件或未分区"
|
|
)
|
|
purpose: NonBlankString = Field(description="展示目的或调整原因")
|
|
component_type: NonBlankString | None = Field(
|
|
default=None, description="建议组件类型,不代表已在组件库中确认可用"
|
|
)
|
|
metrics: list[NonBlankString] = Field(
|
|
default_factory=list, description="展示的指标和维度描述,不是数据绑定"
|
|
)
|
|
design: JsonObject = Field(
|
|
default_factory=dict, description="样式、交互及内容等设计要求,保留扩展字段"
|
|
)
|
|
layout: Layout | None = Field(
|
|
default=None, description="可选的建议布局,画布绝对坐标,不是区域内相对坐标"
|
|
)
|
|
|
|
@model_validator(mode="after")
|
|
def validate_target(self) -> Self:
|
|
if self.disposition == "add":
|
|
if self.target_id is not None:
|
|
raise ValueError("新增组件不能指定已有 target_id")
|
|
elif self.target_id is None:
|
|
raise ValueError("保留、调整或移除组件必须提供 target_id")
|
|
return self
|
|
|
|
|
|
class PlanResourceNeed(ExtensibleModel):
|
|
resource_id: NonBlankString = Field(
|
|
description="本次规划中的资源需求编号,不是已获取资源的 ID"
|
|
)
|
|
kind: NonBlankString = Field(
|
|
description="资源类别,例如 data、component、template、asset、knowledge,允许扩展"
|
|
)
|
|
description: NonBlankString = Field(description="需要查询或补充的内容")
|
|
purpose: NonBlankString = Field(description="资源用途和查询原因")
|
|
component_ids: list[NonBlankString] = Field(
|
|
default_factory=list, description="关联的 plan_id 列表,为空表示整个规划使用"
|
|
)
|
|
|
|
|
|
class ScreenPlan(ExtensibleModel):
|
|
goal: NonBlankString = Field(description="规划目标,遵循已确认需求")
|
|
design_summary: NonBlankString = Field(description="整体或局部设计思路")
|
|
layout_description: NonBlankString = Field(
|
|
description="区域关系、布局安排及保留内容"
|
|
)
|
|
canvas_changes: JsonObject = Field(
|
|
default_factory=dict,
|
|
description="要修改的画布顶层字段及最终值,宽高为绝对值,字段整体替换,为空表示不改画布",
|
|
)
|
|
regions: list[PlanRegion] = Field(default_factory=list)
|
|
components: list[PlanComponent] = Field(default_factory=list)
|
|
resource_needs: list[PlanResourceNeed] = Field(
|
|
default_factory=list, description="规划细化后的待查询资源,不能声称已经获取"
|
|
)
|
|
constraints: list[NonBlankString] = Field(
|
|
default_factory=list, description="沿用用户约束,不擅自放宽"
|
|
)
|
|
assumptions: list[NonBlankString] = Field(
|
|
default_factory=list, description="非阻塞设计偏好的默认假设"
|
|
)
|
|
|
|
@model_validator(mode="after")
|
|
def validate_references(self) -> Self:
|
|
region_ids = [region.region_id for region in self.regions]
|
|
plan_ids = [component.plan_id for component in self.components]
|
|
target_ids = [
|
|
component.target_id
|
|
for component in self.components
|
|
if component.target_id is not None
|
|
]
|
|
resource_ids = [resource.resource_id for resource in self.resource_needs]
|
|
for name, values in (
|
|
("region_id", region_ids),
|
|
("plan_id", plan_ids),
|
|
("target_id", target_ids),
|
|
("resource_id", resource_ids),
|
|
):
|
|
if len(values) != len(set(values)):
|
|
raise ValueError(f"规划中存在重复的 {name}")
|
|
for component in self.components:
|
|
if (
|
|
component.region_id is not None
|
|
and component.region_id not in region_ids
|
|
):
|
|
raise ValueError(f"组件引用了不存在的区域: {component.region_id}")
|
|
for resource in self.resource_needs:
|
|
if len(resource.component_ids) != len(set(resource.component_ids)):
|
|
raise ValueError(
|
|
f"资源 {resource.resource_id} 的 component_ids 不能重复"
|
|
)
|
|
if unknown := set(resource.component_ids) - set(plan_ids):
|
|
raise ValueError(f"资源引用了不存在的规划组件: {sorted(unknown)}")
|
|
return self
|