summaries.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. from __future__ import annotations
  2. from typing import Any
  3. from .common import (
  4. EventLoader,
  5. event_notices,
  6. event_unit,
  7. find_branch,
  8. find_raw_branch,
  9. find_raw_round,
  10. find_round,
  11. integer,
  12. load_events,
  13. )
  14. from .schema import field, payload
  15. def project_round_summary(
  16. detail_ref: str,
  17. round_index: int,
  18. bundle: dict[str, Any],
  19. view: dict[str, Any],
  20. load_event: EventLoader,
  21. ) -> dict[str, Any]:
  22. round_ = find_round(view, round_index)
  23. raw_round = find_raw_round(bundle, round_index)
  24. if round_ is None or raw_round is None:
  25. raise KeyError(detail_ref)
  26. raw_branches = [
  27. row
  28. for row in bundle.get("branches") or []
  29. if integer(row.get("round_index")) == round_index
  30. ]
  31. decisions = [
  32. row
  33. for row in bundle.get("multipathDecisions") or []
  34. if integer(row.get("round_index")) == round_index
  35. ]
  36. evaluation = round_.get("overallEvaluation") or {}
  37. evaluation_ref = str(evaluation.get("detailRef") or "")
  38. evaluation_events = load_events(
  39. load_event,
  40. [evaluation_ref] if evaluation_ref.startswith("event:") else [],
  41. )
  42. goal = field(
  43. f"round:{round_index}:summary:goal",
  44. "本轮目标",
  45. raw_round.get("goal"),
  46. source_kind="database",
  47. source_label="script_build_round",
  48. source_ref=detail_ref,
  49. field_path="goal",
  50. relation="standing-constraint",
  51. )
  52. plan = field(
  53. f"round:{round_index}:summary:plan",
  54. "当前保存的规划",
  55. {
  56. "mode": raw_round.get("race_or_divide"),
  57. "paths": raw_round.get("multipath_plan"),
  58. "note": raw_round.get("plan_note"),
  59. },
  60. source_kind="database",
  61. source_label="script_build_round",
  62. source_ref=detail_ref,
  63. field_path="multipath_plan",
  64. relation="persisted-output",
  65. )
  66. branch_fields = [
  67. field(
  68. f"round:{round_index}:summary:branch:{row.get('branch_id')}",
  69. f"候选方案 {row.get('branch_id')}",
  70. row,
  71. source_kind="database",
  72. source_label="script_build_branch",
  73. source_ref=f"round:{round_index}:branch:{row.get('branch_id')}",
  74. relation="persisted-output",
  75. )
  76. for row in raw_branches
  77. ]
  78. decision_fields = [
  79. field(
  80. f"round:{round_index}:summary:decision:{row.get('id')}",
  81. f"主 Agent 决策 {row.get('id')}",
  82. row,
  83. source_kind="database",
  84. source_label="script_build_multipath_decision",
  85. source_ref=f"multipath-decision:{row.get('id')}",
  86. relation="persisted-output",
  87. )
  88. for row in decisions
  89. ]
  90. convergence = field(
  91. f"round:{round_index}:summary:convergence",
  92. "候选评审与主 Agent 决策",
  93. round_.get("convergenceBatches") or [],
  94. source_kind="calculation",
  95. source_label="按当前轮次的候选范围与明确关联记录汇总",
  96. source_ref=detail_ref,
  97. field_path="convergenceBatches",
  98. relation="persisted-output",
  99. completeness="complete",
  100. )
  101. evaluation_field = field(
  102. f"round:{round_index}:summary:evaluation",
  103. "主脚本整体评审",
  104. evaluation_events[0] if evaluation_events else None,
  105. source_kind="runtime-event",
  106. source_label="主脚本整体评审运行事件",
  107. source_ref=evaluation_ref if evaluation_ref.startswith("event:") else None,
  108. relation="persisted-output",
  109. completeness="complete" if evaluation_events else "missing",
  110. )
  111. result = field(
  112. f"round:{round_index}:summary:result",
  113. "本轮产出",
  114. round_.get("result"),
  115. source_kind="calculation",
  116. source_label="本轮业务记录确定性聚合",
  117. source_ref=detail_ref,
  118. field_path="result",
  119. relation="persisted-output",
  120. completeness="complete" if round_.get("result") else "missing",
  121. )
  122. outputs = [
  123. plan,
  124. *branch_fields,
  125. *decision_fields,
  126. convergence,
  127. evaluation_field,
  128. result,
  129. ]
  130. notices = event_notices(evaluation_events)
  131. if not evaluation_events:
  132. notices.append(
  133. {
  134. "level": "warning",
  135. "message": "本轮没有执行或保存主脚本整体评审运行事件。",
  136. }
  137. )
  138. return payload(
  139. "round-summary",
  140. "aggregate",
  141. inputs=[goal],
  142. units=[event_unit(item) for item in evaluation_events],
  143. outputs=outputs,
  144. runtime_summary="按 round_index 聚合本轮目标、规划、候选分支、决策、整体评审和本轮产出。",
  145. calculation="只聚合当前轮次的结构化业务记录;缺失的整体评审保持缺失。",
  146. notices=notices,
  147. completeness="partial" if not evaluation_events else "complete",
  148. business_modules=[
  149. {"id": "goal", "title": "本轮目标", "sourceIds": [goal["id"]]},
  150. {"id": "plan", "title": "当前保存的规划", "sourceIds": [plan["id"]]},
  151. {"id": "branches", "title": "候选方案", "sourceIds": [item["id"] for item in branch_fields]},
  152. {
  153. "id": "decisions",
  154. "title": "候选评审与主 Agent 决策",
  155. "sourceIds": [
  156. *[item["id"] for item in decision_fields],
  157. convergence["id"],
  158. ],
  159. },
  160. {"id": "evaluation", "title": "主脚本整体评审", "sourceIds": [evaluation_field["id"]]},
  161. {"id": "result", "title": "本轮产出", "sourceIds": [result["id"]]},
  162. ],
  163. )
  164. def project_branch_summary(
  165. detail_ref: str,
  166. round_index: int,
  167. branch_id: int,
  168. bundle: dict[str, Any],
  169. view: dict[str, Any],
  170. load_event: EventLoader,
  171. ) -> dict[str, Any]:
  172. branch = find_branch(view, round_index, branch_id)
  173. raw_branch = find_raw_branch(bundle, branch_id)
  174. if branch is None or raw_branch is None:
  175. raise KeyError(detail_ref)
  176. decisions = [
  177. row
  178. for row in bundle.get("dataDecisions") or []
  179. if integer(row.get("branch_id")) == branch_id
  180. and integer(row.get("round_index")) in {None, round_index}
  181. ]
  182. stage = branch.get("retrievalStage") or {}
  183. retrieval_refs: list[Any] = []
  184. for group in stage.get("directToolGroups") or []:
  185. retrieval_refs.extend(
  186. item.get("eventId") for item in group.get("calls") or []
  187. )
  188. for agent_run in stage.get("agentRuns") or []:
  189. retrieval_refs.append(agent_run.get("eventId"))
  190. retrieval_refs.extend(
  191. item.get("eventId") for item in agent_run.get("attempts") or []
  192. )
  193. retrieval_events = load_events(load_event, retrieval_refs)
  194. task = field(
  195. f"branch:{branch_id}:summary:task",
  196. "实现任务",
  197. raw_branch.get("impl_task"),
  198. source_kind="database",
  199. source_label="script_build_branch",
  200. source_ref=detail_ref,
  201. field_path="impl_task",
  202. relation="direct-input",
  203. )
  204. decision_fields = [
  205. field(
  206. f"branch:{branch_id}:summary:decision:{row.get('id')}",
  207. f"数据取舍 {row.get('id')}",
  208. row,
  209. source_kind="database",
  210. source_label="script_build_data_decision",
  211. source_ref=f"data-decision:{row.get('id')}",
  212. relation="persisted-output",
  213. )
  214. for row in decisions
  215. ]
  216. retrieval_fields = [
  217. field(
  218. f"branch:{branch_id}:summary:retrieval:{event.get('id')}",
  219. str(event.get("event_name") or f"取数 Event {event.get('id')}"),
  220. event,
  221. source_kind="runtime-event",
  222. source_label=str(event.get("event_name") or "取数运行事件"),
  223. source_ref=f"event:{event.get('id')}",
  224. field_path="",
  225. relation="available-upstream",
  226. )
  227. for event in retrieval_events
  228. ]
  229. output = field(
  230. f"branch:{branch_id}:summary:output",
  231. "候选产出",
  232. raw_branch,
  233. source_kind="database",
  234. source_label="script_build_branch",
  235. source_ref=detail_ref,
  236. relation="persisted-output",
  237. )
  238. return payload(
  239. "branch-summary",
  240. "aggregate",
  241. inputs=[task],
  242. units=[event_unit(item) for item in retrieval_events],
  243. outputs=[*retrieval_fields, *decision_fields, output],
  244. runtime_summary="按 branch_id 聚合实现任务、取数、数据取舍和候选产出。",
  245. calculation="只使用当前方案的 Branch、明确 scope 取数 Event 与 DataDecision 记录。",
  246. completeness="complete",
  247. business_modules=[
  248. {"id": "task", "title": "实现任务", "sourceIds": [task["id"]]},
  249. {
  250. "id": "data",
  251. "title": "取数与数据取舍",
  252. "sourceIds": [
  253. *[item["id"] for item in retrieval_fields],
  254. *[item["id"] for item in decision_fields],
  255. ],
  256. },
  257. {"id": "output", "title": "候选产出", "sourceIds": [output["id"]]},
  258. ],
  259. )
  260. def project_missing_round_evaluation(
  261. detail_ref: str,
  262. round_index: int,
  263. bundle: dict[str, Any],
  264. view: dict[str, Any],
  265. ) -> dict[str, Any]:
  266. round_ = find_round(view, round_index)
  267. raw_round = find_raw_round(bundle, round_index)
  268. if round_ is None or raw_round is None:
  269. raise KeyError(detail_ref)
  270. evaluation = round_.get("overallEvaluation") or {}
  271. if str(evaluation.get("detailRef") or "").startswith("event:"):
  272. raise KeyError(detail_ref)
  273. missing = field(
  274. f"round:{round_index}:evaluation:missing",
  275. "记录状态",
  276. None,
  277. source_kind="runtime-event",
  278. source_label="script_evaluator Event",
  279. source_ref=None,
  280. relation="persisted-output",
  281. completeness="missing",
  282. )
  283. return payload(
  284. "evaluation-missing",
  285. "missing",
  286. outputs=[missing],
  287. runtime_summary="本轮没有执行或保存主脚本整体评审运行事件。",
  288. notices=[
  289. {
  290. "level": "warning",
  291. "message": "本轮没有执行或保存主脚本整体评审运行事件。",
  292. }
  293. ],
  294. completeness="missing",
  295. business_modules=[
  296. {"id": "primary", "title": "记录状态", "sourceIds": [missing["id"]]}
  297. ],
  298. )
  299. def project_missing_convergence(
  300. detail_ref: str,
  301. round_index: int,
  302. record_type: str,
  303. view: dict[str, Any],
  304. ) -> dict[str, Any]:
  305. round_ = find_round(view, round_index)
  306. if round_ is None:
  307. raise KeyError(detail_ref)
  308. found_node: dict[str, Any] | None = None
  309. found_batch: dict[str, Any] | None = None
  310. key = "missingReview" if record_type == "review" else "missingDecision"
  311. for batch in round_.get("convergenceBatches") or []:
  312. node = batch.get(key)
  313. if isinstance(node, dict) and detail_ref in {
  314. node.get("id"),
  315. node.get("detailRef"),
  316. }:
  317. found_node, found_batch = node, batch
  318. break
  319. if found_node is None or found_batch is None:
  320. raise KeyError(detail_ref)
  321. branch_ids = found_batch.get("branchIds") or []
  322. unscoped_refs = found_node.get("unscopedReviewRefs") or []
  323. status_value = ((found_node.get("card") or {}).get("primary") or {}).get("value")
  324. if unscoped_refs:
  325. source_kind = "calculation"
  326. source_label = "同轮评审未安全关联到具体候选"
  327. source_ref = detail_ref
  328. value: Any = {
  329. "roundIndex": round_index,
  330. "branchIds": branch_ids,
  331. "unscopedReviewRefs": unscoped_refs,
  332. }
  333. completeness = "partial"
  334. else:
  335. source_kind = "database"
  336. source_label = (
  337. "script_multipath_evaluator Event 查询结果"
  338. if record_type == "review"
  339. else "script_build_multipath_decision 查询结果"
  340. )
  341. source_ref = (
  342. f"missing-convergence:{record_type}:round:{round_index}:"
  343. + ",".join(str(item) for item in branch_ids)
  344. )
  345. value = []
  346. completeness = "complete"
  347. status = field(
  348. f"{detail_ref}:status",
  349. "记录状态",
  350. status_value,
  351. source_kind=source_kind,
  352. source_label=source_label,
  353. source_ref=source_ref,
  354. relation="persisted-output",
  355. completeness=completeness,
  356. )
  357. scope = field(
  358. f"{detail_ref}:scope",
  359. "实际查询范围",
  360. {
  361. "roundIndex": round_index,
  362. "branchIds": branch_ids,
  363. "resultCount": len(unscoped_refs),
  364. },
  365. source_kind="calculation",
  366. source_label="可视化按当前轮次和候选范围确定",
  367. source_ref=detail_ref,
  368. field_path="value",
  369. relation="persisted-output",
  370. )
  371. return payload(
  372. f"multipath-{record_type}-missing",
  373. "aggregate",
  374. outputs=[status, scope],
  375. runtime_summary=str(status_value or "未找到对应记录"),
  376. notices=(
  377. [{"level": "warning", "message": "存在同轮记录,但无法安全关联到具体候选。"}]
  378. if unscoped_refs
  379. else [{"level": "info", "message": "按当前轮次与候选范围查询完成,结果为空。"}]
  380. ),
  381. completeness=completeness,
  382. business_modules=[
  383. {"id": "status", "title": "记录状态", "sourceIds": [status["id"], scope["id"]]}
  384. ],
  385. )