eduevidence 5.2.0 → 6.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 (386) hide show
  1. package/CONTRIBUTING.md +105 -0
  2. package/README.md +142 -75
  3. package/README.zh-CN.md +73 -30
  4. package/SKILL.md +397 -131
  5. package/agents/openai.yaml +4 -0
  6. package/assets/readme/controlled-execution.svg +34 -0
  7. package/assets/readme/landing-tour.gif +0 -0
  8. package/assets/readme/logo.png +0 -0
  9. package/assets/readme/research-workflow.svg +56 -0
  10. package/assets/readme/studio-graph.png +0 -0
  11. package/assets/readme/studio-overview.png +0 -0
  12. package/assets/readme/studio-reports.png +0 -0
  13. package/assets/readme/studio-tour.gif +0 -0
  14. package/autoevolve/config.yaml +17 -0
  15. package/autoevolve/program.md +25 -0
  16. package/autoevolve/protected.manifest.yaml +34 -0
  17. package/benchmarks/adversarial/cases.jsonl +7 -0
  18. package/benchmarks/evidence-library.json +5268 -0
  19. package/benchmarks/partitions.json +8 -0
  20. package/bin/eduevidence.js +2 -1
  21. package/docs/architecture.md +496 -0
  22. package/docs/autoresearch-evolution-plan.md +2903 -0
  23. package/docs/autoresearch-implementation-status.md +101 -0
  24. package/docs/demo-storyboard.md +20 -0
  25. package/docs/demo-workplace-ai.md +92 -0
  26. package/docs/demo.md +32 -0
  27. package/docs/install-guide.md +150 -0
  28. package/docs/orchestration-role-model.md +1254 -0
  29. package/docs/release-closeout/README.md +17 -0
  30. package/docs/release-closeout/frontend-acceptance.md +23 -0
  31. package/docs/release-closeout/issues.md +19 -0
  32. package/docs/release-closeout/verification.md +28 -0
  33. package/docs/release-contract.md +108 -0
  34. package/docs/research-studio-guide.zh-CN.md +166 -0
  35. package/docs/sciverse-api.md +125 -0
  36. package/eduevidence_cli.py +29 -13
  37. package/engine/_resources.py +13 -0
  38. package/engine/autoevolve/__init__.py +3 -0
  39. package/engine/autoevolve/agent_view.py +167 -0
  40. package/engine/autoevolve/core.py +357 -0
  41. package/engine/autoevolve/events.py +11 -0
  42. package/engine/autoevolve/git_workspace.py +77 -0
  43. package/engine/autoevolve/projection.py +23 -0
  44. package/engine/autoevolve/runner.py +413 -0
  45. package/engine/autoevolve/trust.py +146 -0
  46. package/engine/autoresearch/__init__.py +6 -0
  47. package/engine/autoresearch/commit.py +132 -0
  48. package/engine/autoresearch/contracts.py +126 -0
  49. package/engine/autoresearch/controller.py +207 -0
  50. package/engine/autoresearch/events.py +12 -0
  51. package/engine/autoresearch/gap_priority.py +168 -0
  52. package/engine/autoresearch/projection.py +30 -0
  53. package/engine/autoresearch/research_memory.py +59 -0
  54. package/engine/autoresearch/saturation.py +91 -0
  55. package/engine/briefs.py +2 -1
  56. package/engine/capabilities.py +1 -0
  57. package/engine/contracts.py +3 -1
  58. package/engine/decision_policy.py +96 -0
  59. package/engine/evidence_graph.py +14 -10
  60. package/engine/evidencecore.py +7 -5
  61. package/engine/gaps.py +132 -73
  62. package/engine/ids.py +2 -0
  63. package/engine/judge_pack.py +65 -0
  64. package/engine/library.py +6 -2
  65. package/engine/library_builtin.py +3 -1
  66. package/engine/living.py +36 -5
  67. package/engine/meta_synthesis.py +3 -1
  68. package/engine/migration.py +88 -3
  69. package/engine/orchestration.py +460 -0
  70. package/engine/paths.py +2 -0
  71. package/engine/pilot.py +36 -33
  72. package/engine/project.py +2 -2
  73. package/engine/research_service.py +113 -0
  74. package/engine/studio_read_model.py +400 -0
  75. package/engine/taxonomy.py +211 -0
  76. package/engine/tribunal.py +44 -33
  77. package/engine/update.py +1 -0
  78. package/engine/versions.py +1 -1
  79. package/engine/worker_result.py +109 -0
  80. package/engine/workflows.py +70 -0
  81. package/examples/ai-coding-assistant-evidence/EduEvidence_Report.html +2934 -0
  82. package/examples/ai-coding-assistant-evidence/artifact_manifest.json +15 -0
  83. package/examples/ai-coding-assistant-evidence/citation_check.json +79 -0
  84. package/examples/ai-coding-assistant-evidence/claims.jsonl +12 -0
  85. package/examples/ai-coding-assistant-evidence/evaluation.json +35 -0
  86. package/examples/ai-coding-assistant-evidence/evidence.jsonl +12 -0
  87. package/examples/ai-coding-assistant-evidence/final_verdict.json +107 -0
  88. package/examples/ai-coding-assistant-evidence/frame.json +48 -0
  89. package/examples/ai-coding-assistant-evidence/gate_report.json +101 -0
  90. package/examples/ai-coding-assistant-evidence/intervention.json +51 -0
  91. package/examples/ai-coding-assistant-evidence/methodology.json +36 -0
  92. package/examples/ai-coding-assistant-evidence/raw_verdict.json +86 -0
  93. package/examples/ai-coding-assistant-evidence/report_spec.json +230 -0
  94. package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_academic.html +2934 -0
  95. package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_claude.html +2934 -0
  96. package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_datalab-dark.html +2934 -0
  97. package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_datalab.html +2934 -0
  98. package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_presentation.html +2934 -0
  99. package/examples/ai-coding-assistant-evidence/reports-5themes/report_academic.html +2934 -0
  100. package/examples/ai-coding-assistant-evidence/reports-5themes/report_claude.html +2934 -0
  101. package/examples/ai-coding-assistant-evidence/reports-5themes/report_datalab-dark.html +2934 -0
  102. package/examples/ai-coding-assistant-evidence/reports-5themes/report_datalab.html +2934 -0
  103. package/examples/ai-coding-assistant-evidence/reports-5themes/report_presentation.html +2934 -0
  104. package/examples/ai-coding-assistant-evidence/result.json +1457 -0
  105. package/examples/ai-coding-assistant-evidence/result.zh.json +1457 -0
  106. package/examples/ai-coding-assistant-evidence/skeptic.json +72 -0
  107. package/examples/ai-coding-assistant-evidence/sources.jsonl +8 -0
  108. package/examples/ai-coding-assistant-evidence/verdict.json +107 -0
  109. package/examples/spaced-retrieval-practice/applicability.json +14 -0
  110. package/examples/spaced-retrieval-practice/artifact_manifest.json +15 -0
  111. package/examples/spaced-retrieval-practice/claims.jsonl +3 -0
  112. package/examples/spaced-retrieval-practice/evidence.jsonl +6 -0
  113. package/examples/spaced-retrieval-practice/final_verdict.json +93 -0
  114. package/examples/spaced-retrieval-practice/frame.json +58 -0
  115. package/examples/spaced-retrieval-practice/gate_report.json +101 -0
  116. package/examples/spaced-retrieval-practice/methodology.json +78 -0
  117. package/examples/spaced-retrieval-practice/report_spec.json +212 -0
  118. package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_academic.html +2728 -0
  119. package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_claude.html +2728 -0
  120. package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_datalab-dark.html +2728 -0
  121. package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_datalab.html +2728 -0
  122. package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_presentation.html +2728 -0
  123. package/examples/spaced-retrieval-practice/reports-5themes/report_academic.html +2728 -0
  124. package/examples/spaced-retrieval-practice/reports-5themes/report_claude.html +2728 -0
  125. package/examples/spaced-retrieval-practice/reports-5themes/report_datalab-dark.html +2728 -0
  126. package/examples/spaced-retrieval-practice/reports-5themes/report_datalab.html +2728 -0
  127. package/examples/spaced-retrieval-practice/reports-5themes/report_presentation.html +2728 -0
  128. package/examples/spaced-retrieval-practice/result.json +942 -0
  129. package/examples/spaced-retrieval-practice/result.zh.json +942 -0
  130. package/examples/spaced-retrieval-practice/skeptic.json +70 -0
  131. package/examples/spaced-retrieval-practice/sources.jsonl +7 -0
  132. package/examples/spaced-retrieval-practice/verdict.json +93 -0
  133. package/examples/workplace-ai-assistant/artifact_manifest.json +15 -0
  134. package/examples/workplace-ai-assistant/claims.jsonl +4 -0
  135. package/examples/workplace-ai-assistant/evaluation.json +19 -0
  136. package/examples/workplace-ai-assistant/evidence.jsonl +4 -0
  137. package/examples/workplace-ai-assistant/evidence_graph.json +444 -0
  138. package/examples/workplace-ai-assistant/final_verdict.json +78 -0
  139. package/examples/workplace-ai-assistant/frame.json +41 -0
  140. package/examples/workplace-ai-assistant/gate_report.json +101 -0
  141. package/examples/workplace-ai-assistant/intervention.json +27 -0
  142. package/examples/workplace-ai-assistant/legacy-link-check.json +16 -0
  143. package/examples/workplace-ai-assistant/methodology.json +60 -0
  144. package/examples/workplace-ai-assistant/report_spec.json +224 -0
  145. package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_academic.html +2814 -0
  146. package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_claude.html +2814 -0
  147. package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_datalab-dark.html +2814 -0
  148. package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_datalab.html +2814 -0
  149. package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_presentation.html +2814 -0
  150. package/examples/workplace-ai-assistant/reports-5themes/report_academic.html +2814 -0
  151. package/examples/workplace-ai-assistant/reports-5themes/report_claude.html +2814 -0
  152. package/examples/workplace-ai-assistant/reports-5themes/report_datalab-dark.html +2814 -0
  153. package/examples/workplace-ai-assistant/reports-5themes/report_datalab.html +2814 -0
  154. package/examples/workplace-ai-assistant/reports-5themes/report_presentation.html +2814 -0
  155. package/examples/workplace-ai-assistant/result.json +615 -0
  156. package/examples/workplace-ai-assistant/result.zh.json +615 -0
  157. package/examples/workplace-ai-assistant/search_log.json +19 -0
  158. package/examples/workplace-ai-assistant/skeptic.json +72 -0
  159. package/examples/workplace-ai-assistant/sources.jsonl +3 -0
  160. package/examples/workplace-ai-assistant/validation_result.json +9 -0
  161. package/examples/workplace-ai-assistant/verdict.json +78 -0
  162. package/install.sh +7 -7
  163. package/integrations/agent_mcp.py +2 -2
  164. package/integrations/orchestration_dispatch.py +146 -0
  165. package/package.json +46 -3
  166. package/pyproject.toml +14 -22
  167. package/references/autoresearch.md +30 -0
  168. package/references/evaluation-policy.md +24 -0
  169. package/references/orchestration.md +22 -0
  170. package/references/report-copy-style.md +67 -0
  171. package/references/retrieval-compliance.md +75 -0
  172. package/references/retrieval-protocol.md +20 -0
  173. package/references/scientific-invariants.md +19 -0
  174. package/retrieval/audit.py +178 -0
  175. package/retrieval/fetch.py +96 -0
  176. package/retrieval/sciverse.py +398 -0
  177. package/retrieval/search.py +47 -7
  178. package/schemas/applicability.schema.json +94 -0
  179. package/schemas/chart-spec.schema.json +10 -3
  180. package/schemas/evidence.schema.json +316 -43
  181. package/schemas/fetch-result.schema.json +2 -1
  182. package/schemas/intervention.schema.json +106 -21
  183. package/schemas/report-result.schema.json +12 -4
  184. package/schemas/report-spec.schema.json +98 -100
  185. package/schemas/skeptic.schema.json +86 -0
  186. package/schemas/source.schema.json +21 -2
  187. package/schemas/v2/finding.schema.json +5 -1
  188. package/schemas/v2/methodology-audit.schema.json +5 -1
  189. package/schemas/v2/outcome.schema.json +28 -5
  190. package/schemas/v2/project.schema.json +2 -2
  191. package/schemas/v2/run.schema.json +1 -1
  192. package/schemas/v2/study.schema.json +5 -1
  193. package/schemas/vNext/autoevolve-session.schema.json +34 -0
  194. package/schemas/vNext/eval-snapshot.schema.json +77 -0
  195. package/schemas/vNext/execution-plan.schema.json +50 -0
  196. package/schemas/vNext/gap-priority.schema.json +54 -0
  197. package/schemas/vNext/negative-search-record.schema.json +68 -0
  198. package/schemas/vNext/research-iteration.schema.json +87 -0
  199. package/schemas/vNext/research-strategy.schema.json +62 -0
  200. package/schemas/vNext/skill-experiment.schema.json +90 -0
  201. package/schemas/vNext/task-spec.schema.json +156 -0
  202. package/schemas/vNext/worker-result.schema.json +60 -0
  203. package/schemas/verdict.schema.json +164 -28
  204. package/scripts/benchmark_judge.py +2 -2
  205. package/scripts/benchmark_v3.py +26 -43
  206. package/scripts/build_esl_artifacts.py +4 -4
  207. package/scripts/build_evidence_library.py +2 -2
  208. package/scripts/build_gh_pages.py +98 -0
  209. package/scripts/build_readme_diagrams.py +72 -0
  210. package/scripts/build_report_variants.py +101 -0
  211. package/scripts/build_result.py +74 -9
  212. package/scripts/check_autoresearch_invariants.py +95 -0
  213. package/scripts/check_package_parity.py +85 -0
  214. package/scripts/check_protocol_alignment.py +375 -0
  215. package/scripts/check_versioned_schemas.py +254 -0
  216. package/scripts/claim_audit.py +13 -8
  217. package/scripts/compute_confidence.py +10 -0
  218. package/scripts/daily_evolve.py +30 -0
  219. package/scripts/dashboard_server.py +130 -101
  220. package/scripts/did_regression.py +17 -32
  221. package/scripts/enrich_projects_human_and_lieflat.py +1 -1
  222. package/scripts/evidence_score.py +5 -2
  223. package/scripts/generate_metrics.py +4 -3
  224. package/scripts/generate_new_projects.py +5 -5
  225. package/scripts/orchestrator.py +286 -36
  226. package/scripts/pre_verdict_gate.py +224 -26
  227. package/scripts/quickstart.py +18 -2
  228. package/scripts/rebake_all_5themes.py +1 -2
  229. package/scripts/research_auto_cli.py +475 -0
  230. package/scripts/run_workspace.py +24 -8
  231. package/scripts/search_provenance.py +64 -0
  232. package/scripts/serve_web.py +9 -10
  233. package/scripts/skill_lint.py +1 -1
  234. package/scripts/skill_payload.py +81 -0
  235. package/scripts/test_adversarial_empirical.py +26 -19
  236. package/scripts/validate_schema.py +46 -2
  237. package/scripts/vnext_cli.py +133 -0
  238. package/setup.py +12 -0
  239. package/skill/agents/evaluation-designer.md +20 -4
  240. package/skill/agents/evidence-analyst.md +19 -3
  241. package/skill/agents/evidence-judge.md +50 -2
  242. package/skill/agents/evidence-retriever.md +20 -3
  243. package/skill/agents/intervention-designer.md +20 -4
  244. package/skill/agents/method-reviewer.md +18 -2
  245. package/skill/agents/{education-planner.md → research-planner.md} +19 -3
  246. package/skill/agents/skeptic.md +18 -2
  247. package/skill/roles/registry.yaml +45 -0
  248. package/skill/sub-skills/aihot-trend-analysis/SKILL.md +28 -9
  249. package/skill/sub-skills/contradiction-analysis/SKILL.md +31 -11
  250. package/skill/sub-skills/data-analysis/SKILL.md +34 -15
  251. package/skill/sub-skills/ethics-review/SKILL.md +33 -10
  252. package/skill/sub-skills/evidence-extraction/SKILL.md +29 -11
  253. package/skill/sub-skills/evidence-review/SKILL.md +31 -12
  254. package/skill/sub-skills/gap-analysis/SKILL.md +31 -9
  255. package/skill/sub-skills/literature-review/SKILL.md +35 -14
  256. package/skill/sub-skills/methodology-audit/SKILL.md +29 -12
  257. package/skill/sub-skills/report-generation/SKILL.md +40 -6
  258. package/skill/sub-skills/research-planning/SKILL.md +41 -14
  259. package/skill/sub-skills/study-design/SKILL.md +30 -9
  260. package/skill/task-briefs/adjudicate.md +32 -7
  261. package/skill/task-briefs/applicability.md +38 -0
  262. package/skill/task-briefs/audit.md +32 -7
  263. package/skill/task-briefs/challenge.md +34 -5
  264. package/skill/task-briefs/evaluate.md +30 -5
  265. package/skill/task-briefs/extract.md +31 -8
  266. package/skill/task-briefs/frame.md +39 -10
  267. package/skill/task-briefs/intervene.md +32 -6
  268. package/skill/task-briefs/present.md +32 -8
  269. package/skill/task-briefs/projection.md +37 -0
  270. package/skill/task-briefs/retrieve.md +36 -6
  271. package/skill/workflows/decision-and-pilot.md +85 -0
  272. package/skill/workflows/evaluate-and-update.md +93 -0
  273. package/skill/workflows/evidence-review.md +117 -0
  274. package/visualization/eduevidence-report/assets/base.css +2 -2
  275. package/visualization/eduevidence-report/assets/reader.css +752 -0
  276. package/visualization/eduevidence-report/assets/reader.js +132 -0
  277. package/visualization/eduevidence-report/references/chart-selection-catalog.md +109 -0
  278. package/visualization/eduevidence-report/references/lieflat-composition.md +3 -1
  279. package/visualization/eduevidence-report/scripts/build_figures.py +25 -3
  280. package/visualization/eduevidence-report/scripts/build_infographics.py +5 -1
  281. package/visualization/eduevidence-report/scripts/build_report.py +561 -121
  282. package/visualization/eduevidence-report/scripts/charts_data.py +2 -0
  283. package/visualization/eduevidence-report/scripts/lieflat_engine.py +371 -136
  284. package/visualization/eduevidence-report/scripts/zh_labels.py +80 -1
  285. package/visualization/eduevidence-report/themes/academic.css +1 -1
  286. package/visualization/eduevidence-report/themes/claude.css +1 -1
  287. package/visualization/eduevidence-report/themes/datalab-dark.css +2 -2
  288. package/visualization/eduevidence-report/themes/datalab.css +2 -2
  289. package/visualization/eduevidence-report/themes/presentation.css +2 -2
  290. package/web/README.md +18 -0
  291. package/web/architecture.html +14885 -0
  292. package/web/index.html +53 -0
  293. package/web/studio/THIRD_PARTY_LICENSES.txt +146 -0
  294. package/web/studio/assets/index-B8tkF44Q.css +1 -0
  295. package/web/studio/assets/index-CQ6Keoyc.js +230 -0
  296. package/web/studio/config.json +1 -0
  297. package/web/studio/index.html +14 -0
  298. package/engine/__pycache__/__init__.cpython-312.pyc +0 -0
  299. package/engine/__pycache__/analysis.cpython-312.pyc +0 -0
  300. package/engine/__pycache__/bias.cpython-312.pyc +0 -0
  301. package/engine/__pycache__/briefs.cpython-312.pyc +0 -0
  302. package/engine/__pycache__/capabilities.cpython-312.pyc +0 -0
  303. package/engine/__pycache__/citation_check.cpython-312.pyc +0 -0
  304. package/engine/__pycache__/contracts.cpython-312.pyc +0 -0
  305. package/engine/__pycache__/datasets.cpython-312.pyc +0 -0
  306. package/engine/__pycache__/events.cpython-312.pyc +0 -0
  307. package/engine/__pycache__/evidence_graph.cpython-312.pyc +0 -0
  308. package/engine/__pycache__/evidence_review.cpython-312.pyc +0 -0
  309. package/engine/__pycache__/evidencecore.cpython-312.pyc +0 -0
  310. package/engine/__pycache__/gap_lens.cpython-312.pyc +0 -0
  311. package/engine/__pycache__/gaps.cpython-312.pyc +0 -0
  312. package/engine/__pycache__/graph_store.cpython-312.pyc +0 -0
  313. package/engine/__pycache__/graph_validate.cpython-312.pyc +0 -0
  314. package/engine/__pycache__/ids.cpython-312.pyc +0 -0
  315. package/engine/__pycache__/library.cpython-312.pyc +0 -0
  316. package/engine/__pycache__/library_builtin.cpython-312.pyc +0 -0
  317. package/engine/__pycache__/living.cpython-312.pyc +0 -0
  318. package/engine/__pycache__/log.cpython-312.pyc +0 -0
  319. package/engine/__pycache__/meta_analysis.cpython-312.pyc +0 -0
  320. package/engine/__pycache__/meta_synthesis.cpython-312.pyc +0 -0
  321. package/engine/__pycache__/migration.cpython-312.pyc +0 -0
  322. package/engine/__pycache__/mode_router.cpython-312.pyc +0 -0
  323. package/engine/__pycache__/paths.cpython-312.pyc +0 -0
  324. package/engine/__pycache__/pilot.cpython-312.pyc +0 -0
  325. package/engine/__pycache__/planner.cpython-312.pyc +0 -0
  326. package/engine/__pycache__/project.cpython-312.pyc +0 -0
  327. package/engine/__pycache__/projections.cpython-312.pyc +0 -0
  328. package/engine/__pycache__/robustness.cpython-312.pyc +0 -0
  329. package/engine/__pycache__/run.cpython-312.pyc +0 -0
  330. package/engine/__pycache__/semantics.cpython-312.pyc +0 -0
  331. package/engine/__pycache__/study_design.cpython-312.pyc +0 -0
  332. package/engine/__pycache__/synthesis.cpython-312.pyc +0 -0
  333. package/engine/__pycache__/tribunal.cpython-312.pyc +0 -0
  334. package/engine/__pycache__/update.cpython-312.pyc +0 -0
  335. package/engine/__pycache__/versions.cpython-312.pyc +0 -0
  336. package/integrations/__pycache__/__init__.cpython-312.pyc +0 -0
  337. package/integrations/__pycache__/agent_mcp.cpython-312.pyc +0 -0
  338. package/integrations/__pycache__/smart_web_fetch.cpython-312.pyc +0 -0
  339. package/retrieval/__pycache__/__init__.cpython-312.pyc +0 -0
  340. package/retrieval/__pycache__/corpus_store.cpython-312.pyc +0 -0
  341. package/retrieval/__pycache__/dedupe.cpython-312.pyc +0 -0
  342. package/retrieval/__pycache__/failures.cpython-312.pyc +0 -0
  343. package/retrieval/__pycache__/fetch.cpython-312.pyc +0 -0
  344. package/retrieval/__pycache__/search.cpython-312.pyc +0 -0
  345. package/retrieval/__pycache__/source.cpython-312.pyc +0 -0
  346. package/retrieval/__pycache__/validate.cpython-312.pyc +0 -0
  347. package/scripts/__pycache__/__init__.cpython-312.pyc +0 -0
  348. package/scripts/__pycache__/benchmark.cpython-312.pyc +0 -0
  349. package/scripts/__pycache__/benchmark_evaluator.cpython-312.pyc +0 -0
  350. package/scripts/__pycache__/benchmark_judge.cpython-312.pyc +0 -0
  351. package/scripts/__pycache__/benchmark_routing.cpython-312.pyc +0 -0
  352. package/scripts/__pycache__/benchmark_v2.cpython-312.pyc +0 -0
  353. package/scripts/__pycache__/benchmark_v3.cpython-312.pyc +0 -0
  354. package/scripts/__pycache__/build_result.cpython-312.pyc +0 -0
  355. package/scripts/__pycache__/claim_audit.cpython-312.pyc +0 -0
  356. package/scripts/__pycache__/complexity_gate.cpython-312.pyc +0 -0
  357. package/scripts/__pycache__/compute_confidence.cpython-312.pyc +0 -0
  358. package/scripts/__pycache__/dashboard_server.cpython-312.pyc +0 -0
  359. package/scripts/__pycache__/did_regression.cpython-312.pyc +0 -0
  360. package/scripts/__pycache__/effect_calculator.cpython-312.pyc +0 -0
  361. package/scripts/__pycache__/evidence_matrix.cpython-312.pyc +0 -0
  362. package/scripts/__pycache__/evidence_score.cpython-312.pyc +0 -0
  363. package/scripts/__pycache__/evidence_semantics.cpython-312.pyc +0 -0
  364. package/scripts/__pycache__/fetch_benchmark.cpython-312.pyc +0 -0
  365. package/scripts/__pycache__/lint_report_layout.cpython-312.pyc +0 -0
  366. package/scripts/__pycache__/orchestrator.cpython-312.pyc +0 -0
  367. package/scripts/__pycache__/pre_verdict_gate.cpython-312.pyc +0 -0
  368. package/scripts/__pycache__/recompute_demo_quality.cpython-312.pyc +0 -0
  369. package/scripts/__pycache__/render_report.cpython-312.pyc +0 -0
  370. package/scripts/__pycache__/render_report_html.cpython-312.pyc +0 -0
  371. package/scripts/__pycache__/run_workspace.cpython-312.pyc +0 -0
  372. package/scripts/__pycache__/skill_lint.cpython-312.pyc +0 -0
  373. package/scripts/__pycache__/startup_probe.cpython-312.pyc +0 -0
  374. package/scripts/__pycache__/sync_killer_demo_report.cpython-312.pyc +0 -0
  375. package/scripts/__pycache__/test_adversarial_empirical.cpython-312-pytest-9.0.2.pyc +0 -0
  376. package/scripts/__pycache__/test_adversarial_empirical.cpython-312-pytest-9.1.1.pyc +0 -0
  377. package/scripts/__pycache__/validate_schema.cpython-312.pyc +0 -0
  378. package/visualization/eduevidence-report/scripts/__pycache__/adapter_contract.cpython-312.pyc +0 -0
  379. package/visualization/eduevidence-report/scripts/__pycache__/build_artifact_manifest.cpython-312.pyc +0 -0
  380. package/visualization/eduevidence-report/scripts/__pycache__/build_charts.cpython-312.pyc +0 -0
  381. package/visualization/eduevidence-report/scripts/__pycache__/build_figures.cpython-312.pyc +0 -0
  382. package/visualization/eduevidence-report/scripts/__pycache__/build_infographics.cpython-312.pyc +0 -0
  383. package/visualization/eduevidence-report/scripts/__pycache__/build_report.cpython-312.pyc +0 -0
  384. package/visualization/eduevidence-report/scripts/__pycache__/charts_data.cpython-312.pyc +0 -0
  385. package/visualization/eduevidence-report/scripts/__pycache__/lieflat_engine.cpython-312.pyc +0 -0
  386. package/visualization/eduevidence-report/scripts/__pycache__/zh_labels.cpython-312.pyc +0 -0
