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.
- package/LICENSE +21 -0
- package/README.md +506 -0
- package/README.zh-CN.md +434 -0
- package/SKILL.md +195 -0
- package/bin/eduevidence.js +127 -0
- package/domains/education/manifest.json +183 -0
- package/domains/education/outcome_taxonomy.json +127 -0
- package/domains/manifest.json +26 -0
- package/domains/policy/frame.schema.json +234 -0
- package/domains/policy/manifest.json +10 -0
- package/domains/policy/methodology_checklist.json +109 -0
- package/domains/policy/outcome_taxonomy.json +53 -0
- package/domains/policy/references/causal-identification.md +45 -0
- package/domains/policy/references/cost-evidence.md +44 -0
- package/domains/policy/references/equity.md +42 -0
- package/domains/policy/references/evidence-hierarchy.md +41 -0
- package/domains/policy/references/implementation-evidence.md +47 -0
- package/eduevidence_cli.py +26 -0
- package/engine/__init__.py +11 -0
- package/engine/__pycache__/__init__.cpython-312.pyc +0 -0
- package/engine/__pycache__/analysis.cpython-312.pyc +0 -0
- package/engine/__pycache__/bias.cpython-312.pyc +0 -0
- package/engine/__pycache__/briefs.cpython-312.pyc +0 -0
- package/engine/__pycache__/capabilities.cpython-312.pyc +0 -0
- package/engine/__pycache__/citation_check.cpython-312.pyc +0 -0
- package/engine/__pycache__/contracts.cpython-312.pyc +0 -0
- package/engine/__pycache__/datasets.cpython-312.pyc +0 -0
- package/engine/__pycache__/events.cpython-312.pyc +0 -0
- package/engine/__pycache__/evidence_graph.cpython-312.pyc +0 -0
- package/engine/__pycache__/evidence_review.cpython-312.pyc +0 -0
- package/engine/__pycache__/evidencecore.cpython-312.pyc +0 -0
- package/engine/__pycache__/gap_lens.cpython-312.pyc +0 -0
- package/engine/__pycache__/gaps.cpython-312.pyc +0 -0
- package/engine/__pycache__/graph_store.cpython-312.pyc +0 -0
- package/engine/__pycache__/graph_validate.cpython-312.pyc +0 -0
- package/engine/__pycache__/ids.cpython-312.pyc +0 -0
- package/engine/__pycache__/library.cpython-312.pyc +0 -0
- package/engine/__pycache__/library_builtin.cpython-312.pyc +0 -0
- package/engine/__pycache__/living.cpython-312.pyc +0 -0
- package/engine/__pycache__/log.cpython-312.pyc +0 -0
- package/engine/__pycache__/meta_analysis.cpython-312.pyc +0 -0
- package/engine/__pycache__/meta_synthesis.cpython-312.pyc +0 -0
- package/engine/__pycache__/migration.cpython-312.pyc +0 -0
- package/engine/__pycache__/mode_router.cpython-312.pyc +0 -0
- package/engine/__pycache__/paths.cpython-312.pyc +0 -0
- package/engine/__pycache__/pilot.cpython-312.pyc +0 -0
- package/engine/__pycache__/planner.cpython-312.pyc +0 -0
- package/engine/__pycache__/project.cpython-312.pyc +0 -0
- package/engine/__pycache__/projections.cpython-312.pyc +0 -0
- package/engine/__pycache__/robustness.cpython-312.pyc +0 -0
- package/engine/__pycache__/run.cpython-312.pyc +0 -0
- package/engine/__pycache__/semantics.cpython-312.pyc +0 -0
- package/engine/__pycache__/study_design.cpython-312.pyc +0 -0
- package/engine/__pycache__/synthesis.cpython-312.pyc +0 -0
- package/engine/__pycache__/tribunal.cpython-312.pyc +0 -0
- package/engine/__pycache__/update.cpython-312.pyc +0 -0
- package/engine/__pycache__/versions.cpython-312.pyc +0 -0
- package/engine/analysis.py +308 -0
- package/engine/bias.py +178 -0
- package/engine/briefs.py +106 -0
- package/engine/capabilities.py +99 -0
- package/engine/citation_check.py +192 -0
- package/engine/contracts.py +117 -0
- package/engine/datasets.py +165 -0
- package/engine/events.py +67 -0
- package/engine/evidence_graph.py +571 -0
- package/engine/evidence_review.py +88 -0
- package/engine/evidencecore.py +182 -0
- package/engine/gap_lens.py +132 -0
- package/engine/gaps.py +169 -0
- package/engine/graph_store.py +335 -0
- package/engine/graph_validate.py +87 -0
- package/engine/ids.py +77 -0
- package/engine/library.py +268 -0
- package/engine/library_builtin.py +301 -0
- package/engine/living.py +671 -0
- package/engine/log.py +39 -0
- package/engine/meta_analysis.py +333 -0
- package/engine/meta_synthesis.py +111 -0
- package/engine/migration.py +397 -0
- package/engine/mode_router.py +72 -0
- package/engine/paths.py +15 -0
- package/engine/pilot.py +368 -0
- package/engine/planner.py +126 -0
- package/engine/project.py +118 -0
- package/engine/projections.py +240 -0
- package/engine/robustness.py +109 -0
- package/engine/run.py +85 -0
- package/engine/semantics.py +135 -0
- package/engine/study_design.py +87 -0
- package/engine/synthesis.py +187 -0
- package/engine/tribunal.py +408 -0
- package/engine/update.py +113 -0
- package/engine/versions.py +12 -0
- package/install.sh +510 -0
- package/integrations/__init__.py +1 -0
- package/integrations/__pycache__/__init__.cpython-312.pyc +0 -0
- package/integrations/__pycache__/agent_mcp.cpython-312.pyc +0 -0
- package/integrations/__pycache__/smart_web_fetch.cpython-312.pyc +0 -0
- package/integrations/agent_mcp.py +856 -0
- package/integrations/smart_web_fetch.py +59 -0
- package/package.json +50 -0
- package/pyproject.toml +55 -0
- package/references/applicability-policy.md +88 -0
- package/references/education-framing.md +132 -0
- package/references/effect_size_formulas.md +35 -0
- package/references/evaluation-design.md +111 -0
- package/references/evidence-quality.md +79 -0
- package/references/grade_framework.md +29 -0
- package/references/intervention-design.md +98 -0
- package/references/methodology-audit.md +103 -0
- package/references/outcome-taxonomy.md +106 -0
- package/references/retrieval-protocol.md +142 -0
- package/references/skeptic-protocol.md +93 -0
- package/references/social_science_pitfalls.md +48 -0
- package/references/source-validity.md +140 -0
- package/references/tribunal-policy.md +112 -0
- package/references/wwc_standards.md +29 -0
- package/retrieval/__init__.py +1 -0
- package/retrieval/__pycache__/__init__.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/corpus_store.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/dedupe.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/failures.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/fetch.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/search.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/source.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/validate.cpython-312.pyc +0 -0
- package/retrieval/corpus_store.py +181 -0
- package/retrieval/dedupe.py +127 -0
- package/retrieval/failures.py +90 -0
- package/retrieval/fetch.py +435 -0
- package/retrieval/search.py +493 -0
- package/retrieval/source.py +160 -0
- package/retrieval/validate.py +257 -0
- package/schemas/agent-mcp-approval.schema.json +57 -0
- package/schemas/chart-spec.schema.json +88 -0
- package/schemas/cross-model-review.schema.json +28 -0
- package/schemas/education-frame.schema.json +106 -0
- package/schemas/evaluation.schema.json +35 -0
- package/schemas/evidence.schema.json +81 -0
- package/schemas/fetch-result.schema.json +119 -0
- package/schemas/intervention.schema.json +46 -0
- package/schemas/methodology.schema.json +102 -0
- package/schemas/report-result.schema.json +381 -0
- package/schemas/report-spec.schema.json +130 -0
- package/schemas/source.schema.json +311 -0
- package/schemas/v2/analysis-plan.schema.json +28 -0
- package/schemas/v2/analysis-run.schema.json +33 -0
- package/schemas/v2/claim.schema.json +26 -0
- package/schemas/v2/dataset-asset.schema.json +40 -0
- package/schemas/v2/decision-snapshot.schema.json +53 -0
- package/schemas/v2/evidence-link.schema.json +38 -0
- package/schemas/v2/finding.schema.json +47 -0
- package/schemas/v2/graph-revision.schema.json +30 -0
- package/schemas/v2/knowledge-gap.schema.json +40 -0
- package/schemas/v2/methodology-audit.schema.json +30 -0
- package/schemas/v2/outcome.schema.json +18 -0
- package/schemas/v2/project.schema.json +31 -0
- package/schemas/v2/research-intent.schema.json +24 -0
- package/schemas/v2/run.schema.json +43 -0
- package/schemas/v2/source.schema.json +24 -0
- package/schemas/v2/study-design.schema.json +67 -0
- package/schemas/v2/study.schema.json +37 -0
- package/schemas/v3/pilot-outcome.schema.json +132 -0
- package/schemas/v3/run-manifest.schema.json +193 -0
- package/schemas/v3/synthesis.schema.json +86 -0
- package/schemas/v4/drift-report.schema.json +66 -0
- package/schemas/v4/evidence-library.schema.json +42 -0
- package/schemas/v4/living-subscription.schema.json +55 -0
- package/schemas/v4/meta-analysis.schema.json +152 -0
- package/schemas/verdict.schema.json +56 -0
- package/scripts/__init__.py +0 -0
- package/scripts/__pycache__/__init__.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_evaluator.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_judge.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_routing.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_v2.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_v3.cpython-312.pyc +0 -0
- package/scripts/__pycache__/build_result.cpython-312.pyc +0 -0
- package/scripts/__pycache__/claim_audit.cpython-312.pyc +0 -0
- package/scripts/__pycache__/complexity_gate.cpython-312.pyc +0 -0
- package/scripts/__pycache__/compute_confidence.cpython-312.pyc +0 -0
- package/scripts/__pycache__/dashboard_server.cpython-312.pyc +0 -0
- package/scripts/__pycache__/did_regression.cpython-312.pyc +0 -0
- package/scripts/__pycache__/effect_calculator.cpython-312.pyc +0 -0
- package/scripts/__pycache__/evidence_matrix.cpython-312.pyc +0 -0
- package/scripts/__pycache__/evidence_score.cpython-312.pyc +0 -0
- package/scripts/__pycache__/evidence_semantics.cpython-312.pyc +0 -0
- package/scripts/__pycache__/fetch_benchmark.cpython-312.pyc +0 -0
- package/scripts/__pycache__/lint_report_layout.cpython-312.pyc +0 -0
- package/scripts/__pycache__/orchestrator.cpython-312.pyc +0 -0
- package/scripts/__pycache__/pre_verdict_gate.cpython-312.pyc +0 -0
- package/scripts/__pycache__/recompute_demo_quality.cpython-312.pyc +0 -0
- package/scripts/__pycache__/render_report.cpython-312.pyc +0 -0
- package/scripts/__pycache__/render_report_html.cpython-312.pyc +0 -0
- package/scripts/__pycache__/run_workspace.cpython-312.pyc +0 -0
- package/scripts/__pycache__/skill_lint.cpython-312.pyc +0 -0
- package/scripts/__pycache__/startup_probe.cpython-312.pyc +0 -0
- package/scripts/__pycache__/sync_killer_demo_report.cpython-312.pyc +0 -0
- package/scripts/__pycache__/test_adversarial_empirical.cpython-312-pytest-9.0.2.pyc +0 -0
- package/scripts/__pycache__/test_adversarial_empirical.cpython-312-pytest-9.1.1.pyc +0 -0
- package/scripts/__pycache__/validate_schema.cpython-312.pyc +0 -0
- package/scripts/audit_dois.py +292 -0
- package/scripts/bake_pack.sh +37 -0
- package/scripts/benchmark.py +183 -0
- package/scripts/benchmark_evaluator.py +371 -0
- package/scripts/benchmark_judge.py +535 -0
- package/scripts/benchmark_routing.py +120 -0
- package/scripts/benchmark_v2.py +304 -0
- package/scripts/benchmark_v3.py +552 -0
- package/scripts/build_esl_artifacts.py +1921 -0
- package/scripts/build_evidence_library.py +307 -0
- package/scripts/build_killer_demo.py +295 -0
- package/scripts/build_result.py +311 -0
- package/scripts/check_version_consistency.py +96 -0
- package/scripts/citation_check.py +123 -0
- package/scripts/claim_audit.py +157 -0
- package/scripts/complexity_gate.py +180 -0
- package/scripts/compute_confidence.py +176 -0
- package/scripts/dashboard_server.py +536 -0
- package/scripts/did_regression.py +315 -0
- package/scripts/effect_calculator.py +99 -0
- package/scripts/enrich_projects_human_and_lieflat.py +315 -0
- package/scripts/evidence_matrix.py +129 -0
- package/scripts/evidence_score.py +234 -0
- package/scripts/evidence_semantics.py +87 -0
- package/scripts/fetch_benchmark.py +177 -0
- package/scripts/generate_metrics.py +99 -0
- package/scripts/generate_new_projects.py +686 -0
- package/scripts/generate_promo.py +192 -0
- package/scripts/lint_report_layout.py +182 -0
- package/scripts/orchestrator.py +1456 -0
- package/scripts/pre_verdict_gate.py +513 -0
- package/scripts/quickstart.py +121 -0
- package/scripts/rebake_all_5themes.py +88 -0
- package/scripts/recompute_demo_quality.py +205 -0
- package/scripts/render_report.py +270 -0
- package/scripts/render_report_html.py +356 -0
- package/scripts/retraction_watch.py +110 -0
- package/scripts/run_workspace.py +337 -0
- package/scripts/serve_web.py +54 -0
- package/scripts/skill_lint.py +150 -0
- package/scripts/startup_probe.py +265 -0
- package/scripts/sync_killer_demo_report.py +270 -0
- package/scripts/test_adversarial_empirical.py +541 -0
- package/scripts/validate_schema.py +256 -0
- package/skill/agents/education-planner.md +80 -0
- package/skill/agents/evaluation-designer.md +74 -0
- package/skill/agents/evidence-analyst.md +106 -0
- package/skill/agents/evidence-judge.md +111 -0
- package/skill/agents/evidence-retriever.md +80 -0
- package/skill/agents/intervention-designer.md +82 -0
- package/skill/agents/method-reviewer.md +104 -0
- package/skill/agents/skeptic.md +89 -0
- package/skill/sub-skills/aihot-trend-analysis/SKILL.md +31 -0
- package/skill/sub-skills/contradiction-analysis/SKILL.md +17 -0
- package/skill/sub-skills/data-analysis/SKILL.md +23 -0
- package/skill/sub-skills/ethics-review/SKILL.md +25 -0
- package/skill/sub-skills/evidence-extraction/SKILL.md +19 -0
- package/skill/sub-skills/evidence-review/SKILL.md +18 -0
- package/skill/sub-skills/gap-analysis/SKILL.md +25 -0
- package/skill/sub-skills/literature-review/SKILL.md +21 -0
- package/skill/sub-skills/methodology-audit/SKILL.md +20 -0
- package/skill/sub-skills/report-generation/SKILL.md +51 -0
- package/skill/sub-skills/research-planning/SKILL.md +21 -0
- package/skill/sub-skills/study-design/SKILL.md +16 -0
- package/skill/task-briefs/adjudicate.md +17 -0
- package/skill/task-briefs/audit.md +15 -0
- package/skill/task-briefs/challenge.md +15 -0
- package/skill/task-briefs/evaluate.md +13 -0
- package/skill/task-briefs/extract.md +16 -0
- package/skill/task-briefs/frame.md +17 -0
- package/skill/task-briefs/intervene.md +14 -0
- package/skill/task-briefs/present.md +16 -0
- package/skill/task-briefs/retrieve.md +15 -0
- package/visualization/eduevidence-report/assets/base.css +337 -0
- package/visualization/eduevidence-report/motion/motion.css +157 -0
- package/visualization/eduevidence-report/motion/motion.js +107 -0
- package/visualization/eduevidence-report/references/bilingual-style.md +7 -0
- package/visualization/eduevidence-report/references/component-catalog.md +145 -0
- package/visualization/eduevidence-report/references/evidence-expansion.md +65 -0
- package/visualization/eduevidence-report/references/full-report-outline.md +86 -0
- package/visualization/eduevidence-report/references/layout-constraints.md +63 -0
- package/visualization/eduevidence-report/references/lieflat-composition.md +79 -0
- package/visualization/eduevidence-report/references/motion-system.md +31 -0
- package/visualization/eduevidence-report/schemas/adapter-envelope.schema.json +22 -0
- package/visualization/eduevidence-report/schemas/visual-layout.schema.json +87 -0
- package/visualization/eduevidence-report/scripts/__pycache__/adapter_contract.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_artifact_manifest.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_charts.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_figures.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_infographics.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_report.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/charts_data.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/lieflat_engine.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/zh_labels.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/adapter_contract.py +72 -0
- package/visualization/eduevidence-report/scripts/build_artifact_manifest.py +70 -0
- package/visualization/eduevidence-report/scripts/build_charts.py +283 -0
- package/visualization/eduevidence-report/scripts/build_figures.py +515 -0
- package/visualization/eduevidence-report/scripts/build_infographics.py +268 -0
- package/visualization/eduevidence-report/scripts/build_report.py +3211 -0
- package/visualization/eduevidence-report/scripts/charts_data.py +617 -0
- package/visualization/eduevidence-report/scripts/check_mobile_layout.js +220 -0
- package/visualization/eduevidence-report/scripts/lieflat_engine.py +852 -0
- package/visualization/eduevidence-report/scripts/zh_labels.py +245 -0
- package/visualization/eduevidence-report/themes/academic.css +94 -0
- package/visualization/eduevidence-report/themes/claude.css +96 -0
- package/visualization/eduevidence-report/themes/datalab-dark.css +147 -0
- package/visualization/eduevidence-report/themes/datalab.css +151 -0
- 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()
|