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,315 @@
1
+ #!/usr/bin/env python3
2
+ """scripts/did_regression.py — Deterministic Difference-in-Differences (DID) Statistical Engine.
3
+
4
+ Pure Python stdlib implementation with zero mandatory dependencies (no pandas/numpy required),
5
+ with automatic acceleration when pandas/numpy are present.
6
+
7
+ Model:
8
+ Y_ist = beta0 + beta1 * Treat_i + beta2 * Post_t + delta * (Treat_i * Post_t) + epsilon_ist
9
+
10
+ Where:
11
+ delta = Causal DID treatment effect estimate
12
+ beta1 = Baseline difference between treatment and control
13
+ beta2 = Common secular time trend
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import csv
18
+ import json
19
+ import math
20
+ import sys
21
+ from pathlib import Path
22
+ def _two_tailed_p_from_z(z: float) -> float:
23
+ """Standard normal two-tailed p-value."""
24
+ return 2.0 * (1.0 - 0.5 * (1.0 + math.erf(abs(z) / math.sqrt(2.0))))
25
+
26
+
27
+ def _null_inference() -> Dict[str, Any]:
28
+ """Fail-closed inference fields for not-estimable designs (never fake SE/p)."""
29
+ return {
30
+ "did_coefficient": None,
31
+ "standard_error": None,
32
+ "t_statistic": None,
33
+ "p_value": None,
34
+ "ci_95": None,
35
+ "hedges_g": None,
36
+ }
37
+
38
+
39
+ def _solve_linear_system(A: List[List[float]], b: List[float]) -> List[float]:
40
+ """Gaussian elimination with partial pivoting for small OLS systems (p <= 10)."""
41
+ n = len(b)
42
+ # Augmented matrix
43
+ M = [A[i][:] + [b[i]] for i in range(n)]
44
+
45
+ for i in range(n):
46
+ # Pivot
47
+ max_row = max(range(i, n), key=lambda r: abs(M[r][i]))
48
+ if abs(M[max_row][i]) < 1e-12:
49
+ raise ValueError("Singular matrix in OLS estimation")
50
+ M[i], M[max_row] = M[max_row], M[i]
51
+
52
+ pivot = M[i][i]
53
+ for j in range(i, n + 1):
54
+ M[i][j] /= pivot
55
+
56
+ for r in range(n):
57
+ if r != i:
58
+ factor = M[r][i]
59
+ for c in range(i, n + 1):
60
+ M[r][c] -= factor * M[i][c]
61
+
62
+ return [M[i][n] for i in range(n)]
63
+
64
+
65
+ def _matrix_inverse(A: List[List[float]]) -> List[List[float]]:
66
+ """Inverts an n x n matrix using Gauss-Jordan elimination."""
67
+ n = len(A)
68
+ # Augment with identity
69
+ M = [A[i][:] + [1.0 if i == j else 0.0 for j in range(n)] for i in range(n)]
70
+
71
+ for i in range(n):
72
+ max_row = max(range(i, n), key=lambda r: abs(M[r][i]))
73
+ if abs(M[max_row][i]) < 1e-12:
74
+ raise ValueError("Singular matrix in inversion")
75
+ M[i], M[max_row] = M[max_row], M[i]
76
+
77
+ pivot = M[i][i]
78
+ for j in range(2 * n):
79
+ M[i][j] /= pivot
80
+
81
+ for r in range(n):
82
+ if r != i:
83
+ factor = M[r][i]
84
+ for c in range(2 * n):
85
+ M[r][c] -= factor * M[i][c]
86
+
87
+ return [[M[i][n + j] for j in range(n)] for i in range(n)]
88
+
89
+
90
+ def run_did_analysis(csv_path: str) -> Dict[str, Any]:
91
+ """Runs Difference-in-Differences regression on a classroom/field CSV dataset."""
92
+ path = Path(csv_path)
93
+ if not path.exists():
94
+ return {"status": "error", "error_code": "ERR_NO_FILE",
95
+ "message": f"File not found: {csv_path}"}
96
+
97
+ rows = []
98
+ with open(path, "r", encoding="utf-8-sig") as f:
99
+ reader = csv.DictReader(f)
100
+ for row in reader:
101
+ rows.append(row)
102
+
103
+ if len(rows) < 4:
104
+ return {"status": "error", "error_code": "ERR_INSUFFICIENT_ROWS",
105
+ "message": "Insufficient data rows (minimum 4 required)"}
106
+
107
+ # Normalize column names (+ cluster candidates; exact names only, never guessed)
108
+ field_map = {}
109
+ cluster_columns: List[str] = []
110
+ for col in rows[0].keys():
111
+ cl = col.strip().lower()
112
+ if cl in ("cluster_id", "class_id", "school_id", "group_id") or cl.endswith("_cluster"):
113
+ cluster_columns.append(col)
114
+ if "treat" in cl or cl in ("group", "condition", "is_treatment"):
115
+ field_map["treat"] = col
116
+ elif "post" in cl or "after" in cl or "period" in cl or "time" in cl or "pre_post" in cl:
117
+ field_map["post"] = col
118
+ elif "score" in cl or "outcome" in cl or "grade" in cl or "result" in cl or "performance" in cl or cl == "y":
119
+ field_map["outcome"] = col
120
+
121
+ if "treat" not in field_map or "post" not in field_map or "outcome" not in field_map:
122
+ return {
123
+ "status": "error", "error_code": "ERR_MISSING_COLUMNS",
124
+ "message": f"CSV missing required columns (need treat/post/outcome). Found: {list(rows[0].keys())}"
125
+ }
126
+
127
+ # Parse numeric arrays
128
+ y_vals: List[float] = []
129
+ treat_vals: List[float] = []
130
+ post_vals: List[float] = []
131
+ treat_post_vals: List[float] = []
132
+
133
+ # Cells for 2x2 table
134
+ cell_y = {(0, 0): [], (0, 1): [], (1, 0): [], (1, 1): []}
135
+
136
+ for r in rows:
137
+ try:
138
+ t = 1.0 if float(r[field_map["treat"]]) > 0.5 else 0.0
139
+ p = 1.0 if float(r[field_map["post"]]) > 0.5 else 0.0
140
+ y = float(r[field_map["outcome"]])
141
+
142
+ treat_vals.append(t)
143
+ post_vals.append(p)
144
+ treat_post_vals.append(t * p)
145
+ y_vals.append(y)
146
+ cell_y[(int(t), int(p))].append(y)
147
+ except (ValueError, TypeError):
148
+ continue
149
+
150
+ n = len(y_vals)
151
+ if n < 4:
152
+ return {"status": "error", "error_code": "ERR_PARSE",
153
+ "message": "Failed to parse sufficient numeric rows"}
154
+
155
+ # Cell means
156
+ means = {}
157
+ stds = {}
158
+ for k, v in cell_y.items():
159
+ if v:
160
+ m = sum(v) / len(v)
161
+ means[k] = m
162
+ var = sum((x - m) ** 2 for x in v) / (len(v) - 1) if len(v) > 1 else 1.0
163
+ stds[k] = math.sqrt(var)
164
+ else:
165
+ means[k] = 0.0
166
+ stds[k] = 1.0
167
+
168
+ # --- Estimability gates (fail closed; never fabricate inference) ---
169
+ if len(set(treat_vals)) < 2:
170
+ return {
171
+ "status": "error", "error_code": "ERR_NO_TREAT_VARIATION",
172
+ "message": "Treatment column has no variation; DID not estimable",
173
+ **_null_inference(),
174
+ }
175
+ if len(set(post_vals)) < 2:
176
+ return {
177
+ "status": "error", "error_code": "ERR_NO_POST_VARIATION",
178
+ "message": "Post column has no variation; DID not estimable",
179
+ **_null_inference(),
180
+ }
181
+ for k in ((0, 0), (0, 1), (1, 0), (1, 1)):
182
+ if not cell_y[k]:
183
+ return {
184
+ "status": "error", "error_code": "ERR_EMPTY_CELL",
185
+ "message": f"DID design has an empty 2x2 cell ({k}); not estimable",
186
+ **_null_inference(),
187
+ }
188
+ if n - 4 <= 0:
189
+ return {
190
+ "status": "error", "error_code": "ERR_SATURATED",
191
+ "message": "Model saturated (n - 4 <= 0); no residual degrees of freedom for inference",
192
+ **_null_inference(),
193
+ }
194
+ y_mean = sum(y_vals) / n
195
+ tss = sum((y - y_mean) ** 2 for y in y_vals)
196
+ if tss <= 0:
197
+ return {
198
+ "status": "error", "error_code": "ERR_ZERO_VARIANCE",
199
+ "message": "Outcome has zero variance; DID inference not estimable",
200
+ **_null_inference(),
201
+ }
202
+
203
+ y_c_pre = means[(0, 0)]
204
+ y_c_post = means[(0, 1)]
205
+ y_t_pre = means[(1, 0)]
206
+ y_t_post = means[(1, 1)]
207
+
208
+ # Simple 2x2 delta
209
+ delta_simple = (y_t_post - y_t_pre) - (y_c_post - y_c_pre)
210
+
211
+ # OLS Estimation: Y = X * beta + e, X = [1, Treat, Post, Treat*Post]
212
+ # Build X^T X (4x4) and X^T Y (4x1)
213
+ X = [[1.0, treat_vals[i], post_vals[i], treat_post_vals[i]] for i in range(n)]
214
+ XtX = [[0.0] * 4 for _ in range(4)]
215
+ XtY = [0.0] * 4
216
+
217
+ for i in range(n):
218
+ row = X[i]
219
+ yi = y_vals[i]
220
+ for r in range(4):
221
+ XtY[r] += row[r] * yi
222
+ for c in range(4):
223
+ XtX[r][c] += row[r] * row[c]
224
+
225
+ try:
226
+ beta = _solve_linear_system(XtX, XtY)
227
+ XtX_inv = _matrix_inverse(XtX)
228
+ except Exception:
229
+ return {
230
+ "status": "error", "error_code": "ERR_DESIGN_NOT_ESTIMABLE",
231
+ "message": "Design matrix inversion failed: singular or collinear design",
232
+ **_null_inference(),
233
+ }
234
+
235
+ # Residual sum of squares & Standard Error
236
+ rss = 0.0
237
+ for i in range(n):
238
+ y_hat = beta[0] + beta[1] * treat_vals[i] + beta[2] * post_vals[i] + beta[3] * treat_post_vals[i]
239
+ rss += (y_vals[i] - y_hat) ** 2
240
+
241
+ df_resid = n - 4
242
+ sigma2 = rss / df_resid
243
+ r_squared = max(0.0, 1.0 - (rss / tss)) if tss > 0 else 0.0
244
+
245
+ if rss <= 0:
246
+ return {
247
+ "status": "error", "error_code": "ERR_ZERO_RESIDUAL",
248
+ "message": "Zero residual variance; inference not estimable",
249
+ **_null_inference(),
250
+ }
251
+ se_delta = math.sqrt(sigma2 * XtX_inv[3][3])
252
+ t_stat = beta[3] / se_delta if se_delta > 0 else 0.0
253
+ p_val = _two_tailed_p_from_z(t_stat)
254
+
255
+ # Standardized Effect Size: Hedges' g
256
+ s_pooled_pre = math.sqrt((stds[(0, 0)] ** 2 + stds[(1, 0)] ** 2) / 2.0) if stds[(0, 0)] and stds[(1, 0)] else 1.0
257
+ hedges_j = 1.0 - (3.0 / (4.0 * df_resid - 1.0)) if df_resid > 2 else 1.0
258
+ hedges_g = round(hedges_j * (beta[3] / s_pooled_pre), 3) if s_pooled_pre > 0 else 0.0
259
+
260
+ # Baseline Equivalence
261
+ baseline_diff = y_t_pre - y_c_pre
262
+ baseline_g = baseline_diff / s_pooled_pre if s_pooled_pre > 0 else 0.0
263
+ # QED/DID can never meet WWC 5.0 standards WITHOUT reservations; and without
264
+ # covariate-adjustment fields a 0.05<|g|<=0.25 baseline is not passable.
265
+ if abs(baseline_g) <= 0.05:
266
+ wwc_rating = "Meets Standards With Reservations"
267
+ elif abs(baseline_g) <= 0.25:
268
+ wwc_rating = "Does Not Meet Standards (Statistical Adjustment Required)"
269
+ else:
270
+ wwc_rating = "Does Not Meet Standards (Baseline Imbalance)"
271
+
272
+ ci_95 = [
273
+ round(beta[3] - 1.96 * se_delta, 3),
274
+ round(beta[3] + 1.96 * se_delta, 3)
275
+ ]
276
+
277
+ cluster_note = (
278
+ f"cluster column(s) detected ({', '.join(cluster_columns)}) but cluster-robust "
279
+ "inference is not implemented in this build; p-value is not cluster-robust"
280
+ if cluster_columns else
281
+ "cluster identifier missing; p-value is not cluster-robust"
282
+ )
283
+ return {
284
+ "status": "success",
285
+ "inference_status": "non_cluster_warning",
286
+ "inference_warning": cluster_note,
287
+ "cluster_columns": cluster_columns,
288
+ "sample_size": n,
289
+ "treatment_n": len(cell_y[(1, 0)]) + len(cell_y[(1, 1)]),
290
+ "control_n": len(cell_y[(0, 0)]) + len(cell_y[(0, 1)]),
291
+ "did_coefficient": round(beta[3], 4),
292
+ "standard_error": round(se_delta, 4),
293
+ "t_statistic": round(t_stat, 3),
294
+ "p_value": round(p_val, 4),
295
+ "ci_95": ci_95,
296
+ "r_squared": round(r_squared, 4),
297
+ "hedges_g": hedges_g,
298
+ "baseline_equivalence_g": round(baseline_g, 3),
299
+ "wwc_baseline_rating": wwc_rating,
300
+ "cell_means": {
301
+ "control_pre": round(y_c_pre, 2),
302
+ "control_post": round(y_c_post, 2),
303
+ "treatment_pre": round(y_t_pre, 2),
304
+ "treatment_post": round(y_t_post, 2),
305
+ }
306
+ }
307
+
308
+
309
+ if __name__ == "__main__":
310
+ if len(sys.argv) < 2:
311
+ print("Usage: python3 scripts/did_regression.py <path_to_csv>")
312
+ sys.exit(1)
313
+ res = run_did_analysis(sys.argv[1])
314
+ print(json.dumps(res, indent=2, ensure_ascii=False))
315
+ sys.exit(0 if res.get("status") == "success" else 1)
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env python3
2
+ """scripts/effect_calculator.py — Effect Size & Confidence Interval Calculator.
3
+
4
+ Pure Python stdlib tool to compute:
5
+ - Pooled Standard Deviation (s_pooled)
6
+ - Cohen's d
7
+ - Hedges' g (exact or small-sample bias corrected)
8
+ - Standard Error of g
9
+ - 95% Confidence Interval [lower, upper]
10
+
11
+ Usage:
12
+ python3 scripts/effect_calculator.py --mean1 78.5 --sd1 10.2 --n1 90 --mean2 72.1 --sd2 11.0 --n2 90
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import argparse
17
+ import json
18
+ import math
19
+ import sys
20
+
21
+
22
+ def compute_hedges_g(
23
+ mean1: float, sd1: float, n1: int,
24
+ mean2: float, sd2: float, n2: int
25
+ ) -> dict:
26
+ if n1 <= 1 or n2 <= 1:
27
+ raise ValueError("Group sample sizes must both be > 1")
28
+ if sd1 <= 0 or sd2 <= 0:
29
+ raise ValueError("Standard deviations must be positive")
30
+
31
+ df = n1 + n2 - 2
32
+ # Pooled SD
33
+ s_pooled = math.sqrt(((n1 - 1) * (sd1 ** 2) + (n2 - 1) * (sd2 ** 2)) / df)
34
+
35
+ # Cohen's d
36
+ d = (mean1 - mean2) / s_pooled
37
+
38
+ # Hedges' correction factor J(df)
39
+ j = 1.0 - (3.0 / (4.0 * df - 1.0))
40
+ g = j * d
41
+
42
+ # Variance and Standard Error of g
43
+ var_g = (float(n1 + n2) / (n1 * n2)) + ((g ** 2) / (2.0 * (n1 + n2)))
44
+ se_g = math.sqrt(var_g)
45
+
46
+ # 95% Confidence Interval (Z_0.975 = 1.95996)
47
+ z_crit = 1.95996398454
48
+ ci_lower = g - z_crit * se_g
49
+ ci_upper = g + z_crit * se_g
50
+
51
+ # Two-tailed p-value
52
+ z_stat = abs(g / se_g) if se_g > 0 else 0.0
53
+ p_value = 2.0 * (1.0 - 0.5 * (1.0 + math.erf(z_stat / math.sqrt(2.0))))
54
+
55
+ return {
56
+ "status": "success",
57
+ "cohens_d": round(d, 4),
58
+ "hedges_g": round(g, 4),
59
+ "pooled_sd": round(s_pooled, 4),
60
+ "standard_error": round(se_g, 4),
61
+ "ci_95": [round(ci_lower, 4), round(ci_upper, 4)],
62
+ "p_value": round(p_value, 4),
63
+ "degrees_of_freedom": df,
64
+ "sample_size_total": n1 + n2,
65
+ "sample_size_treatment": n1,
66
+ "sample_size_control": n2,
67
+ }
68
+
69
+
70
+ def main():
71
+ parser = argparse.ArgumentParser(description="Calculate Hedges' g effect size and 95% CI.")
72
+ parser.add_argument("--mean1", type=float, required=True, help="Treatment group mean")
73
+ parser.add_argument("--sd1", type=float, required=True, help="Treatment group standard deviation")
74
+ parser.add_argument("--n1", type=int, required=True, help="Treatment group sample size")
75
+ parser.add_argument("--mean2", type=float, required=True, help="Control group mean")
76
+ parser.add_argument("--sd2", type=float, required=True, help="Control group standard deviation")
77
+ parser.add_argument("--n2", type=int, required=True, help="Control group sample size")
78
+ parser.add_argument("--json", action="store_true", help="Output raw JSON")
79
+
80
+ args = parser.parse_args()
81
+ try:
82
+ res = compute_hedges_g(args.mean1, args.sd1, args.n1, args.mean2, args.sd2, args.n2)
83
+ if args.json:
84
+ print(json.dumps(res, indent=2))
85
+ else:
86
+ print("=== Effect Size Calculation Results ===")
87
+ print(f" • Hedges' g: {res['hedges_g']} (95% CI: [{res['ci_95'][0]}, {res['ci_95'][1]}])")
88
+ print(f" • Cohen's d: {res['cohens_d']}")
89
+ print(f" • Pooled SD: {res['pooled_sd']}")
90
+ print(f" • Standard Error: {res['standard_error']}")
91
+ print(f" • p-value: {res['p_value']}")
92
+ print(f" • Sample Size: Treatment N={res['sample_size_treatment']}, Control N={res['sample_size_control']} (Total N={res['sample_size_total']})")
93
+ except Exception as e:
94
+ print(f"Error: {e}", file=sys.stderr)
95
+ sys.exit(1)
96
+
97
+
98
+ if __name__ == "__main__":
99
+ main()