schemas.py 4.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. """主通风机筛查输入、审计结果与平台公共结果。"""
  2. from typing import Any, Literal
  3. from pydantic import AliasChoices, BaseModel, ConfigDict, Field, field_validator, model_validator
  4. from algorithms.main_fan.models import Screening, ScreeningInput
  5. from runtime.business_models import JudgmentResult
  6. from runtime.result_models import ResultCode
  7. class AgentEvidence(BaseModel):
  8. """Agent 的来源引用;宿主只绑定来源,不代替模型判断原文含义。"""
  9. model_config = ConfigDict(extra="forbid", strict=True)
  10. source: str = Field(min_length=1, description="本轮原件、input.json/data.json 或实时/时序查询工具返回的可信文件虚拟路径")
  11. page: int | None = Field(default=None, ge=1, description="原件物理页码;JSON 数据引用不用填")
  12. pointer: str = Field(default="", description="原始 JSON 中的字段路径或 JSON Pointer")
  13. excerpt: str = Field(default="", description="实际读取的原文或数据说明")
  14. @field_validator('source', mode='before')
  15. @classmethod
  16. def workspace_reference(cls, value):
  17. # 代码中的相对工作区路径与工具虚拟路径指向同一个来源,不必让模型为前缀重新计算。
  18. # 宿主仍会与本轮来源清单精确匹配,不能借此引用其他文件或任务。
  19. if isinstance(value, str) and value and not value.startswith('/'):
  20. return '/workspace/' + value.removeprefix('./')
  21. return value
  22. class AgentAssessment(BaseModel):
  23. """沙箱脚本交付契约;只约束交付格式,不规定算法和中间事实结构。"""
  24. model_config = ConfigDict(extra="forbid", strict=True)
  25. status: Literal["completed", "needs_materials"] = "completed"
  26. result_code: ResultCode
  27. summary: str = Field(min_length=1, max_length=4000)
  28. data_detail: str = Field(default="", max_length=12000)
  29. missing_materials: list[str] = Field(default_factory=list, max_length=50)
  30. evidence: list[AgentEvidence] = Field(default_factory=list, max_length=200)
  31. details: dict[str, Any] = Field(default_factory=dict)
  32. @model_validator(mode="after")
  33. def check_delivery(self):
  34. if not self.summary.strip():
  35. raise ValueError("研判摘要不能为空白")
  36. if self.result_code != "UNDETERMINED" and not self.evidence:
  37. raise ValueError("确定结论必须引用本轮原件或原始数据")
  38. if self.status == "needs_materials" and self.result_code != "UNDETERMINED":
  39. raise ValueError("缺少关键材料时应提交 UNDETERMINED")
  40. return self
  41. class MainFanInput(ScreeningInput):
  42. target: str = Field(default="", max_length=256, description="可仅传目标名称或配置中的风机标识,例如众源煤业1号主通风机")
  43. mine_code: str = Field(default="", min_length=1, validation_alias=AliasChoices("mine_code","mineCode"), description="可由服务端按target解析")
  44. mine_name: str = Field(default="", min_length=1, validation_alias=AliasChoices("mine_name","mineName"), description="可由服务端按target解析")
  45. fan_name: str = ""
  46. dept_id: str = Field(default='', max_length=64, description='批次绑定的真实部门编号;取数工具校验同矿范围')
  47. task_description: str = "矿井主通风机研判"
  48. materials: list[dict[str, Any]] = Field(default_factory=list, max_length=20)
  49. measurements: dict[str, Any] = Field(default_factory=dict)
  50. use_gateway: bool = False
  51. document_cache_policy: Literal["auto", "refresh", "off"] = "auto"
  52. paired_assessment: bool = True
  53. @model_validator(mode="before")
  54. @classmethod
  55. def resolve_business_target(cls, value):
  56. from extensions.main_fan.targets import resolve_request
  57. return resolve_request(value)
  58. class MainFanResult(JudgmentResult):
  59. status: Literal["completed", "needs_materials"]
  60. judgment_scope: Literal["preliminary_screening", "main_fan_consistency"] = "main_fan_consistency"
  61. analysis_id: str = ""
  62. analysis: Screening | None = None
  63. result_format: Literal["report"] | None = None
  64. sources: list[dict[str, str]] = Field(default_factory=list)
  65. assessment: dict[str, Any] | None = None
  66. workflow_id: str = ""
  67. @model_validator(mode="after")
  68. def screening_receipt(self):
  69. if self.status == "completed" and not self.analysis_id:
  70. raise ValueError("已完成筛查必须包含实际工具生成的 analysis_id;完整原件由结果验证器恢复")
  71. return self