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,283 @@
1
+ #!/usr/bin/env python3
2
+ """build_charts.py — ECharts chart-spec generator (v5 Iteration 3, §34 router).
3
+
4
+ From result.json, produce ECharts option specs for the interactive surfaces:
5
+ - Outcome Evidence Overview (diverging evidence bar)
6
+ - Benchmark panel (bar + quality-vs-cost scatter)
7
+ - Claim-Evidence Trace (graph: Decision -> Claim -> Evidence -> Source)
8
+
9
+ Purpose = interactive_analysis -> engine = echarts (v5 §34). The specs are
10
+ plain JSON consumed by the report's ECharts enhancement layer; when ECharts is
11
+ unavailable (offline/no JS), the static tables/SVGs still carry the report
12
+ (v5 §28 static degradation).
13
+
14
+ 语言:spec 按 lang 生成(系列名/标题/摘要随语言,数字不变)。diverging bar 为真
15
+ diverging:support 从中心向右、contradict 从中心向左,neutral 走独立子网格细条道,
16
+ 三系列互不覆盖(P0-10);计数轴强制整数刻度 minInterval=1(P0-11)。
17
+
18
+ Usage:
19
+ python3 visualization/eduevidence-report/scripts/build_charts.py \
20
+ --result examples/ai-coding-assistant/result.json \
21
+ --out examples/ai-coding-assistant/chart_specs.json
22
+ """
23
+ from __future__ import annotations
24
+
25
+ import argparse
26
+ import json
27
+ import sys
28
+ from pathlib import Path
29
+ from typing import Any
30
+
31
+ from adapter_contract import load_result, write_adapter_output
32
+ from zh_labels import label
33
+
34
+ WARM_PALETTE = ["#B8694A", "#5E8A6A", "#A85B53", "#C99A4A", "#8A867E", "#4F7A55"]
35
+ OKABE_ITO = ["#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00",
36
+ "#CC79A7", "#000000"]
37
+
38
+ TITLES = {
39
+ "zh": {"overview": "结果证据概览", "benchmark": "基准测试:B0-B4",
40
+ "quality_cost": "质量 vs 成本", "trace": "主张-证据追溯"},
41
+ "en": {"overview": "Outcome Evidence Overview", "benchmark": "Benchmark: B0-B4",
42
+ "quality_cost": "Quality vs Cost", "trace": "Claim-Evidence Trace"},
43
+ }
44
+
45
+ SUMMARIES = {
46
+ "zh": {
47
+ "overview": "各 Outcome 的正向 / 负向 / 零效应证据条数对比。这里展示的是 effect_direction,不是“这条证据是否支持某个主张”。",
48
+ "benchmark": "B0-B4 基线在引用支持/无支撑率/反方发现上的对比及质量-成本散点。",
49
+ "trace": "决策→结论→证据→来源 的可追溯图谱;点击节点可追踪支持/反驳路径。",
50
+ },
51
+ "en": {
52
+ "overview": "Positive / negative / null effect-direction evidence counts per outcome. "
53
+ "This visual encodes effect_direction, not whether evidence supports a claim.",
54
+ "benchmark": "B0-B4 baselines compared on citation support, unsupported rate and "
55
+ "contradiction discovery, plus the quality-cost scatter.",
56
+ "trace": "Traceable graph Decision → Claim → Evidence → Source; click nodes to follow "
57
+ "support/contradict paths.",
58
+ },
59
+ }
60
+
61
+
62
+ def effect_outcomes(result: dict[str, Any]) -> list[dict[str, Any]]:
63
+ """Aggregate evidence by outcome using effect_direction, not relation_to_claim.
64
+
65
+ `direction` / `relation_to_claim` answers whether evidence supports a claim. It does
66
+ not say whether the measured outcome improved or worsened. Outcome visuals must use
67
+ `effect_direction` to avoid turning evidence for a harmful effect into a green bar.
68
+ """
69
+ ordered = [o.get("outcome_type", "") for o in result.get("outcomes", []) if o.get("outcome_type")]
70
+ seen = set(ordered)
71
+ for ev in result.get("evidence", []) or []:
72
+ outcome = ev.get("outcome_type") or ""
73
+ if outcome and outcome not in seen:
74
+ ordered.append(outcome)
75
+ seen.add(outcome)
76
+
77
+ buckets = {name: {"positive_count": 0, "negative_count": 0, "null_count": 0,
78
+ "evidence_ids": []} for name in ordered}
79
+ for ev in result.get("evidence", []) or []:
80
+ outcome = ev.get("outcome_type") or ""
81
+ if not outcome:
82
+ continue
83
+ bucket = buckets.setdefault(outcome, {"positive_count": 0, "negative_count": 0,
84
+ "null_count": 0, "evidence_ids": []})
85
+ effect = str(ev.get("effect_direction") or "null").lower()
86
+ field = {"positive": "positive_count", "negative": "negative_count",
87
+ "null": "null_count", "neutral": "null_count"}.get(effect, "null_count")
88
+ bucket[field] += 1
89
+ if ev.get("evidence_id"):
90
+ bucket["evidence_ids"].append(ev["evidence_id"])
91
+
92
+ return [{"outcome_type": name, **buckets[name]} for name in ordered if name in buckets]
93
+
94
+
95
+ def outcome_overview(result: dict[str, Any], lang: str = "zh") -> dict[str, Any]:
96
+ """Diverging effect-direction bar: positive right, negative left, null thin lane."""
97
+ outcomes = effect_outcomes(result)
98
+ names = [label(lang, "outcome", o.get("outcome_type", "")) for o in outcomes]
99
+ positive = [o.get("positive_count", 0) for o in outcomes]
100
+ negative = [-o.get("negative_count", 0) for o in outcomes]
101
+ null = [o.get("null_count", 0) for o in outcomes]
102
+ vmax = max([1] + [abs(v) for v in positive + negative + null])
103
+ series_names = (["正向效应", "负向效应", "零效应"] if lang == "zh"
104
+ else ["Positive effect", "Negative effect", "Null effect"])
105
+ return {
106
+ "chart_id": "outcome-evidence-overview",
107
+ "purpose": "interactive_analysis",
108
+ "engine": "echarts",
109
+ "chart_type": "diverging_bar",
110
+ "semantic_basis": "effect_direction",
111
+ "title": TITLES[lang]["overview"],
112
+ "option": {
113
+ "tooltip": {"trigger": "axis"},
114
+ "legend": {"data": series_names},
115
+ "grid": [
116
+ {"left": 150, "right": 40, "top": 30, "height": "52%"},
117
+ {"left": 150, "right": 40, "top": "70%", "height": "18%"},
118
+ ],
119
+ "xAxis": [
120
+ {"type": "value", "gridIndex": 0, "minInterval": 1},
121
+ {"type": "value", "gridIndex": 1, "min": 0, "max": vmax, "minInterval": 1},
122
+ ],
123
+ "yAxis": [
124
+ {"type": "category", "data": names, "inverse": True, "gridIndex": 0},
125
+ {"type": "category", "data": names, "inverse": True, "gridIndex": 1, "show": False},
126
+ ],
127
+ "series": [
128
+ {"name": series_names[0], "type": "bar", "data": positive,
129
+ "itemStyle": {"color": "#5E8A6A"}, "xAxisIndex": 0, "yAxisIndex": 0,
130
+ "lane": "main"},
131
+ {"name": series_names[1], "type": "bar", "data": negative,
132
+ "itemStyle": {"color": "#A85B53"}, "xAxisIndex": 0, "yAxisIndex": 0,
133
+ "lane": "main"},
134
+ {"name": series_names[2], "type": "bar", "data": null,
135
+ "itemStyle": {"color": "#C99A4A"}, "xAxisIndex": 1, "yAxisIndex": 1,
136
+ "barWidth": 6, "lane": "neutral"},
137
+ ],
138
+ },
139
+ "summary_text": SUMMARIES[lang]["overview"],
140
+ "integrity": {"numbers_match_result": "NOT_CHECKED", "no_axis_distortion": "NOT_CHECKED",
141
+ "no_false_precision": "NOT_CHECKED", "colorblind_safe": "NOT_CHECKED"},
142
+ }
143
+
144
+
145
+ def benchmark_panel(benchmark: dict[str, Any], lang: str = "zh") -> dict[str, Any]:
146
+ """Benchmark four-chart panel (v5 §20): bar charts + quality-vs-cost scatter."""
147
+ baselines = benchmark.get("baselines", {})
148
+ names = list(baselines.keys())
149
+ citation = [b.get("citation_support_precision", 0) for b in baselines.values()]
150
+ unsupported = [b.get("unsupported_claim_rate", 0) for b in baselines.values()]
151
+ contradiction = [b.get("contradiction_discovery_rate", 0) for b in baselines.values()]
152
+ costs = [b.get("usage", {}).get("cost_usd", 0) for b in baselines.values()]
153
+ return {
154
+ "chart_id": "benchmark-panel",
155
+ "purpose": "interactive_analysis",
156
+ "engine": "echarts",
157
+ "chart_type": "composite",
158
+ "title": TITLES[lang]["benchmark"],
159
+ "option": {
160
+ "tooltip": {"trigger": "axis"},
161
+ "legend": {"data": ["Citation Support", "Unsupported Rate", "Contradiction"]},
162
+ "grid": {"left": 60, "right": 40},
163
+ "xAxis": {"type": "category", "data": names},
164
+ "yAxis": {"type": "value", "max": 1, "min": 0},
165
+ "series": [
166
+ {"name": "Citation Support", "type": "bar", "data": citation},
167
+ {"name": "Unsupported Rate", "type": "bar", "data": unsupported},
168
+ {"name": "Contradiction", "type": "bar", "data": contradiction},
169
+ ],
170
+ },
171
+ "cost_vs_quality": {
172
+ "chart_id": "benchmark-quality-cost",
173
+ "purpose": "interactive_analysis",
174
+ "engine": "echarts",
175
+ "chart_type": "scatter",
176
+ "title": TITLES[lang]["quality_cost"],
177
+ "option": {
178
+ "tooltip": {"trigger": "item"},
179
+ "xAxis": {"type": "value", "name": "cost (USD)", "min": 0},
180
+ "yAxis": {"type": "value", "name": "citation support", "min": 0, "max": 1},
181
+ "series": [{
182
+ "type": "scatter",
183
+ "data": [[c, q, n] for c, q, n in zip(costs, citation, names)],
184
+ }],
185
+ },
186
+ },
187
+ "summary_text": SUMMARIES[lang]["benchmark"],
188
+ "integrity": {"numbers_match_result": "NOT_CHECKED", "no_axis_distortion": "NOT_CHECKED",
189
+ "no_false_precision": "NOT_CHECKED", "colorblind_safe": "NOT_CHECKED"},
190
+ }
191
+
192
+
193
+ def claim_trace(result: dict[str, Any], lang: str = "zh") -> dict[str, Any]:
194
+ """Claim-Evidence Trace graph (v5 §16 / §41): Decision -> Claim -> Evidence -> Source."""
195
+ evidence = result.get("evidence", [])
196
+ claims = result.get("claims", [])
197
+ sources = result.get("sources", [])
198
+ decision = result.get("decision", {})
199
+ action = decision.get("recommended_action", "insufficient_evidence")
200
+
201
+ nodes: list[dict] = []
202
+ edges: list[dict] = []
203
+ node_ids: set[str] = set()
204
+
205
+ def add_node(nid: str, name: str, category: int) -> None:
206
+ if nid not in node_ids:
207
+ node_ids.add(nid)
208
+ nodes.append({"id": nid, "name": name, "category": category})
209
+
210
+ add_node("decision", action.upper(), 0)
211
+ for i, claim in enumerate(claims):
212
+ cid = f"claim-{i}"
213
+ add_node(cid, (claim.get("claim") or "")[:40], 1)
214
+ edges.append({"source": "decision", "target": cid})
215
+ for eid in claim.get("evidence_ids", []):
216
+ ev = next((e for e in evidence if e.get("evidence_id") == eid), None)
217
+ if ev:
218
+ add_node(eid, eid, 2)
219
+ direction = ev.get("direction", "neutral")
220
+ edges.append({"source": cid, "target": eid,
221
+ "label": direction, "lineStyle": {"color": {
222
+ "support": "#5E8A6A", "contradict": "#A85B53",
223
+ "neutral": "#C99A4A"}.get(direction, "#C99A4A")}})
224
+ sid = ev.get("source_id", "")
225
+ src = next((s for s in sources if s.get("source_id") == sid), None)
226
+ if src:
227
+ add_node(sid, sid, 3)
228
+ edges.append({"source": eid, "target": sid})
229
+
230
+ return {
231
+ "chart_id": "claim-evidence-trace",
232
+ "purpose": "interactive_analysis",
233
+ "engine": "echarts",
234
+ "chart_type": "graph",
235
+ "title": TITLES[lang]["trace"],
236
+ "option": {
237
+ "tooltip": {"trigger": "item"},
238
+ "legend": {"data": ["Decision", "Claim", "Evidence", "Source"]},
239
+ "series": [{
240
+ "type": "graph",
241
+ "layout": "force",
242
+ "roam": True,
243
+ "draggable": True,
244
+ "categories": [{"name": "Decision"}, {"name": "Claim"},
245
+ {"name": "Evidence"}, {"name": "Source"}],
246
+ "data": nodes,
247
+ "links": edges,
248
+ "label": {"show": True, "position": "right", "fontSize": 9},
249
+ "lineStyle": {"curveness": 0.15},
250
+ }],
251
+ },
252
+ "summary_text": SUMMARIES[lang]["trace"],
253
+ "integrity": {"numbers_match_result": "NOT_CHECKED", "no_axis_distortion": "NOT_CHECKED",
254
+ "no_false_precision": "NOT_CHECKED", "colorblind_safe": "NOT_CHECKED"},
255
+ }
256
+
257
+
258
+ def build_all(result: dict[str, Any], lang: str = "zh") -> dict[str, Any]:
259
+ return {
260
+ "charts": [
261
+ outcome_overview(result, lang),
262
+ claim_trace(result, lang),
263
+ ],
264
+ "benchmark": benchmark_panel(result.get("benchmark", {}), lang),
265
+ }
266
+
267
+
268
+ def main() -> int:
269
+ parser = argparse.ArgumentParser(description="Build ECharts chart specs from result.json")
270
+ parser.add_argument("--result", required=True)
271
+ parser.add_argument("--out", required=True)
272
+ parser.add_argument("--lang", choices=["zh", "en"], default="zh")
273
+ args = parser.parse_args()
274
+
275
+ result = load_result(args.result)
276
+ specs = build_all(result, lang=args.lang)
277
+ write_adapter_output(args.out, "charts", args.result, specs, locale=args.lang)
278
+ print(f"wrote {args.out} (charts={len(specs['charts'])})")
279
+ return 0
280
+
281
+
282
+ if __name__ == "__main__":
283
+ sys.exit(main())