eduevidence 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (312) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +506 -0
  3. package/README.zh-CN.md +434 -0
  4. package/SKILL.md +195 -0
  5. package/bin/eduevidence.js +127 -0
  6. package/domains/education/manifest.json +183 -0
  7. package/domains/education/outcome_taxonomy.json +127 -0
  8. package/domains/manifest.json +26 -0
  9. package/domains/policy/frame.schema.json +234 -0
  10. package/domains/policy/manifest.json +10 -0
  11. package/domains/policy/methodology_checklist.json +109 -0
  12. package/domains/policy/outcome_taxonomy.json +53 -0
  13. package/domains/policy/references/causal-identification.md +45 -0
  14. package/domains/policy/references/cost-evidence.md +44 -0
  15. package/domains/policy/references/equity.md +42 -0
  16. package/domains/policy/references/evidence-hierarchy.md +41 -0
  17. package/domains/policy/references/implementation-evidence.md +47 -0
  18. package/eduevidence_cli.py +26 -0
  19. package/engine/__init__.py +11 -0
  20. package/engine/__pycache__/__init__.cpython-312.pyc +0 -0
  21. package/engine/__pycache__/analysis.cpython-312.pyc +0 -0
  22. package/engine/__pycache__/bias.cpython-312.pyc +0 -0
  23. package/engine/__pycache__/briefs.cpython-312.pyc +0 -0
  24. package/engine/__pycache__/capabilities.cpython-312.pyc +0 -0
  25. package/engine/__pycache__/citation_check.cpython-312.pyc +0 -0
  26. package/engine/__pycache__/contracts.cpython-312.pyc +0 -0
  27. package/engine/__pycache__/datasets.cpython-312.pyc +0 -0
  28. package/engine/__pycache__/events.cpython-312.pyc +0 -0
  29. package/engine/__pycache__/evidence_graph.cpython-312.pyc +0 -0
  30. package/engine/__pycache__/evidence_review.cpython-312.pyc +0 -0
  31. package/engine/__pycache__/evidencecore.cpython-312.pyc +0 -0
  32. package/engine/__pycache__/gap_lens.cpython-312.pyc +0 -0
  33. package/engine/__pycache__/gaps.cpython-312.pyc +0 -0
  34. package/engine/__pycache__/graph_store.cpython-312.pyc +0 -0
  35. package/engine/__pycache__/graph_validate.cpython-312.pyc +0 -0
  36. package/engine/__pycache__/ids.cpython-312.pyc +0 -0
  37. package/engine/__pycache__/library.cpython-312.pyc +0 -0
  38. package/engine/__pycache__/library_builtin.cpython-312.pyc +0 -0
  39. package/engine/__pycache__/living.cpython-312.pyc +0 -0
  40. package/engine/__pycache__/log.cpython-312.pyc +0 -0
  41. package/engine/__pycache__/meta_analysis.cpython-312.pyc +0 -0
  42. package/engine/__pycache__/meta_synthesis.cpython-312.pyc +0 -0
  43. package/engine/__pycache__/migration.cpython-312.pyc +0 -0
  44. package/engine/__pycache__/mode_router.cpython-312.pyc +0 -0
  45. package/engine/__pycache__/paths.cpython-312.pyc +0 -0
  46. package/engine/__pycache__/pilot.cpython-312.pyc +0 -0
  47. package/engine/__pycache__/planner.cpython-312.pyc +0 -0
  48. package/engine/__pycache__/project.cpython-312.pyc +0 -0
  49. package/engine/__pycache__/projections.cpython-312.pyc +0 -0
  50. package/engine/__pycache__/robustness.cpython-312.pyc +0 -0
  51. package/engine/__pycache__/run.cpython-312.pyc +0 -0
  52. package/engine/__pycache__/semantics.cpython-312.pyc +0 -0
  53. package/engine/__pycache__/study_design.cpython-312.pyc +0 -0
  54. package/engine/__pycache__/synthesis.cpython-312.pyc +0 -0
  55. package/engine/__pycache__/tribunal.cpython-312.pyc +0 -0
  56. package/engine/__pycache__/update.cpython-312.pyc +0 -0
  57. package/engine/__pycache__/versions.cpython-312.pyc +0 -0
  58. package/engine/analysis.py +308 -0
  59. package/engine/bias.py +178 -0
  60. package/engine/briefs.py +106 -0
  61. package/engine/capabilities.py +99 -0
  62. package/engine/citation_check.py +192 -0
  63. package/engine/contracts.py +117 -0
  64. package/engine/datasets.py +165 -0
  65. package/engine/events.py +67 -0
  66. package/engine/evidence_graph.py +571 -0
  67. package/engine/evidence_review.py +88 -0
  68. package/engine/evidencecore.py +182 -0
  69. package/engine/gap_lens.py +132 -0
  70. package/engine/gaps.py +169 -0
  71. package/engine/graph_store.py +335 -0
  72. package/engine/graph_validate.py +87 -0
  73. package/engine/ids.py +77 -0
  74. package/engine/library.py +268 -0
  75. package/engine/library_builtin.py +301 -0
  76. package/engine/living.py +671 -0
  77. package/engine/log.py +39 -0
  78. package/engine/meta_analysis.py +333 -0
  79. package/engine/meta_synthesis.py +111 -0
  80. package/engine/migration.py +397 -0
  81. package/engine/mode_router.py +72 -0
  82. package/engine/paths.py +15 -0
  83. package/engine/pilot.py +368 -0
  84. package/engine/planner.py +126 -0
  85. package/engine/project.py +118 -0
  86. package/engine/projections.py +240 -0
  87. package/engine/robustness.py +109 -0
  88. package/engine/run.py +85 -0
  89. package/engine/semantics.py +135 -0
  90. package/engine/study_design.py +87 -0
  91. package/engine/synthesis.py +187 -0
  92. package/engine/tribunal.py +408 -0
  93. package/engine/update.py +113 -0
  94. package/engine/versions.py +12 -0
  95. package/install.sh +510 -0
  96. package/integrations/__init__.py +1 -0
  97. package/integrations/__pycache__/__init__.cpython-312.pyc +0 -0
  98. package/integrations/__pycache__/agent_mcp.cpython-312.pyc +0 -0
  99. package/integrations/__pycache__/smart_web_fetch.cpython-312.pyc +0 -0
  100. package/integrations/agent_mcp.py +856 -0
  101. package/integrations/smart_web_fetch.py +59 -0
  102. package/package.json +50 -0
  103. package/pyproject.toml +55 -0
  104. package/references/applicability-policy.md +88 -0
  105. package/references/education-framing.md +132 -0
  106. package/references/effect_size_formulas.md +35 -0
  107. package/references/evaluation-design.md +111 -0
  108. package/references/evidence-quality.md +79 -0
  109. package/references/grade_framework.md +29 -0
  110. package/references/intervention-design.md +98 -0
  111. package/references/methodology-audit.md +103 -0
  112. package/references/outcome-taxonomy.md +106 -0
  113. package/references/retrieval-protocol.md +142 -0
  114. package/references/skeptic-protocol.md +93 -0
  115. package/references/social_science_pitfalls.md +48 -0
  116. package/references/source-validity.md +140 -0
  117. package/references/tribunal-policy.md +112 -0
  118. package/references/wwc_standards.md +29 -0
  119. package/retrieval/__init__.py +1 -0
  120. package/retrieval/__pycache__/__init__.cpython-312.pyc +0 -0
  121. package/retrieval/__pycache__/corpus_store.cpython-312.pyc +0 -0
  122. package/retrieval/__pycache__/dedupe.cpython-312.pyc +0 -0
  123. package/retrieval/__pycache__/failures.cpython-312.pyc +0 -0
  124. package/retrieval/__pycache__/fetch.cpython-312.pyc +0 -0
  125. package/retrieval/__pycache__/search.cpython-312.pyc +0 -0
  126. package/retrieval/__pycache__/source.cpython-312.pyc +0 -0
  127. package/retrieval/__pycache__/validate.cpython-312.pyc +0 -0
  128. package/retrieval/corpus_store.py +181 -0
  129. package/retrieval/dedupe.py +127 -0
  130. package/retrieval/failures.py +90 -0
  131. package/retrieval/fetch.py +435 -0
  132. package/retrieval/search.py +493 -0
  133. package/retrieval/source.py +160 -0
  134. package/retrieval/validate.py +257 -0
  135. package/schemas/agent-mcp-approval.schema.json +57 -0
  136. package/schemas/chart-spec.schema.json +88 -0
  137. package/schemas/cross-model-review.schema.json +28 -0
  138. package/schemas/education-frame.schema.json +106 -0
  139. package/schemas/evaluation.schema.json +35 -0
  140. package/schemas/evidence.schema.json +81 -0
  141. package/schemas/fetch-result.schema.json +119 -0
  142. package/schemas/intervention.schema.json +46 -0
  143. package/schemas/methodology.schema.json +102 -0
  144. package/schemas/report-result.schema.json +381 -0
  145. package/schemas/report-spec.schema.json +130 -0
  146. package/schemas/source.schema.json +311 -0
  147. package/schemas/v2/analysis-plan.schema.json +28 -0
  148. package/schemas/v2/analysis-run.schema.json +33 -0
  149. package/schemas/v2/claim.schema.json +26 -0
  150. package/schemas/v2/dataset-asset.schema.json +40 -0
  151. package/schemas/v2/decision-snapshot.schema.json +53 -0
  152. package/schemas/v2/evidence-link.schema.json +38 -0
  153. package/schemas/v2/finding.schema.json +47 -0
  154. package/schemas/v2/graph-revision.schema.json +30 -0
  155. package/schemas/v2/knowledge-gap.schema.json +40 -0
  156. package/schemas/v2/methodology-audit.schema.json +30 -0
  157. package/schemas/v2/outcome.schema.json +18 -0
  158. package/schemas/v2/project.schema.json +31 -0
  159. package/schemas/v2/research-intent.schema.json +24 -0
  160. package/schemas/v2/run.schema.json +43 -0
  161. package/schemas/v2/source.schema.json +24 -0
  162. package/schemas/v2/study-design.schema.json +67 -0
  163. package/schemas/v2/study.schema.json +37 -0
  164. package/schemas/v3/pilot-outcome.schema.json +132 -0
  165. package/schemas/v3/run-manifest.schema.json +193 -0
  166. package/schemas/v3/synthesis.schema.json +86 -0
  167. package/schemas/v4/drift-report.schema.json +66 -0
  168. package/schemas/v4/evidence-library.schema.json +42 -0
  169. package/schemas/v4/living-subscription.schema.json +55 -0
  170. package/schemas/v4/meta-analysis.schema.json +152 -0
  171. package/schemas/verdict.schema.json +56 -0
  172. package/scripts/__init__.py +0 -0
  173. package/scripts/__pycache__/__init__.cpython-312.pyc +0 -0
  174. package/scripts/__pycache__/benchmark.cpython-312.pyc +0 -0
  175. package/scripts/__pycache__/benchmark_evaluator.cpython-312.pyc +0 -0
  176. package/scripts/__pycache__/benchmark_judge.cpython-312.pyc +0 -0
  177. package/scripts/__pycache__/benchmark_routing.cpython-312.pyc +0 -0
  178. package/scripts/__pycache__/benchmark_v2.cpython-312.pyc +0 -0
  179. package/scripts/__pycache__/benchmark_v3.cpython-312.pyc +0 -0
  180. package/scripts/__pycache__/build_result.cpython-312.pyc +0 -0
  181. package/scripts/__pycache__/claim_audit.cpython-312.pyc +0 -0
  182. package/scripts/__pycache__/complexity_gate.cpython-312.pyc +0 -0
  183. package/scripts/__pycache__/compute_confidence.cpython-312.pyc +0 -0
  184. package/scripts/__pycache__/dashboard_server.cpython-312.pyc +0 -0
  185. package/scripts/__pycache__/did_regression.cpython-312.pyc +0 -0
  186. package/scripts/__pycache__/effect_calculator.cpython-312.pyc +0 -0
  187. package/scripts/__pycache__/evidence_matrix.cpython-312.pyc +0 -0
  188. package/scripts/__pycache__/evidence_score.cpython-312.pyc +0 -0
  189. package/scripts/__pycache__/evidence_semantics.cpython-312.pyc +0 -0
  190. package/scripts/__pycache__/fetch_benchmark.cpython-312.pyc +0 -0
  191. package/scripts/__pycache__/lint_report_layout.cpython-312.pyc +0 -0
  192. package/scripts/__pycache__/orchestrator.cpython-312.pyc +0 -0
  193. package/scripts/__pycache__/pre_verdict_gate.cpython-312.pyc +0 -0
  194. package/scripts/__pycache__/recompute_demo_quality.cpython-312.pyc +0 -0
  195. package/scripts/__pycache__/render_report.cpython-312.pyc +0 -0
  196. package/scripts/__pycache__/render_report_html.cpython-312.pyc +0 -0
  197. package/scripts/__pycache__/run_workspace.cpython-312.pyc +0 -0
  198. package/scripts/__pycache__/skill_lint.cpython-312.pyc +0 -0
  199. package/scripts/__pycache__/startup_probe.cpython-312.pyc +0 -0
  200. package/scripts/__pycache__/sync_killer_demo_report.cpython-312.pyc +0 -0
  201. package/scripts/__pycache__/test_adversarial_empirical.cpython-312-pytest-9.0.2.pyc +0 -0
  202. package/scripts/__pycache__/test_adversarial_empirical.cpython-312-pytest-9.1.1.pyc +0 -0
  203. package/scripts/__pycache__/validate_schema.cpython-312.pyc +0 -0
  204. package/scripts/audit_dois.py +292 -0
  205. package/scripts/bake_pack.sh +37 -0
  206. package/scripts/benchmark.py +183 -0
  207. package/scripts/benchmark_evaluator.py +371 -0
  208. package/scripts/benchmark_judge.py +535 -0
  209. package/scripts/benchmark_routing.py +120 -0
  210. package/scripts/benchmark_v2.py +304 -0
  211. package/scripts/benchmark_v3.py +552 -0
  212. package/scripts/build_esl_artifacts.py +1921 -0
  213. package/scripts/build_evidence_library.py +307 -0
  214. package/scripts/build_killer_demo.py +295 -0
  215. package/scripts/build_result.py +311 -0
  216. package/scripts/check_version_consistency.py +96 -0
  217. package/scripts/citation_check.py +123 -0
  218. package/scripts/claim_audit.py +157 -0
  219. package/scripts/complexity_gate.py +180 -0
  220. package/scripts/compute_confidence.py +176 -0
  221. package/scripts/dashboard_server.py +536 -0
  222. package/scripts/did_regression.py +315 -0
  223. package/scripts/effect_calculator.py +99 -0
  224. package/scripts/enrich_projects_human_and_lieflat.py +315 -0
  225. package/scripts/evidence_matrix.py +129 -0
  226. package/scripts/evidence_score.py +234 -0
  227. package/scripts/evidence_semantics.py +87 -0
  228. package/scripts/fetch_benchmark.py +177 -0
  229. package/scripts/generate_metrics.py +99 -0
  230. package/scripts/generate_new_projects.py +686 -0
  231. package/scripts/generate_promo.py +192 -0
  232. package/scripts/lint_report_layout.py +182 -0
  233. package/scripts/orchestrator.py +1456 -0
  234. package/scripts/pre_verdict_gate.py +513 -0
  235. package/scripts/quickstart.py +121 -0
  236. package/scripts/rebake_all_5themes.py +88 -0
  237. package/scripts/recompute_demo_quality.py +205 -0
  238. package/scripts/render_report.py +270 -0
  239. package/scripts/render_report_html.py +356 -0
  240. package/scripts/retraction_watch.py +110 -0
  241. package/scripts/run_workspace.py +337 -0
  242. package/scripts/serve_web.py +54 -0
  243. package/scripts/skill_lint.py +150 -0
  244. package/scripts/startup_probe.py +265 -0
  245. package/scripts/sync_killer_demo_report.py +270 -0
  246. package/scripts/test_adversarial_empirical.py +541 -0
  247. package/scripts/validate_schema.py +256 -0
  248. package/skill/agents/education-planner.md +80 -0
  249. package/skill/agents/evaluation-designer.md +74 -0
  250. package/skill/agents/evidence-analyst.md +106 -0
  251. package/skill/agents/evidence-judge.md +111 -0
  252. package/skill/agents/evidence-retriever.md +80 -0
  253. package/skill/agents/intervention-designer.md +82 -0
  254. package/skill/agents/method-reviewer.md +104 -0
  255. package/skill/agents/skeptic.md +89 -0
  256. package/skill/sub-skills/aihot-trend-analysis/SKILL.md +31 -0
  257. package/skill/sub-skills/contradiction-analysis/SKILL.md +17 -0
  258. package/skill/sub-skills/data-analysis/SKILL.md +23 -0
  259. package/skill/sub-skills/ethics-review/SKILL.md +25 -0
  260. package/skill/sub-skills/evidence-extraction/SKILL.md +19 -0
  261. package/skill/sub-skills/evidence-review/SKILL.md +18 -0
  262. package/skill/sub-skills/gap-analysis/SKILL.md +25 -0
  263. package/skill/sub-skills/literature-review/SKILL.md +21 -0
  264. package/skill/sub-skills/methodology-audit/SKILL.md +20 -0
  265. package/skill/sub-skills/report-generation/SKILL.md +51 -0
  266. package/skill/sub-skills/research-planning/SKILL.md +21 -0
  267. package/skill/sub-skills/study-design/SKILL.md +16 -0
  268. package/skill/task-briefs/adjudicate.md +17 -0
  269. package/skill/task-briefs/audit.md +15 -0
  270. package/skill/task-briefs/challenge.md +15 -0
  271. package/skill/task-briefs/evaluate.md +13 -0
  272. package/skill/task-briefs/extract.md +16 -0
  273. package/skill/task-briefs/frame.md +17 -0
  274. package/skill/task-briefs/intervene.md +14 -0
  275. package/skill/task-briefs/present.md +16 -0
  276. package/skill/task-briefs/retrieve.md +15 -0
  277. package/visualization/eduevidence-report/assets/base.css +337 -0
  278. package/visualization/eduevidence-report/motion/motion.css +157 -0
  279. package/visualization/eduevidence-report/motion/motion.js +107 -0
  280. package/visualization/eduevidence-report/references/bilingual-style.md +7 -0
  281. package/visualization/eduevidence-report/references/component-catalog.md +145 -0
  282. package/visualization/eduevidence-report/references/evidence-expansion.md +65 -0
  283. package/visualization/eduevidence-report/references/full-report-outline.md +86 -0
  284. package/visualization/eduevidence-report/references/layout-constraints.md +63 -0
  285. package/visualization/eduevidence-report/references/lieflat-composition.md +79 -0
  286. package/visualization/eduevidence-report/references/motion-system.md +31 -0
  287. package/visualization/eduevidence-report/schemas/adapter-envelope.schema.json +22 -0
  288. package/visualization/eduevidence-report/schemas/visual-layout.schema.json +87 -0
  289. package/visualization/eduevidence-report/scripts/__pycache__/adapter_contract.cpython-312.pyc +0 -0
  290. package/visualization/eduevidence-report/scripts/__pycache__/build_artifact_manifest.cpython-312.pyc +0 -0
  291. package/visualization/eduevidence-report/scripts/__pycache__/build_charts.cpython-312.pyc +0 -0
  292. package/visualization/eduevidence-report/scripts/__pycache__/build_figures.cpython-312.pyc +0 -0
  293. package/visualization/eduevidence-report/scripts/__pycache__/build_infographics.cpython-312.pyc +0 -0
  294. package/visualization/eduevidence-report/scripts/__pycache__/build_report.cpython-312.pyc +0 -0
  295. package/visualization/eduevidence-report/scripts/__pycache__/charts_data.cpython-312.pyc +0 -0
  296. package/visualization/eduevidence-report/scripts/__pycache__/lieflat_engine.cpython-312.pyc +0 -0
  297. package/visualization/eduevidence-report/scripts/__pycache__/zh_labels.cpython-312.pyc +0 -0
  298. package/visualization/eduevidence-report/scripts/adapter_contract.py +72 -0
  299. package/visualization/eduevidence-report/scripts/build_artifact_manifest.py +70 -0
  300. package/visualization/eduevidence-report/scripts/build_charts.py +283 -0
  301. package/visualization/eduevidence-report/scripts/build_figures.py +515 -0
  302. package/visualization/eduevidence-report/scripts/build_infographics.py +268 -0
  303. package/visualization/eduevidence-report/scripts/build_report.py +3211 -0
  304. package/visualization/eduevidence-report/scripts/charts_data.py +617 -0
  305. package/visualization/eduevidence-report/scripts/check_mobile_layout.js +220 -0
  306. package/visualization/eduevidence-report/scripts/lieflat_engine.py +852 -0
  307. package/visualization/eduevidence-report/scripts/zh_labels.py +245 -0
  308. package/visualization/eduevidence-report/themes/academic.css +94 -0
  309. package/visualization/eduevidence-report/themes/claude.css +96 -0
  310. package/visualization/eduevidence-report/themes/datalab-dark.css +147 -0
  311. package/visualization/eduevidence-report/themes/datalab.css +151 -0
  312. package/visualization/eduevidence-report/themes/presentation.css +140 -0
