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,182 @@
1
+ """engine/evidencecore.py - EvidenceCore v4 domain registry (抽象第一步).
2
+
3
+ v4 领域包机制:domains/ 注册表 + 领域契约加载 + frame 校验。
4
+
5
+ - 领域无关常量:DECISION_STATES(四态决策)、PROTOCOL_STEPS(9 步协议)。
6
+ - list_domains():按注册表顺序列出领域。
7
+ - load_domain(domain_id):读取并校验领域条目——所有引用契约必须真实存在。
8
+ - validate_frame(domain_id, frame_dict):用该领域的 frame schema 校验
9
+ frame(education 复用现有 scripts.validate_schema 校验
10
+ schemas/education-frame.schema.json;policy 用 domains/policy/frame.schema.json)。
11
+
12
+ education 域只是"指向现有契约"的注册:不新增任何逻辑路径、不引入新 schema
13
+ 或新校验器。领域选择(domain select)由主 agent 接 CLI 完成,引擎层不做选择。
14
+
15
+ 路径解析:当前按仓库布局(domains/ 在仓库根目录)解析;wheel 安装场景的
16
+ share/ 回退留给后续步骤(pyproject data-files 未包含 domains/)。
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import json
22
+ from pathlib import Path
23
+ from typing import Any
24
+
25
+ REPO_ROOT = Path(__file__).resolve().parent.parent
26
+
27
+
28
+ def _resolve_domains_dir() -> Path:
29
+ """Repository layout first; wheel-installed share/ layout as fallback."""
30
+ repo = REPO_ROOT / "domains"
31
+ if repo.is_dir():
32
+ return repo
33
+ import sys
34
+ share = Path(sys.prefix) / "share" / "eduevidence" / "domains"
35
+ if share.is_dir():
36
+ return share
37
+ return repo
38
+
39
+
40
+ DOMAINS_DIR = _resolve_domains_dir()
41
+ REGISTRY_FILE = DOMAINS_DIR / "manifest.json"
42
+
43
+ #: 领域无关:四态决策(README: ADOPT / PILOT / REJECT / INSUFFICIENT EVIDENCE)。
44
+ DECISION_STATES = ("adopt", "pilot", "reject", "insufficient_evidence")
45
+
46
+ #: 领域无关:9 步 EvidenceFlow 协议(SKILL.md Research Core 6 + Decision Extension 3)。
47
+ PROTOCOL_STEPS = (
48
+ "Frame", "Retrieve", "Extract", "Challenge", "Audit",
49
+ "Adjudicate", "Applicability", "Intervene", "Evaluate",
50
+ )
51
+
52
+ #: 注册表条目必备字段(domains/manifest.json)。
53
+ REQUIRED_REGISTRY_FIELDS = (
54
+ "id", "name", "description", "frame_schema", "outcome_taxonomy",
55
+ "methodology_checklist", "golds_dir", "references_dir",
56
+ )
57
+
58
+ _cache: dict[str, Any] = {}
59
+
60
+
61
+ def _load_registry() -> dict:
62
+ """读取(缓存)domains/manifest.json 注册表。"""
63
+ if "_registry" not in _cache:
64
+ _cache["_registry"] = json.loads(
65
+ REGISTRY_FILE.read_text(encoding="utf-8"))
66
+ return _cache["_registry"]
67
+
68
+
69
+ def list_domains() -> list[dict]:
70
+ """按注册表顺序返回全部领域条目(id/name/description/契约引用)。"""
71
+ return list(_load_registry()["domains"])
72
+
73
+
74
+ def _field_target(entry: dict, field: str) -> Path | None:
75
+ """把注册表路径字段解析为仓库内路径。
76
+
77
+ JSON Pointer 引用("file.json#/pointer")取文件本体路径;
78
+ 指针指向的内容由 _check_pointer 另行校验。None 表示该字段
79
+ 明确为 null(可选契约,如 policy 的 golds_dir)。
80
+ """
81
+ raw = entry.get(field)
82
+ if raw is None:
83
+ return None
84
+ return REPO_ROOT / str(raw).partition("#")[0]
85
+
86
+
87
+ def _check_pointer(entry: dict, field: str) -> None:
88
+ """校验 "file.json#/pointer" 引用:文件存在且指针可解析为非空列表。"""
89
+ raw = str(entry[field])
90
+ path_text, _, pointer = raw.partition("#")
91
+ if not pointer:
92
+ return
93
+ target = REPO_ROOT / path_text
94
+ if not target.is_file():
95
+ raise FileNotFoundError(
96
+ f"domain {entry['id']!r}: {field} file missing: {target}")
97
+ node: Any = json.loads(target.read_text(encoding="utf-8"))
98
+ for part in pointer.strip("/").split("/"):
99
+ # RFC 6901 unescaping: ~1 -> "/", ~0 -> "~" (P2-9)
100
+ part = part.replace("~1", "/").replace("~0", "~")
101
+ if not isinstance(node, dict) or part not in node:
102
+ raise ValueError(
103
+ f"domain {entry['id']!r}: {field} pointer {pointer!r} "
104
+ f"unresolvable in {target}")
105
+ node = node[part]
106
+ if not isinstance(node, list) or not node:
107
+ raise ValueError(
108
+ f"domain {entry['id']!r}: {field} pointer {pointer!r} must "
109
+ f"resolve to a non-empty list, got {type(node).__name__}")
110
+
111
+
112
+ def _validate_contracts(entry: dict) -> None:
113
+ """校验领域条目引用的契约真实存在(load_domain 的核心职责)。
114
+
115
+ - frame_schema / outcome_taxonomy / methodology_checklist:文件存在且
116
+ 为可解析 JSON(指针引用另校验指针内容);
117
+ - golds_dir / references_dir:目录存在(null 视为"无此契约",跳过)。
118
+ """
119
+ domain_id = entry["id"]
120
+
121
+ def check_file(field: str) -> None:
122
+ target = _field_target(entry, field)
123
+ if target is None:
124
+ raise FileNotFoundError(
125
+ f"domain {domain_id!r}: {field} must reference a file")
126
+ if not target.is_file():
127
+ raise FileNotFoundError(
128
+ f"domain {domain_id!r}: {field} contract missing: {target}")
129
+ json.loads(target.read_text(encoding="utf-8")) # must parse as JSON
130
+ if "#" in str(entry[field]):
131
+ _check_pointer(entry, field)
132
+
133
+ def check_dir(field: str) -> None:
134
+ raw = entry.get(field)
135
+ if raw is None:
136
+ return # 可选契约(如 policy 的 golds_dir)
137
+ target = REPO_ROOT / str(raw)
138
+ if not target.is_dir():
139
+ raise FileNotFoundError(
140
+ f"domain {domain_id!r}: {field} missing: {target}")
141
+
142
+ check_file("frame_schema")
143
+ check_file("outcome_taxonomy")
144
+ check_file("methodology_checklist")
145
+ check_dir("golds_dir")
146
+ check_dir("references_dir")
147
+
148
+
149
+ def load_domain(domain_id: str) -> dict:
150
+ """加载领域条目并校验 manifest/契约存在性。
151
+
152
+ Returns: domains/manifest.json 中该领域的注册条目(路径为仓库相对路径)。
153
+ Raises: KeyError(未知领域);FileNotFoundError / ValueError(契约缺失
154
+ 或损坏)。
155
+ """
156
+ for entry in list_domains():
157
+ if entry["id"] == domain_id:
158
+ _validate_contracts(entry)
159
+ return entry
160
+ known = ", ".join(d["id"] for d in list_domains())
161
+ raise KeyError(f"unknown domain {domain_id!r} (registered: {known})")
162
+
163
+
164
+ def validate_frame(domain_id: str, frame: dict) -> list[str]:
165
+ """用该领域的 frame schema 校验 frame dict。
166
+
167
+ Returns: 稳定错误字符串列表(空 == 合法),与 engine/contracts.py 的
168
+ validate_record 约定一致;education 复用 scripts.validate_schema 校验
169
+ 现有 schemas/education-frame.schema.json,不引入新校验器。
170
+ """
171
+ from scripts.validate_schema import SchemaError, validate # noqa: PLC0415
172
+
173
+ entry = load_domain(domain_id)
174
+ schema_path = _field_target(entry, "frame_schema")
175
+ assert schema_path is not None # load_domain 已保证存在
176
+ schema = json.loads(schema_path.read_text(encoding="utf-8"))
177
+ errors: list[str] = []
178
+ try:
179
+ validate(frame, schema)
180
+ except SchemaError as exc:
181
+ errors.append(str(exc))
182
+ return errors
@@ -0,0 +1,132 @@
1
+ """engine/gap_lens.py — Research Gap Discovery & Contradiction Lens.
2
+
3
+ Inspired by BioGapLens & PICO gap taxonomy. Analyzes the EvidenceGraph to identify:
4
+ 1. Population Gaps (unexplored cohorts, demographic bias)
5
+ 2. Measurement / Retention Gaps (missing delayed post-tests, lack of unassisted transfer)
6
+ 3. Methodological Gaps (lack of baseline equivalence, missing active controls)
7
+ 4. Contradiction Lenses (identifying moderators explaining divergent study findings)
8
+
9
+ Generates pre-registered 12-week quasi-experimental DID / RCT trial protocols
10
+ specifically grounded on verified gaps (enforcing the rule: 'No study design without evidence grounding').
11
+ """
12
+ from __future__ import annotations
13
+
14
+ from typing import Any, Dict, List, Optional
15
+ from engine.evidence_graph import EvidenceGraph, GapNode, RiskNode
16
+ from engine.semantics import OutcomeDimension
17
+
18
+
19
+ class GapLensAnalyzer:
20
+ """Automated research gap discovery and contradiction diagnostic engine."""
21
+
22
+ def analyze_gaps(self, graph: EvidenceGraph) -> List[GapNode]:
23
+ gaps: List[GapNode] = []
24
+ pico = graph.intent.get("pico", {})
25
+ intervention = pico.get("intervention", "Target Intervention")
26
+ population = pico.get("population", "Target Population")
27
+
28
+ ev_list = list(graph.evidence.values())
29
+ if not ev_list:
30
+ return gaps
31
+
32
+ # 1. Measurement & Retention Gap Check
33
+ transfer_nodes = [ev for ev in ev_list if ev.outcome_dimension == OutcomeDimension.INDEPENDENT_TRANSFER]
34
+ procedural_nodes = [ev for ev in ev_list if ev.outcome_dimension == OutcomeDimension.PROCEDURAL_EFFICIENCY]
35
+
36
+ if procedural_nodes and not transfer_nodes:
37
+ gaps.append(GapNode(
38
+ gap_id=f"GAP-RETENTION-001",
39
+ gap_type="Measurement/Retention Gap",
40
+ description=f"Existing studies on {intervention} exclusively evaluate immediate in-session task speed without measuring delayed conceptual retention or unassisted transfer.",
41
+ target_outcome="Delayed Unassisted Transfer",
42
+ existing_evidence_summary=f"{len(procedural_nodes)} studies evaluated active speed; 0 studies measured unassisted delayed retention.",
43
+ recommended_trial_design="12-Week Cluster Randomized Trial with 4-week delayed post-test without AI assistance.",
44
+ ))
45
+ elif transfer_nodes and any(ev.effect_size.get("value", 0.0) <= 0.05 for ev in transfer_nodes):
46
+ avg_proc = (
47
+ sum(ev.effect_size.get("value", 0.0) for ev in procedural_nodes) / len(procedural_nodes)
48
+ if procedural_nodes else 0.0
49
+ )
50
+ avg_trans = (
51
+ sum(ev.effect_size.get("value", 0.0) for ev in transfer_nodes) / len(transfer_nodes)
52
+ if transfer_nodes else 0.0
53
+ )
54
+ gaps.append(GapNode(
55
+ gap_id=f"GAP-SCAFFOLD-FADE-001",
56
+ gap_type="Methodological Gap",
57
+ description=f"Delayed transfer tests indicate cognitive offloading/scaffolding dependency under unguarded {intervention}. Lack of trials evaluating structured 'fading' protocols.",
58
+ target_outcome="Independent Problem Solving",
59
+ existing_evidence_summary=f"Synthesized evidence shows {avg_proc:+.2f}g in-task speed ({len(procedural_nodes)} studies) vs {avg_trans:+.2f}g delayed unassisted transfer ({len(transfer_nodes)} studies).",
60
+ recommended_trial_design="2x2 Factorial Trial: Unguarded Access vs Structured Socratic Fading Scaffold vs Control.",
61
+ ))
62
+
63
+ # 2. Population & Context Heterogeneity Gap
64
+ cohorts = [ev.sample_description.lower() for ev in ev_list if ev.sample_description]
65
+ has_novice = any("freshman" in c or "novice" in c or "cs1" in c or "intro" in c or "初学" in c for c in cohorts)
66
+ has_advanced = any("senior" in c or "advanced" in c or "professional" in c or "高年级" in c or "专家" in c for c in cohorts)
67
+
68
+ if has_novice and not has_advanced:
69
+ gaps.append(GapNode(
70
+ gap_id=f"GAP-POP-EXPERTISE-001",
71
+ gap_type="Population Gap",
72
+ description=f"Evidence is heavily skewed toward introductory novices; efficacy on advanced algorithmic design and large-scale software engineering remains unexplored.",
73
+ target_outcome="Advanced Software Architecture",
74
+ existing_evidence_summary=f"Evaluations across {len(cohorts)} studies are restricted to introductory novice cohorts ({population}).",
75
+ recommended_trial_design="Comparative Quasi-Experimental Study evaluating CS3/CS4 upper-division capstone cohorts.",
76
+ ))
77
+
78
+ # 3. Contradiction Lens (Divergent effect size isolation)
79
+ pos_studies = [ev for ev in ev_list if ev.effect_size.get("value", 0.0) > 0.20]
80
+ neg_studies = [ev for ev in ev_list if ev.effect_size.get("value", 0.0) < -0.10]
81
+
82
+ if pos_studies and neg_studies:
83
+ gaps.append(GapNode(
84
+ gap_id=f"GAP-CONTRADICTION-001",
85
+ gap_type="Contradiction Lens",
86
+ description=f"Contradictory findings detected across {len(pos_studies)} positive vs {len(neg_studies)} negative studies. Moderator hypothesis: Degree of real-time Socratic prompting vs direct code generation.",
87
+ target_outcome="Syntax Speed vs Algorithmic Reasoning",
88
+ existing_evidence_summary=f"Positive studies ({[s.evidence_id for s in pos_studies[:2]]}) measured active code drafting; negative studies ({[s.evidence_id for s in neg_studies[:2]]}) measured unassisted closed-book exams.",
89
+ recommended_trial_design="Dismantling Study isolating the active ingredient of AI prompting modality.",
90
+ ))
91
+
92
+ # Register discovered gaps into graph
93
+ for g in gaps:
94
+ graph.add_gap(g)
95
+
96
+ return gaps
97
+
98
+ def generate_pre_registered_protocol(self, gap: GapNode, graph: EvidenceGraph) -> Dict[str, Any]:
99
+ """Generates a complete 12-week pre-registered quasi-experimental DID trial protocol."""
100
+ pico = graph.intent.get("pico", {})
101
+ intervention = pico.get("intervention", "Target Intervention")
102
+ population = pico.get("population", "Target Population")
103
+
104
+ return {
105
+ "protocol_id": f"PROTO-{gap.gap_id}",
106
+ "grounded_gap_id": gap.gap_id,
107
+ "title": f"Pre-Registered 12-Week Field Trial: Evaluating {gap.target_outcome} under {intervention}",
108
+ "design_type": "Difference-in-Differences (DID) with Baseline Equivalence",
109
+ "duration_weeks": 12,
110
+ "target_population": population,
111
+ "sample_size_target": 240,
112
+ "arms": [
113
+ {"name": "Treatment Arm (Scaffolded Intervention)", "n": 120, "description": f"{intervention} with mandatory reflection and phased scaffolding fade"},
114
+ {"name": "Active Control Arm (Standard Curriculum)", "n": 120, "description": "Traditional IDE with standard pedagogical TA support"},
115
+ ],
116
+ "timeline": [
117
+ {"week": "Week 1", "phase": "Pre-Test Baseline", "measurement": "Baseline Equivalence & Prior Achievement Test (WWC 5.0 compliant)"},
118
+ {"week": "Weeks 2-5", "phase": "Phase 1: Assisted Foundation", "measurement": "In-task procedural completion time and cognitive load"},
119
+ {"week": "Weeks 6-9", "phase": "Phase 2: Scaffolding Fade", "measurement": "Socratic prompt fidelity and conceptual mental model check"},
120
+ {"week": "Week 10", "phase": "Phase 3: Unassisted Transfer", "measurement": "Solo unassisted closed-book problem solving exam"},
121
+ {"week": "Week 12", "phase": "Phase 4: Delayed Retention Wave", "measurement": "4-week delayed retention test + DID regression data export"},
122
+ ],
123
+ "statistical_model": "DID Regression: Y_it = beta_0 + beta_1*Treat_i + beta_2*Post_t + delta*(Treat_i * Post_t) + gamma*X_it + epsilon_it",
124
+ "causal_estimand": "delta (Average Treatment Effect on the Treated / ATT)",
125
+ "stopping_rules": [
126
+ "Severe drop in solo mid-term exam performance (> 1.0 SD deficit vs control)",
127
+ "Academic integrity breach rate exceeding 15% in treatment cohort",
128
+ ]
129
+ }
130
+
131
+
132
+ gap_lens = GapLensAnalyzer()
package/engine/gaps.py ADDED
@@ -0,0 +1,169 @@
1
+ """Structured Knowledge Gap derivation.
2
+
3
+ A KnowledgeGap is not free-form "future work": it is derived from coverage —
4
+ the research frame's requested outcomes vs what the graph's Findings
5
+ actually measure. A task-performance Finding never covers a retention or
6
+ transfer gap.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ from pathlib import Path
13
+
14
+ from engine.contracts import validate_record
15
+ from engine.graph_store import GraphStore
16
+ from engine.ids import new_local_id
17
+ from engine.synthesis import ClaimSynthesis
18
+
19
+ # outcome_type names for timepoint-like gaps (frame.requested_outcomes entries
20
+ # may carry outcome_type or be plain strings; we match on outcome_type)
21
+ _RETENTION_TYPES = {"retention", "long_term", "learning_retention"}
22
+ _TRANSFER_TYPES = {"transfer", "transfer_learning", "far_transfer"}
23
+ _TASK_PERFORMANCE = {"task_performance", "assignment_score", "task_completion"}
24
+ _LEARNING = {"learning"}
25
+
26
+
27
+ def derive_gaps(*, store: GraphStore,
28
+ syntheses: tuple[ClaimSynthesis, ...] | None = None,
29
+ frame: dict | None = None) -> list[dict]:
30
+ """Derive structured gaps from graph coverage vs the research frame.
31
+
32
+ `frame` carries `requested_outcomes` (list of outcome names/types) and
33
+ optionally `target_population`. Findings' outcome types come from the
34
+ graph's outcomes table.
35
+ """
36
+ frame = frame or {}
37
+ requested = frame.get("requested_outcomes") or []
38
+ if not requested and frame.get("target_outcomes"):
39
+ requested = frame["target_outcomes"]
40
+
41
+ findings = store.read_table("findings")
42
+ outcomes = {o["outcome_id"]: o for o in store.read_table("outcomes")}
43
+ covered_types: set[str] = set()
44
+ for f in findings:
45
+ o = outcomes.get(f["outcome_id"])
46
+ if o:
47
+ covered_types.add(o.get("outcome_type", ""))
48
+
49
+ claims = store.read_table("claims")
50
+ claim_ids = [c["claim_id"] for c in claims]
51
+
52
+ gaps: list[dict] = []
53
+ rev = store.active_revision()
54
+
55
+ def add(gap_type: str, priority: str, reasoning: str,
56
+ related_claims: list[str] | None = None,
57
+ related_outcomes: list[str] | None = None):
58
+ gaps.append({
59
+ "gap_id": new_local_id("GAP", {g["gap_id"] for g in gaps}),
60
+ "gap_type": gap_type,
61
+ "related_claim_ids": related_claims or [],
62
+ "related_outcome_ids": related_outcomes or [],
63
+ "priority": priority,
64
+ "reasoning": reasoning,
65
+ "status": "open",
66
+ "derived_from_graph_revision": rev,
67
+ "extensions": {},
68
+ })
69
+
70
+ def _req_kind(req) -> tuple[str, str]:
71
+ """Classify a requested outcome: retention | transfer |
72
+ task_performance | learning | other. Type-aware: names are matched
73
+ only within the outcome's declared type, never type-blind."""
74
+ if isinstance(req, dict):
75
+ req_name = str(req.get("name", "")).lower()
76
+ req_type = str(req.get("outcome_type", "")).lower()
77
+ else:
78
+ req_name, req_type = str(req).lower(), ""
79
+ if req_type in _RETENTION_TYPES or req_name in _RETENTION_TYPES:
80
+ return "retention", req.get("name", "") if isinstance(req, dict) else str(req)
81
+ if req_type in _TRANSFER_TYPES or req_name in _TRANSFER_TYPES:
82
+ return "transfer", req.get("name", "") if isinstance(req, dict) else str(req)
83
+ if req_type in _TASK_PERFORMANCE or req_name in _TASK_PERFORMANCE:
84
+ return "task_performance", req.get("name", "") if isinstance(req, dict) else str(req)
85
+ if req_type in _LEARNING or req_name in _LEARNING:
86
+ return "learning", req.get("name", "") if isinstance(req, dict) else str(req)
87
+ return "other", req.get("name", "") if isinstance(req, dict) else str(req)
88
+
89
+ _RETENTION_COVER = _RETENTION_TYPES
90
+ _TRANSFER_COVER = _TRANSFER_TYPES
91
+ def covered_for_kind(kind: str) -> bool:
92
+ if kind == "retention":
93
+ return bool(covered_types & _RETENTION_COVER)
94
+ if kind == "transfer":
95
+ return bool(covered_types & _TRANSFER_COVER)
96
+ if kind == "task_performance":
97
+ return bool(covered_types & _TASK_PERFORMANCE)
98
+ if kind == "learning":
99
+ return bool(covered_types & _LEARNING)
100
+ return False
101
+
102
+ # one pass per requested outcome; each gap emitted exactly once
103
+ seen: set[tuple[str, str]] = set()
104
+ for req in requested:
105
+ kind, label = _req_kind(req)
106
+ if not label:
107
+ continue
108
+ key = (kind, label)
109
+ if key in seen:
110
+ continue
111
+ seen.add(key)
112
+ if covered_for_kind(kind):
113
+ continue
114
+ if kind == "retention":
115
+ add("missing_retention", "high",
116
+ f"frame requests retention outcome {label!r} but the graph has "
117
+ f"no retention-type measurement; task-performance coverage does "
118
+ f"not count (RULE 3)")
119
+ elif kind == "transfer":
120
+ add("missing_transfer", "high",
121
+ f"frame requests transfer outcome {label!r} but the graph has "
122
+ f"no transfer-type measurement; AI-assisted task performance "
123
+ f"does not count (RULE 3)")
124
+ elif kind == "task_performance":
125
+ add("missing_outcome", "medium",
126
+ f"frame requests task-performance outcome {label!r} with no "
127
+ f"covering finding")
128
+ elif kind == "learning":
129
+ add("missing_outcome", "medium",
130
+ f"frame requests learning outcome {label!r} with no covering "
131
+ f"learning finding; task performance is not learning (RULE 3)")
132
+ else:
133
+ add("missing_outcome", "medium",
134
+ f"frame requests outcome {label!r} with no covering finding")
135
+ claim_outcomes = {c["claim_id"]: c.get("primary_outcome_ids", [])
136
+ for c in claims}
137
+
138
+ # contradiction gaps
139
+ for syn in syntheses or ():
140
+ if syn.status == "contested":
141
+ add("unresolved_conflict", "high",
142
+ f"claim {syn.claim_id} has independent contradictory studies "
143
+ f"({', '.join(syn.study_ids)})", [syn.claim_id],
144
+ claim_outcomes.get(syn.claim_id, []))
145
+
146
+ # methodology weakness / insufficient independence
147
+ if syntheses:
148
+ for syn in syntheses:
149
+ if syn.status == "insufficient" and len(syn.study_ids) < 2:
150
+ add("insufficient_sample_independence", "medium",
151
+ f"claim {syn.claim_id} rests on fewer than two independent "
152
+ f"studies", [syn.claim_id],
153
+ claim_outcomes.get(syn.claim_id, []))
154
+
155
+ # validate each gap
156
+ for g in gaps:
157
+ errors = validate_record("knowledge-gap", g)
158
+ if errors:
159
+ raise ValueError(f"invalid gap: {errors}")
160
+ return gaps
161
+
162
+
163
+ def save_gaps(project, *, graph_revision: int, gaps: list[dict]) -> Path:
164
+ """Persist gaps under gaps/ (one JSONL file per revision)."""
165
+ path = project.path / "gaps" / f"gaps-rev-{graph_revision:06d}.jsonl"
166
+ path.parent.mkdir(parents=True, exist_ok=True)
167
+ lines = "".join(json.dumps(g, ensure_ascii=False) + "\n" for g in gaps)
168
+ path.write_text(lines, encoding="utf-8")
169
+ return path