@@ -0,0 +1,398 @@
1
+ #!/usr/bin/env python3
2
+ """sciverse.py — Sciverse Open Platform retrieval channel (key-based).
3
+
4
+ Sciverse exposes citation-grade academic retrieval to agents through four
5
+ endpoints used here (canonical spec: openapi.yaml v0.14.2 of
6
+ opendatalab/Sciverse-Agent-Tools, archived in docs/sciverse-api.md):
7
+
8
+ POST /meta-search structured metadata search -> Source-level hits
9
+ POST /agentic-search semantic chunk retrieval -> chunk locators
10
+ GET /content read original text slice -> evidence content
11
+ POST /meta-paper-relations citations / references -> citation chain
12
+
13
+ Scientific discipline encoded here (RULE 2): an /agentic-search chunk is a
14
+ discovery locator, never evidence. It must be expanded through ``/content``
15
+ and pass the fetch/validate gate before any extraction stage may use it.
16
+
17
+ The module is stdlib-only and never raises for network or API content issues:
18
+ every failure is reported as a typed status so the pipeline degrades cleanly
19
+ to the zero-config channels.
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import json
24
+ import os
25
+ import urllib.error
26
+ import urllib.parse
27
+ import urllib.request
28
+ from dataclasses import dataclass, field
29
+ from typing import Any, List, Optional
30
+
31
+ from engine.log import get_log
32
+
33
+ log = get_log("sciverse")
34
+
35
+ BASE_URL = os.environ.get("SCIVERSE_BASE_URL", "https://api.sciverse.space").rstrip("/")
36
+ TOKEN_ENV = "SCIVERSE_API_TOKEN"
37
+ DEFAULT_TIMEOUT = 20
38
+ USER_AGENT = "EduEvidence-Research-Agent/6.1 (+https://github.com/37chengshan/eduevidence)"
39
+
40
+ #: Reading window used when expanding a chunk locator into evidence content.
41
+ DEFAULT_CONTENT_LIMIT = 4096
42
+ MAX_CONTENT_LIMIT = 16384 # LLM-facing ceiling recommended by the spec
43
+
44
+ #: Typed failure statuses (never exceptions) surfaced to callers and audits.
45
+ STATUS_OK = "ok"
46
+ STATUS_UNAVAILABLE = "SCIVERSE_UNAVAILABLE" # no token configured
47
+ STATUS_UNAUTHORIZED = "SCIVERSE_UNAUTHORIZED" # 401/403
48
+ STATUS_BAD_REQUEST = "SCIVERSE_BAD_REQUEST" # 400/404/429
49
+ STATUS_UPSTREAM = "SCIVERSE_UPSTREAM_ERROR" # 502/503/5xx
50
+ STATUS_NETWORK = "SCIVERSE_NETWORK_ERROR" # timeout / DNS / TLS
51
+
52
+
53
+ def api_token() -> str:
54
+ """Bearer token from the environment; empty string means unavailable."""
55
+ return os.environ.get(TOKEN_ENV, "").strip()
56
+
57
+
58
+ def available() -> bool:
59
+ return bool(api_token())
60
+
61
+
62
+ @dataclass
63
+ class SciverseResponse:
64
+ """One endpoint call outcome. ``data`` is empty unless ``ok`` is True."""
65
+
66
+ status: str
67
+ data: dict[str, Any] = field(default_factory=dict)
68
+ error: str = ""
69
+ http_status: Optional[int] = None
70
+
71
+ @property
72
+ def ok(self) -> bool:
73
+ return self.status == STATUS_OK
74
+
75
+
76
+ def _classify(http_status: int) -> str:
77
+ if http_status in (401, 403):
78
+ return STATUS_UNAUTHORIZED
79
+ if http_status in (400, 404, 429):
80
+ return STATUS_BAD_REQUEST
81
+ if http_status >= 500:
82
+ return STATUS_UPSTREAM
83
+ return STATUS_UPSTREAM
84
+
85
+
86
+ def _error_message(body: str) -> str:
87
+ """Extract ApiError.message without leaking tokens or full payloads."""
88
+ try:
89
+ payload = json.loads(body)
90
+ except (ValueError, TypeError):
91
+ return body.strip()[:200]
92
+ if isinstance(payload, dict):
93
+ for key in ("message", "error", "detail"):
94
+ value = payload.get(key)
95
+ if isinstance(value, str) and value.strip():
96
+ return value.strip()[:200]
97
+ return ""
98
+
99
+
100
+ def _request(
101
+ method: str,
102
+ path: str,
103
+ *,
104
+ params: Optional[dict[str, Any]] = None,
105
+ payload: Optional[dict[str, Any]] = None,
106
+ timeout: int = DEFAULT_TIMEOUT,
107
+ ) -> SciverseResponse:
108
+ """Single typed HTTP call. Never raises; never logs the token."""
109
+ token = api_token()
110
+ if not token:
111
+ return SciverseResponse(STATUS_UNAVAILABLE, error=f"{TOKEN_ENV} is not set")
112
+
113
+ url = BASE_URL + path
114
+ if params:
115
+ clean = {k: v for k, v in params.items() if v is not None}
116
+ if clean:
117
+ url = f"{url}?{urllib.parse.urlencode(clean)}"
118
+
119
+ headers = {
120
+ "Authorization": f"Bearer {token}",
121
+ "Accept": "application/json",
122
+ "User-Agent": USER_AGENT,
123
+ }
124
+ body_bytes: Optional[bytes] = None
125
+ if payload is not None:
126
+ body_bytes = json.dumps(payload).encode("utf-8")
127
+ headers["Content-Type"] = "application/json"
128
+
129
+ req = urllib.request.Request(url, data=body_bytes, headers=headers, method=method)
130
+ try:
131
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
132
+ raw = resp.read().decode("utf-8", errors="replace")
133
+ data = json.loads(raw) if raw.strip() else {}
134
+ return SciverseResponse(STATUS_OK, data=data if isinstance(data, dict) else {},
135
+ http_status=resp.status)
136
+ except urllib.error.HTTPError as exc:
137
+ detail = ""
138
+ try:
139
+ detail = _error_message(exc.read().decode("utf-8", errors="replace"))
140
+ except Exception: # pragma: no cover - body already consumed/closed
141
+ detail = ""
142
+ status = _classify(exc.code)
143
+ log.debug("sciverse http error path=%s status=%s", path, exc.code)
144
+ return SciverseResponse(status, error=detail or f"HTTP {exc.code}", http_status=exc.code)
145
+ except Exception as exc: # timeouts, DNS, TLS, decode
146
+ log.debug("sciverse network error path=%s err=%s", path, type(exc).__name__)
147
+ return SciverseResponse(STATUS_NETWORK, error=type(exc).__name__)
148
+
149
+
150
+ # ---------------------------------------------------------------------------
151
+ # Endpoint wrappers (core four)
152
+ # ---------------------------------------------------------------------------
153
+
154
+ def meta_search(
155
+ query: str,
156
+ *,
157
+ limit: int = 10,
158
+ year_from: Optional[int] = None,
159
+ year_to: Optional[int] = None,
160
+ filters: Optional[list[dict[str, Any]]] = None,
161
+ collection: str = "papers",
162
+ ) -> SciverseResponse:
163
+ """Structured metadata search (``/meta-search``). Source-level hits.
164
+
165
+ Returns paper metadata including ``unique_id`` (always) and ``doc_id``
166
+ (only when full text exists) — both are required for downstream
167
+ ``/content`` and ``/meta-paper-relations`` calls.
168
+ """
169
+ payload: dict[str, Any] = {
170
+ "collection": collection,
171
+ "query": query,
172
+ "page": 1,
173
+ "page_size": max(1, min(limit, 50)),
174
+ }
175
+ advanced: list[dict[str, Any]] = list(filters or [])
176
+ if year_from is not None:
177
+ advanced.append({"field": "publication_published_year",
178
+ "operator": "FILTER_OP_GTE", "value": year_from})
179
+ if year_to is not None:
180
+ advanced.append({"field": "publication_published_year",
181
+ "operator": "FILTER_OP_LTE", "value": year_to})
182
+ if advanced:
183
+ payload["filters_advanced"] = advanced
184
+ return _request("POST", "/meta-search", payload=payload)
185
+
186
+
187
+ def agentic_search(
188
+ query: str,
189
+ *,
190
+ top_k: int = 10,
191
+ mode: str = "balanced",
192
+ filters: Optional[dict[str, Any]] = None,
193
+ ) -> SciverseResponse:
194
+ """Natural-language chunk retrieval (``/agentic-search``).
195
+
196
+ Each hit is a locator (chunk_id/doc_id/offset/score) — a discovery aid.
197
+ ``offset`` is a Unicode code-point offset usable directly by
198
+ :func:`read_content`.
199
+ """
200
+ payload: dict[str, Any] = {
201
+ "query": query,
202
+ "top_k": max(1, min(top_k, 100)),
203
+ "mode": mode if mode in ("fast", "balanced", "quality") else "balanced",
204
+ }
205
+ if filters:
206
+ payload["filters"] = filters
207
+ return _request("POST", "/agentic-search", payload=payload)
208
+
209
+
210
+ def read_content(
211
+ doc_id: str,
212
+ *,
213
+ offset: int = 0,
214
+ limit: int = DEFAULT_CONTENT_LIMIT,
215
+ ) -> SciverseResponse:
216
+ """Read original text by code-point range (``/content``).
217
+
218
+ ``offset`` is always sent explicitly: omitting it makes the server return
219
+ the whole document and ignore ``limit``.
220
+ """
221
+ return _request("GET", "/content", params={
222
+ "doc_id": doc_id,
223
+ "offset": max(0, int(offset)),
224
+ "limit": max(1, min(int(limit), MAX_CONTENT_LIMIT)),
225
+ })
226
+
227
+
228
+ def paper_relations(
229
+ unique_id: str,
230
+ *,
231
+ relation: str = "REFERENCES",
232
+ page: int = 1,
233
+ page_size: int = 25,
234
+ ) -> SciverseResponse:
235
+ """Paginate citations / references / related works (``/meta-paper-relations``).
236
+
237
+ ``unique_id`` (not ``doc_id``) identifies the target paper. CITATIONS is
238
+ incoming (who cites me); REFERENCES is outgoing (who I cite).
239
+ """
240
+ relation_name = relation.upper()
241
+ if relation_name not in ("CITATIONS", "REFERENCES", "RELATED_WORKS"):
242
+ relation_name = "REFERENCES"
243
+ return _request("POST", "/meta-paper-relations", payload={
244
+ "unique_id": unique_id,
245
+ "relation": relation_name,
246
+ "page": max(1, int(page)),
247
+ "page_size": max(1, min(int(page_size), 200)),
248
+ })
249
+
250
+
251
+ # ---------------------------------------------------------------------------
252
+ # Chunk / relation record helpers (run-workspace working sets)
253
+ # ---------------------------------------------------------------------------
254
+
255
+ def chunk_records(response: SciverseResponse, *, query_id: str = "") -> list[dict[str, Any]]:
256
+ """Normalize ``/agentic-search`` hits into chunk-locator records.
257
+
258
+ These records are a working set, never evidence: they carry the locator
259
+ (doc_id/offset) that :func:`read_content` needs.
260
+ """
261
+ if not response.ok:
262
+ return []
263
+ records: list[dict[str, Any]] = []
264
+ for hit in response.data.get("hits") or []:
265
+ if not isinstance(hit, dict):
266
+ continue
267
+ doc_id = str(hit.get("doc_id") or "")
268
+ if not doc_id:
269
+ continue
270
+ records.append({
271
+ "chunk_id": str(hit.get("chunk_id") or ""),
272
+ "doc_id": doc_id,
273
+ "offset": int(hit.get("offset") or 0),
274
+ "score": float(hit.get("score") or 0.0),
275
+ "chunk": str(hit.get("chunk") or ""),
276
+ "title": str(hit.get("title") or ""),
277
+ "page_no": hit.get("page_no"),
278
+ "source_type": str(hit.get("source_type") or ""),
279
+ "query_id": query_id,
280
+ })
281
+ return records
282
+
283
+
284
+ def relation_records(response: SciverseResponse, *, unique_id: str = "",
285
+ relation: str = "") -> list[dict[str, Any]]:
286
+ """Normalize a ``/meta-paper-relations`` page into citation-chain records."""
287
+ if not response.ok:
288
+ return []
289
+ records: list[dict[str, Any]] = []
290
+ for item in response.data.get("items") or []:
291
+ if not isinstance(item, dict):
292
+ continue
293
+ records.append({
294
+ "source_unique_id": unique_id,
295
+ "relation": relation,
296
+ "id": str(item.get("id") or ""),
297
+ "id_type": str(item.get("id_type") or ""),
298
+ "title": str(item.get("title") or ""),
299
+ })
300
+ return records
301
+
302
+
303
+ # ---------------------------------------------------------------------------
304
+ # Router provider (priority: key-based academic channel)
305
+ # ---------------------------------------------------------------------------
306
+
307
+ class SciverseProvider:
308
+ """MultiSearchRouter-compatible provider over ``/meta-search`` + ``/agentic-search``.
309
+
310
+ ``meta_search`` is the Source-level channel; ``agentic`` (opt-in via
311
+ ``include_chunks``) additionally records chunk locators on the hit so the
312
+ run workspace can expand them through ``/content``.
313
+ """
314
+
315
+ name = "sciverse"
316
+
317
+ def __init__(self, *, token: Optional[str] = None, include_chunks: bool = True):
318
+ if token is not None:
319
+ os.environ[TOKEN_ENV] = token
320
+ self.include_chunks = include_chunks
321
+
322
+ def is_available(self) -> bool:
323
+ return available()
324
+
325
+ def search(self, query: str, limit: int = 10) -> List[Any]:
326
+ from retrieval.search import SearchHit
327
+
328
+ if not self.is_available():
329
+ return []
330
+ response = meta_search(query, limit=limit)
331
+ if not response.ok:
332
+ log.debug("sciverse meta-search unavailable status=%s", response.status)
333
+ return []
334
+
335
+ chunk_index: dict[str, dict[str, Any]] = {}
336
+ if self.include_chunks:
337
+ semantic = agentic_search(query, top_k=max(1, min(limit, 50)))
338
+ for record in chunk_records(semantic):
339
+ chunk_index.setdefault(record["doc_id"], record)
340
+
341
+ hits: List[Any] = []
342
+ for item in response.data.get("results") or []:
343
+ if not isinstance(item, dict):
344
+ continue
345
+ title = str(item.get("title") or "Untitled")
346
+ doi = _normalize_doi(item.get("doi"))
347
+ doc_id = str(item.get("doc_id") or "")
348
+ unique_id = str(item.get("unique_id") or "")
349
+ locator = chunk_index.get(doc_id, {}) if doc_id else {}
350
+
351
+ authors = [
352
+ str(a.get("name")) for a in (item.get("author") or [])
353
+ if isinstance(a, dict) and a.get("name")
354
+ ]
355
+ hit = SearchHit(
356
+ title=title,
357
+ url=_source_url(doi),
358
+ snippet=str(item.get("abstract") or title),
359
+ provider=self.name,
360
+ doi=doi,
361
+ year=item.get("publication_published_year"),
362
+ citation_count=item.get("citation_count"),
363
+ authors=authors[:5],
364
+ is_academic=True,
365
+ score=1.35,
366
+ doc_id=doc_id or None,
367
+ chunk_id=locator.get("chunk_id"),
368
+ offset=locator.get("offset"),
369
+ unique_id=unique_id or None,
370
+ )
371
+ hits.append(hit)
372
+ return hits
373
+
374
+
375
+ def _normalize_doi(value: Any) -> Optional[str]:
376
+ if not isinstance(value, str) or not value.strip():
377
+ return None
378
+ doi = value.strip()
379
+ for prefix in ("https://doi.org/", "http://doi.org/", "doi:"):
380
+ if doi.lower().startswith(prefix):
381
+ doi = doi[len(prefix):]
382
+ return doi or None
383
+
384
+
385
+ def _source_url(doi: Optional[str]) -> str:
386
+ """Canonical citation pointer. Never fabricate a URL for a DOI-less record:
387
+ the empty string marks it as ``needs_manual_location`` downstream."""
388
+ return f"https://doi.org/{doi}" if doi else ""
389
+
390
+
391
+ __all__ = [
392
+ "BASE_URL", "TOKEN_ENV", "DEFAULT_CONTENT_LIMIT", "MAX_CONTENT_LIMIT",
393
+ "STATUS_OK", "STATUS_UNAVAILABLE", "STATUS_UNAUTHORIZED", "STATUS_BAD_REQUEST",
394
+ "STATUS_UPSTREAM", "STATUS_NETWORK",
395
+ "SciverseResponse", "SciverseProvider",
396
+ "api_token", "available", "meta_search", "agentic_search", "read_content",
397
+ "paper_relations", "chunk_records", "relation_records",
398
+ ]
@@ -11,12 +11,10 @@ Supports both:
11
11
  - DuckDuckGo (Zero-auth general web search fallback)
12
12
 
13
13
  2. User-Configured Search Channels (Key-Based):
14
+ - Sciverse (SCIVERSE_API_TOKEN) — citation-grade academic retrieval with
15
+ doc_id/chunk offset provenance (/meta-search + /agentic-search)
14
16
  - Tavily (TAVILY_API_KEY)
15
17
  - Brave Search (BRAVE_API_KEY)
16
- - SerpAPI (SERPAPI_API_KEY)
17
- - Serper (SERPER_API_KEY)
18
- - Exa (EXA_API_KEY)
19
- - Bocha (BOCHA_API_KEY)
20
18
 
21
19
  Pure stdlib HTTP client with robust error handling, SSL verification,
22
20
  timeout safeguards, and intelligent multi-source deduplication.
@@ -52,6 +50,16 @@ class SearchHit:
52
50
  authors: List[str] = field(default_factory=list)
53
51
  is_academic: bool = False
54
52
  score: float = 1.0
53
+ #: Sciverse locators (None for every other provider). ``doc_id`` addresses
54
+ #: the full-text artifact for /content; ``chunk_id``/``offset`` locate the
55
+ #: retrieved passage (Unicode code points) inside it. They are discovery
56
+ #: locators, never evidence (RULE 2).
57
+ doc_id: Optional[str] = None
58
+ chunk_id: Optional[str] = None
59
+ offset: Optional[int] = None
60
+ #: Sciverse metadata identifier (always present); required by
61
+ #: /meta-paper-relations to page the citation chain.
62
+ unique_id: Optional[str] = None
55
63
 
56
64
  def to_dict(self) -> dict:
57
65
  return asdict(self)
@@ -389,6 +397,15 @@ class MultiSearchRouter:
389
397
  """Orchestrates zero-config and configured search channels with deduplication."""
390
398
 
391
399
  def __init__(self):
400
+ self.academic_key_providers = []
401
+ try: # optional channel: activating it must never break the router
402
+ from retrieval.sciverse import SciverseProvider
403
+
404
+ provider = SciverseProvider()
405
+ if provider.is_available():
406
+ self.academic_key_providers.append(provider)
407
+ except Exception: # pragma: no cover - import/环境异常时静默降级
408
+ log.debug("sciverse provider unavailable at router init", exc_info=True)
392
409
  self.zero_config_academic = [
393
410
  OpenAlexProvider(),
394
411
  SemanticScholarProvider(),
@@ -406,6 +423,9 @@ class MultiSearchRouter:
406
423
 
407
424
  def get_provider_status(self) -> List[dict]:
408
425
  status = []
426
+ for p in self.academic_key_providers:
427
+ status.append({"provider": p.name, "type": "academic_key",
428
+ "status": "active", "requires_key": True})
409
429
  for p in self.zero_config_academic:
410
430
  status.append({"provider": p.name, "type": "academic_zero_config", "status": "active", "requires_key": False})
411
431
  for p in self.zero_config_web:
@@ -423,6 +443,7 @@ class MultiSearchRouter:
423
443
  def search(self, query: str, limit: int = 15, academic_only: bool = False) -> List[SearchHit]:
424
444
  all_hits: List[SearchHit] = []
425
445
  seen_urls = set()
446
+ seen_locators: set[str] = set()
426
447
 
427
448
  # 1. Try configured high-priority commercial providers if active
428
449
  if not academic_only:
@@ -437,7 +458,26 @@ class MultiSearchRouter:
437
458
  except Exception:
438
459
  pass
439
460
 
440
- # 2. Run Zero-Config Academic Providers
461
+ # 2. Run key-based academic providers (Sciverse: citation-grade
462
+ # retrieval with doc_id/offset provenance) before zero-config ones.
463
+ for kp in self.academic_key_providers:
464
+ try:
465
+ hits = kp.search(query, limit=limit)
466
+ for h in hits:
467
+ locator = getattr(h, "doc_id", None)
468
+ if locator and locator in seen_locators:
469
+ continue
470
+ if h.url and h.url in seen_urls:
471
+ continue
472
+ if h.url:
473
+ seen_urls.add(h.url)
474
+ if locator:
475
+ seen_locators.add(locator)
476
+ all_hits.append(h)
477
+ except Exception:
478
+ pass
479
+
480
+ # 3. Run Zero-Config Academic Providers
441
481
  for ap in self.zero_config_academic:
442
482
  try:
443
483
  hits = ap.search(query, limit=limit)
@@ -448,7 +488,7 @@ class MultiSearchRouter:
448
488
  except Exception:
449
489
  pass
450
490
 
451
- # 3. Run Zero-Config Web/Dynamic Providers if not academic_only
491
+ # 4. Run Zero-Config Web/Dynamic Providers if not academic_only
452
492
  if not academic_only:
453
493
  for wp in self.zero_config_web:
454
494
  try:
@@ -460,7 +500,7 @@ class MultiSearchRouter:
460
500
  except Exception:
461
501
  pass
462
502
 
463
- # 4. Fallback to verified offline domain corpus if external search returned 0 hits
503
+ # 5. Fallback to verified offline domain corpus if external search returned 0 hits
464
504
  if not all_hits:
465
505
  try:
466
506
  from retrieval.corpus_store import DomainCorpusStore
@@ -0,0 +1,94 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://github.com/37chengshan/eduevidence/schemas/applicability.schema.json",
4
+ "title": "ApplicabilityAssessment",
5
+ "description": "Stage-7 deliverable: who the evidence applies to, in which contexts, for which outcomes, and where it stops. Either an assessment or an explicit NOT_CAPTURED record is required - silence used to render as an empty report section with no gate failing.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": [
9
+ "status"
10
+ ],
11
+ "properties": {
12
+ "status": {
13
+ "type": "string",
14
+ "enum": [
15
+ "ASSESSED",
16
+ "NOT_CAPTURED"
17
+ ]
18
+ },
19
+ "reason": {
20
+ "type": "string"
21
+ },
22
+ "who": {
23
+ "type": "string"
24
+ },
25
+ "suitable_for": {
26
+ "type": "string"
27
+ },
28
+ "not_suitable_for": {
29
+ "type": "string"
30
+ },
31
+ "which_course": {
32
+ "type": "string"
33
+ },
34
+ "which_outcome": {
35
+ "type": "string"
36
+ },
37
+ "conditions": {
38
+ "type": [
39
+ "string",
40
+ "array"
41
+ ]
42
+ },
43
+ "target_population": {
44
+ "type": "string"
45
+ },
46
+ "target_context": {
47
+ "type": "string"
48
+ },
49
+ "boundary": {
50
+ "type": "string"
51
+ },
52
+ "notes": {
53
+ "type": "string"
54
+ },
55
+ "excluded_populations": {
56
+ "type": [
57
+ "string",
58
+ "array"
59
+ ]
60
+ },
61
+ "uncertainty": {
62
+ "type": [
63
+ "string",
64
+ "array"
65
+ ]
66
+ },
67
+ "extensions": {
68
+ "type": "object",
69
+ "additionalProperties": true
70
+ },
71
+ "required_conditions": {
72
+ "type": [
73
+ "string",
74
+ "array"
75
+ ],
76
+ "description": "Conditions that must hold for the evidence to apply."
77
+ },
78
+ "evidence_boundary": {
79
+ "type": "string"
80
+ },
81
+ "transfer_boundaries": {
82
+ "type": [
83
+ "string",
84
+ "array"
85
+ ]
86
+ },
87
+ "outcome_limits": {
88
+ "type": [
89
+ "string",
90
+ "array"
91
+ ]
92
+ }
93
+ }
94
+ }
@@ -2,14 +2,14 @@
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "$id": "https://github.com/37chengshan/eduevidence/schemas/chart-spec.schema.json",
4
4
  "title": "ChartSpec",
5
- "description": "Chart spec produced by visualization build_charts.py. integrity is a per-check map with PASS/FAIL/NOT_CHECKED strings (P0-2 rewrite to match the real producer).",
5
+ "description": "One chart record inside a ReportSpec. Producer: visualization/eduevidence-report/scripts/build_report.py::build_report_spec. Kept in sync with the producer by scripts/check_protocol_alignment.py.",
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
8
  "required": [
9
9
  "chart_id",
10
10
  "purpose",
11
11
  "engine",
12
- "chart_type"
12
+ "data_ref"
13
13
  ],
14
14
  "properties": {
15
15
  "chart_id": {
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "chart_type": {
30
30
  "type": "string",
31
- "minLength": 1
31
+ "description": "Optional chart family label. The Lieflat gallery carries its own type on result.visual_layout; this field is only used by callers that build chart specs by hand."
32
32
  },
33
33
  "semantic_basis": {
34
34
  "type": "string"
@@ -83,6 +83,13 @@
83
83
  ]
84
84
  }
85
85
  }
86
+ },
87
+ "data_ref": {
88
+ "type": [
89
+ "string",
90
+ "null"
91
+ ],
92
+ "description": "Pointer to the data the chart reads, or null when the chart is rendered inline from result.json by the report builder."
86
93
  }
87
94
  }
88
95
  }