@@ -0,0 +1,268 @@
1
+ #!/usr/bin/env python3
2
+ """build_infographics.py — AntV-style infographic adapter (v5 Iteration 3, §33/§38).
3
+
4
+ Produces inline-SVG research infographics (process/story purpose) from
5
+ result.json — engine mapping per Visualization Router (v5 §34):
6
+ process_or_story -> AntV Infographic -> inline SVG.
7
+
8
+ First-version scope (v5 §51): 4 infographics:
9
+ - workflow Research / EvidenceFlow 流程
10
+ - tribunal 证据裁决三段式(CAN CLAIM / CANNOT CLAIM / WHY)
11
+ - intervention 教学干预阶段时间线
12
+ - evaluation 评价设计流程
13
+
14
+ 语言(P0-13):render_infographics(result, lang='zh'|'en') 按 lang 渲染——
15
+ zh 使用 result.zh.json 的 claim / reason_for_disagreement / intervention /
16
+ evaluation 等文本,en 使用 result.json;数字与结构两边一致(双语同构门校验)。
17
+ Generated SVG is deterministic, zero-dependency, embedded in the report as
18
+ InfographicBlock components (v5 §36). Numbers always come from result.json.
19
+
20
+ HTML-02 (2026-08-12): SVG 只承载短标题 / 关键词 / 阶段 / 方向 / 数字(证据 ID、
21
+ 阶段名、计数、动作徽章);长解释(完整 claim、规则、评估文本)一律保留在 SVG
22
+ 下方的 HTML <p>/<details> 中,避免单行 SVG <text> 溢出。详情由 build_report.py
23
+ 的 tribunal cards / phase blocks / evaluation sections 渲染。
24
+
25
+ Usage:
26
+ python3 visualization/eduevidence-report/scripts/build_infographics.py \
27
+ --result examples/ai-coding-assistant/result.json \
28
+ --out examples/ai-coding-assistant/infographics.json
29
+ """
30
+ from __future__ import annotations
31
+
32
+ import argparse
33
+ import json
34
+ import re
35
+ import sys
36
+ import xml.sax.saxutils as sax
37
+ from pathlib import Path
38
+ from typing import Any
39
+
40
+ from adapter_contract import load_result, write_adapter_output
41
+ from zh_labels import label
42
+
43
+ W = 720
44
+ H = 260
45
+ PALETTE = {"primary": "#B8694A", "support": "#5E8A6A", "contradict": "#A85B53",
46
+ "uncertain": "#C99A4A", "muted": "#8A867E", "bg": "#FCFAF6",
47
+ "border": "#E5DFD3", "text": "#3A3833"}
48
+
49
+ TITLES = {
50
+ "zh": {"workflow": "EvidenceFlow 协议", "tribunal": "证据裁决",
51
+ "intervention": "教学干预时间线", "evaluation": "评价设计流程"},
52
+ "en": {"workflow": "EvidenceFlow Protocol", "tribunal": "Evidence Tribunal",
53
+ "intervention": "Intervention Timeline", "evaluation": "Evaluation Design Flow"},
54
+ }
55
+
56
+ TRIBUNAL_LABELS = {
57
+ "zh": {"can": "可以主张", "cannot": "不可主张", "source": "冲突来源",
58
+ "none": "(无)", "why_short": "详见下方裁决卡片",
59
+ "action": "建议决策"},
60
+ "en": {"can": "Can claim", "cannot": "Cannot claim", "source": "Source of conflict",
61
+ "none": "(none)", "why_short": "See tribunal cards below",
62
+ "action": "Recommended action"},
63
+ }
64
+
65
+ FLOW_STEPS = {
66
+ "zh": ["问题框架", "检索", "抓取\n验证", "证据抽取", "反方质疑",
67
+ "方法\n审计", "裁决", "适用性", "干预\n评价"],
68
+ "en": ["Frame", "Retrieve", "Fetch\nVerify", "Extract", "Challenge",
69
+ "Audit\nMethod", "Adjudicate", "Applicability", "Intervene\nEvaluate"],
70
+ }
71
+
72
+ EVAL_NODES = {
73
+ "zh": [("基线", "前测"), ("后测", "后测"), ("保持", "保持测试"),
74
+ ("迁移", "迁移测试(无 AI)")],
75
+ "en": [("Baseline", "pre-test"), ("Post test", "post-test"),
76
+ ("Retention", "retention test"), ("Transfer", "transfer test (no AI)")],
77
+ }
78
+
79
+ ACTIVITY_LABEL = {"zh": "项活动", "en": "activities"}
80
+
81
+
82
+ def _esc(text: Any) -> str:
83
+ return sax.escape(str(text if text is not None else ""))
84
+
85
+
86
+ def _tspan_text(x: int, first_y: int, label: str, font_size: int, fill: str,
87
+ line_height: int = 14) -> str:
88
+ """Split \n-separated labels into <tspan> lines (SVG never collapses them)."""
89
+ lines = str(label).split("\n")
90
+ out = []
91
+ for i, line in enumerate(lines):
92
+ y = first_y if i == 0 else first_y + i * line_height
93
+ out.append(f'<tspan x="{x}" y="{y}">{_esc(line)}</tspan>')
94
+ return "".join(out)
95
+
96
+
97
+ def _box(x: int, y: int, w: int, h: int, label: str, fill: str, text_color: str = "#FFFFFF",
98
+ font_size: int = 13, sub: str = "") -> str:
99
+ parts = [f'<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="8" fill="{fill}"/>']
100
+ ty = y + h / 2 - (5 if sub else 0)
101
+ parts.append(f'<text x="{x + w / 2}" y="{ty}" text-anchor="middle" fill="{text_color}" '
102
+ f'font-size="{font_size}" font-weight="600">{_tspan_text(x + w / 2, ty, label, font_size, text_color)}</text>')
103
+ if sub:
104
+ parts.append(f'<text x="{x + w / 2}" y="{ty + 16}" text-anchor="middle" '
105
+ f'fill="{text_color}" font-size="11" opacity="0.85">{_esc(sub)}</text>')
106
+ return "".join(parts)
107
+
108
+
109
+ def _arrow(x1: int, y1: int, x2: int, y2: int) -> str:
110
+ return (f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" stroke="{PALETTE["muted"]}" '
111
+ f'stroke-width="2" marker-end="url(#arr)"/>')
112
+
113
+
114
+ def _svg(title: str, body: str) -> str:
115
+ return (f'<svg viewBox="0 0 {W} {H}" xmlns="http://www.w3.org/2000/svg" role="img" '
116
+ f'aria-label="{_esc(title)}">'
117
+ f'<defs><marker id="arr" markerWidth="8" markerHeight="8" refX="6" refY="3" '
118
+ f'orient="auto"><path d="M0,0 L6,3 L0,6 Z" fill="{PALETTE["muted"]}"/></marker></defs>'
119
+ f'<rect width="{W}" height="{H}" fill="{PALETTE["bg"]}" rx="12"/>'
120
+ f'<text x="24" y="34" font-size="16" font-weight="700" fill="{PALETTE["text"]}">{_esc(title)}</text>'
121
+ f'{body}</svg>')
122
+
123
+
124
+ def _evidence_ids(items: list[Any], limit: int = 4) -> list[str]:
125
+ """从主张/反证条目文本中抽取证据 ID(短标识),长文本不进 SVG。"""
126
+ ids: list[str] = []
127
+ pattern = re.compile(r"\b(?:E|EV)-[A-Za-z0-9-]+\b") # W6: E-xxx 与 EV-xxx 双匹配
128
+ for item in items:
129
+ for eid in pattern.findall(str(item or "")):
130
+ if eid not in ids:
131
+ ids.append(eid)
132
+ if len(ids) >= limit:
133
+ break
134
+ return ids[:limit]
135
+
136
+
137
+ def workflow_svg(lang: str = "zh") -> str:
138
+ steps = FLOW_STEPS[lang]
139
+ n = len(steps)
140
+ bw, gap = 64, 12
141
+ total = n * bw + (n - 1) * gap
142
+ x0 = (W - total) // 2
143
+ y = 100
144
+ boxes = []
145
+ for i, lab in enumerate(steps):
146
+ x = x0 + i * (bw + gap)
147
+ fill = PALETTE["support"] if i in (4, 5) else (PALETTE["primary"] if i < 6 else PALETTE["uncertain"])
148
+ boxes.append(_box(x, y, bw, 52, lab, fill, font_size=10))
149
+ if i < n - 1:
150
+ boxes.append(_arrow(x + bw, y + 26, x + bw + gap, y + 26))
151
+ return _svg(TITLES[lang]["workflow"], "".join(boxes))
152
+
153
+
154
+ def tribunal_svg(verdict: dict, lang: str = "zh") -> str:
155
+ """裁决信息图:只放短标题、计数、证据 ID 与动作徽章。
156
+
157
+ 完整 claim / reason_for_disagreement 文本由 HTML 卡片承载(HTML-02)。
158
+ """
159
+ labs = TRIBUNAL_LABELS[lang]
160
+ none_text = labs["none"]
161
+ can = verdict.get("supported_claims") or verdict.get("what_can_be_claimed") or [none_text]
162
+ cannot = verdict.get("contradicted_claims") or verdict.get("what_cannot_be_claimed") or [none_text]
163
+ action = verdict.get("recommended_action", "insufficient_evidence").upper()
164
+
165
+ def col(x: int, title: str, items: list[str], color: str) -> str:
166
+ ids = _evidence_ids(items)
167
+ if not ids:
168
+ ids = [none_text]
169
+ parts = [f'<text x="{x + 10}" y="80" font-size="13" font-weight="700" fill="{color}">'
170
+ f'{_esc(title)}</text>']
171
+ for i, eid in enumerate(ids):
172
+ yy = 105 + i * 22
173
+ parts.append(f'<circle cx="{x + 14}" cy="{yy - 5}" r="3" fill="{color}"/>')
174
+ parts.append(f'<text x="{x + 26}" y="{yy}" font-size="11" fill="{PALETTE["text"]}">'
175
+ f'{_esc(eid)}</text>')
176
+ return "".join(parts)
177
+
178
+ body = (
179
+ f'<text x="24" y="180" font-size="13" font-weight="700" fill="{PALETTE["text"]}">{_esc(labs["source"])}</text>'
180
+ f'<text x="24" y="202" font-size="11" fill="{PALETTE["muted"]}">{_esc(labs["why_short"])}</text>'
181
+ f'<rect x="24" y="216" width="180" height="28" rx="14" fill="{PALETTE["uncertain"]}"/>'
182
+ f'<text x="114" y="235" text-anchor="middle" font-size="13" font-weight="700" fill="#fff">{_esc(action)}</text>'
183
+ + col(240, f'{labs["can"]} ({len(can)})', can, PALETTE["support"])
184
+ + col(480, f'{labs["cannot"]} ({len(cannot)})', cannot, PALETTE["contradict"])
185
+ )
186
+ return _svg(TITLES[lang]["tribunal"], body)
187
+
188
+
189
+ def _phase_short(name: Any, index: int, lang: str) -> str:
190
+ """阶段名只取标题头部(分隔符前),避免长句进 SVG。"""
191
+ text = str(name or f"Phase {index + 1}")
192
+ if lang == "zh" and ":" in text:
193
+ head = text.split(":")[0].strip()
194
+ if len(head) <= 12:
195
+ return head
196
+ elif lang == "en" and ":" in text:
197
+ head = text.split(":")[0].strip()
198
+ if len(head) <= 26:
199
+ return head
200
+ return f"Phase {index + 1}"
201
+
202
+
203
+ def intervention_svg(intervention: dict, lang: str = "zh") -> str:
204
+ """干预时间线:只放阶段短名与活动数(HTML-02),长规则文本在 HTML 阶段块。"""
205
+ phases = [intervention.get(p) for p in ("phase_1", "phase_2", "phase_3", "phase_4")]
206
+ phases = [p for p in phases if isinstance(p, dict)]
207
+ if not phases:
208
+ phases = [{"name": "Phase 1", "activities": []}]
209
+ bw, gap, y = 160, 16, 100
210
+ boxes = []
211
+ for i, p in enumerate(phases):
212
+ x = 24 + i * (bw + gap)
213
+ name = _phase_short(p.get("name"), i, lang)
214
+ n_act = len(p.get("activities") or [])
215
+ sub = f"{n_act} {ACTIVITY_LABEL[lang]}" if n_act else ""
216
+ boxes.append(_box(x, y, bw, 96, name, PALETTE["primary"], font_size=12, sub=sub))
217
+ if i < len(phases) - 1:
218
+ boxes.append(_arrow(x + bw, y + 48, x + bw + gap, y + 48))
219
+ return _svg(TITLES[lang]["intervention"], "".join(boxes))
220
+
221
+
222
+ def evaluation_svg(evaluation: dict, lang: str = "zh") -> str:
223
+ """评价设计流程:只放阶段关键词(HTML-02),评估长文本在 HTML 段落。"""
224
+ nodes = EVAL_NODES[lang]
225
+ keys = ("baseline", "post_test", "retention_test", "transfer_test")
226
+ bw, gap, y = 150, 12, 110
227
+ boxes = []
228
+ for i, ((lab, keyword), key) in enumerate(zip(nodes, keys)):
229
+ x = 30 + i * (bw + gap)
230
+ boxes.append(_box(x, y, bw, 56, lab, PALETTE["support"], font_size=12))
231
+ boxes.append(f'<text x="{x + bw / 2}" y="{y + 38}" text-anchor="middle" font-size="9" '
232
+ f'fill="#fff" opacity="0.95">{_esc(keyword)}</text>')
233
+ if i < len(nodes) - 1:
234
+ boxes.append(_arrow(x + bw, y + 28, x + bw + gap, y + 28))
235
+ return _svg(TITLES[lang]["evaluation"], "".join(boxes))
236
+
237
+
238
+ def render_infographics(result: dict, lang: str = "zh") -> dict[str, str]:
239
+ """按语言渲染 4 张信息图(zh 数据 = result.zh.json,en 数据 = result.json)。"""
240
+ return {
241
+ "workflow": workflow_svg(lang),
242
+ "tribunal": tribunal_svg(result.get("decision", {}), lang),
243
+ "intervention": intervention_svg(result.get("intervention", {}), lang),
244
+ "evaluation": evaluation_svg(result.get("evaluation", {}), lang),
245
+ }
246
+
247
+
248
+ def build_all(result: dict, lang: str = "zh") -> dict[str, str]:
249
+ """兼容别名:等价于 render_infographics(result, lang)。"""
250
+ return render_infographics(result, lang)
251
+
252
+
253
+ def main() -> int:
254
+ parser = argparse.ArgumentParser(description="Build AntV-style infographic SVGs from result.json")
255
+ parser.add_argument("--result", required=True)
256
+ parser.add_argument("--out", required=True)
257
+ parser.add_argument("--lang", choices=["zh", "en"], default="zh")
258
+ args = parser.parse_args()
259
+
260
+ result = load_result(args.result)
261
+ infographics = build_all(result, lang=args.lang)
262
+ write_adapter_output(args.out, "infographics", args.result, infographics, locale=args.lang)
263
+ print(f"wrote {args.out} ({', '.join(infographics.keys())})")
264
+ return 0
265
+
266
+
267
+ if __name__ == "__main__":
268
+ sys.exit(main())