batch_models.py 3.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. """月度判识的矿级计划与报告档案契约;业务归属和算法由 Agent 判断。"""
  2. from typing import Any, Literal
  3. from pydantic import BaseModel, ConfigDict, Field, model_validator
  4. class HistoryPoint(BaseModel):
  5. model_config = ConfigDict(extra='forbid', str_strip_whitespace=True)
  6. system: Literal['safety', 'equipment']
  7. node_id: str = Field(min_length=1, max_length=128, pattern=r'^[A-Za-z0-9_-]+$')
  8. role: Literal['fan_state', 'flow']
  9. fan_id: str = ''
  10. label: str = ''
  11. unit: str = ''
  12. mapping_confirmed: bool = False
  13. mapping_reason: str = ''
  14. class ShaftPlan(BaseModel):
  15. model_config = ConfigDict(extra='forbid', str_strip_whitespace=True)
  16. shaft_id: str = Field(min_length=1, max_length=80, pattern=r'^[A-Za-z0-9_-]+$')
  17. shaft_name: str = Field(min_length=1, max_length=160)
  18. identity_status: Literal['confirmed', 'candidate'] = 'candidate'
  19. identity_reason: str = Field(min_length=1, max_length=2000)
  20. fan_pair: list[str] = Field(default_factory=list, max_length=2)
  21. points: list[HistoryPoint] = Field(default_factory=list, max_length=32)
  22. limitations: list[str] = Field(default_factory=list, max_length=30)
  23. class MinePlan(BaseModel):
  24. model_config = ConfigDict(extra='forbid', str_strip_whitespace=True)
  25. coverage: Literal['confirmed', 'partial'] = 'partial'
  26. shafts: list[ShaftPlan] = Field(min_length=1, max_length=20)
  27. limitations: list[str] = Field(default_factory=list, max_length=30)
  28. @model_validator(mode='after')
  29. def unique_shafts(self):
  30. if len({shaft.shaft_id for shaft in self.shafts}) != len(self.shafts):
  31. raise ValueError('同矿通风井任务标识不能重复')
  32. return self
  33. class ReportArchiveFacts(BaseModel):
  34. """只约束完整读取声明和来源,具体提取事实结构由 Agent 设计。"""
  35. model_config = ConfigDict(extra='forbid')
  36. source_sha256: str = Field(pattern=r'^[0-9a-f]{64}$')
  37. page_count: int = Field(ge=1)
  38. read_pages: list[int] = Field(min_length=1)
  39. page_sources: dict[int, str] = Field(default_factory=dict, description='每个物理页实际读取的独立图片或文本文件路径')
  40. reports: list[dict[str, Any]] = Field(min_length=1)
  41. analysis: dict[str, Any] = Field(default_factory=dict)
  42. limitations: list[str] = Field(default_factory=list)
  43. class BatchRequest(BaseModel):
  44. model_config = ConfigDict(extra='forbid')
  45. # 必须显式指定矿井;CLI的联调保护默认最多两个,无隐式全量执行。
  46. dept_ids: list[str] = Field(default_factory=list, max_length=500)
  47. all_producing_mines: bool = False
  48. reassess: bool = Field(default=False, description='在原批次和原时段内重新分析已完成结果,保留旧版本并复用已取得原件')
  49. snapshot_id: str | None = None
  50. cutoff: str | None = None
  51. lookback_days: int = Field(default=40, ge=1, le=40)
  52. user_name: str = Field(default='main-fan-batch', min_length=1, max_length=128)
  53. @model_validator(mode='after')
  54. def explicit_selection(self):
  55. if self.all_producing_mines == bool(self.dept_ids):
  56. raise ValueError('必须显式选择部门列表或全部正常生产煤矿,两种方式只能选一种')
  57. return self