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,88 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""scripts/rebake_all_5themes.py — Re-bake all 5 themes for all 3 projects with Lieflat figures and human language.
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
11
|
+
|
|
12
|
+
PROJECTS = [
|
|
13
|
+
"examples/highschool-math-ai-tutor",
|
|
14
|
+
"examples/esl-academic-writing-ai",
|
|
15
|
+
"examples/ai-coding-assistant-evidence"
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
THEMES = ["claude", "academic", "datalab", "datalab-dark", "presentation"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def rebake():
|
|
22
|
+
for proj in PROJECTS:
|
|
23
|
+
p_dir = ROOT / proj
|
|
24
|
+
r_en = p_dir / "result.json"
|
|
25
|
+
r_zh = p_dir / "result.zh.json"
|
|
26
|
+
out_html = p_dir / "EduEvidence_Report.html"
|
|
27
|
+
themes_dir = p_dir / "reports-5themes"
|
|
28
|
+
themes_dir.mkdir(parents=True, exist_ok=True)
|
|
29
|
+
|
|
30
|
+
print(f"\n==========================================")
|
|
31
|
+
print(f"Re-baking project: {proj}")
|
|
32
|
+
print(f"==========================================")
|
|
33
|
+
|
|
34
|
+
# 1. Main report (default claude theme)
|
|
35
|
+
cmd = [
|
|
36
|
+
sys.executable,
|
|
37
|
+
str(ROOT / "visualization/eduevidence-report/scripts/build_report.py"),
|
|
38
|
+
"--result", str(r_en),
|
|
39
|
+
"--result-zh", str(r_zh),
|
|
40
|
+
"--out", str(out_html)
|
|
41
|
+
]
|
|
42
|
+
res = subprocess.run(cmd, capture_output=True, text=True)
|
|
43
|
+
print(f"Main report: code={res.returncode}")
|
|
44
|
+
if res.returncode != 0:
|
|
45
|
+
print("STDERR:", res.stderr)
|
|
46
|
+
sys.exit(1)
|
|
47
|
+
|
|
48
|
+
# 2. 5 theme reports
|
|
49
|
+
for t in THEMES:
|
|
50
|
+
t_out = themes_dir / f"report_{t}.html"
|
|
51
|
+
t_cmd = [
|
|
52
|
+
sys.executable,
|
|
53
|
+
str(ROOT / "visualization/eduevidence-report/scripts/build_report.py"),
|
|
54
|
+
"--result", str(r_en),
|
|
55
|
+
"--result-zh", str(r_zh),
|
|
56
|
+
"--theme", t,
|
|
57
|
+
"--out", str(t_out)
|
|
58
|
+
]
|
|
59
|
+
t_res = subprocess.run(t_cmd, capture_output=True, text=True)
|
|
60
|
+
print(f" Theme {t:15s} -> {t_out.name} (code={t_res.returncode})")
|
|
61
|
+
if t_res.returncode != 0:
|
|
62
|
+
print(" STDERR:", t_res.stderr)
|
|
63
|
+
sys.exit(1)
|
|
64
|
+
# Also copy to EduEvidence_Report_{t}.html for compatibility
|
|
65
|
+
compat_out = themes_dir / f"EduEvidence_Report_{t}.html"
|
|
66
|
+
compat_out.write_bytes(t_out.read_bytes())
|
|
67
|
+
|
|
68
|
+
# 3. Artifact manifest (HTML-03): all 5 theme HTMLs must embed the same
|
|
69
|
+
# result.json hash; refresh artifact_manifest.json after the bake.
|
|
70
|
+
manifest_cmd = [
|
|
71
|
+
sys.executable,
|
|
72
|
+
str(ROOT / "visualization/eduevidence-report/scripts/build_artifact_manifest.py"),
|
|
73
|
+
"--result", str(r_en),
|
|
74
|
+
"--result-zh", str(r_zh),
|
|
75
|
+
"--html-dir", str(themes_dir),
|
|
76
|
+
"--out", str(p_dir / "artifact_manifest.json"),
|
|
77
|
+
]
|
|
78
|
+
m_res = subprocess.run(manifest_cmd, capture_output=True, text=True)
|
|
79
|
+
print(f"Manifest: code={m_res.returncode} {m_res.stdout.strip()}")
|
|
80
|
+
if m_res.returncode != 0:
|
|
81
|
+
print("STDERR:", m_res.stderr)
|
|
82
|
+
sys.exit(1)
|
|
83
|
+
|
|
84
|
+
print("\nAll 3 projects (15 theme reports) successfully re-baked with Lieflat Charts!")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
rebake()
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""recompute_demo_quality.py — Recompute evidence quality scores for the demo
|
|
3
|
+
against references/evidence-quality.md and emit an explainable report.
|
|
4
|
+
|
|
5
|
+
The five dimensions (each 0/1/2, total 0-10) are recomputed from the evidence's
|
|
6
|
+
own structured fields, so every score is traceable to a rubric rule:
|
|
7
|
+
|
|
8
|
+
D1 Study Design -> study_type / comparison
|
|
9
|
+
D2 Sample Quality -> sample_size / attrition / self-selection / baseline
|
|
10
|
+
(population matching belongs to D5, NOT D2)
|
|
11
|
+
D3 Measurement Validity-> outcome_measure / method
|
|
12
|
+
D4 Temporal Strength -> duration / method / outcome_measure
|
|
13
|
+
(2 requires >=8-week intervention OR a retention/
|
|
14
|
+
transfer measure)
|
|
15
|
+
D5 Directness -> education_level / subject vs the target frame
|
|
16
|
+
(university first-year C programming + generative
|
|
17
|
+
AI coding assistant + independent problem solving)
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
python3 scripts/recompute_demo_quality.py [--evidence examples/ai-coding-assistant/evidence.jsonl]
|
|
21
|
+
python3 scripts/recompute_demo_quality.py --evidence ... --check
|
|
22
|
+
|
|
23
|
+
--check additionally verifies that the quality_dimensions / quality_score
|
|
24
|
+
stored in the data file match the recomputed values; exit code 1 on mismatch.
|
|
25
|
+
|
|
26
|
+
Exit code 0 = recomputed successfully (and, with --check, data is consistent).
|
|
27
|
+
"""
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import argparse
|
|
31
|
+
import json
|
|
32
|
+
import sys
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
from typing import Any
|
|
35
|
+
|
|
36
|
+
from evidence_score import quality_level
|
|
37
|
+
|
|
38
|
+
# Target frame the demo decision is anchored to (frame.json).
|
|
39
|
+
TARGET_FRAME = "大学一年级 C 语言课程 + 生成式 AI 编程助手 + 独立问题解决"
|
|
40
|
+
|
|
41
|
+
DIMS = [
|
|
42
|
+
("D1_study_design", "D1 研究设计"),
|
|
43
|
+
("D2_sample_quality", "D2 样本质量"),
|
|
44
|
+
("D3_measurement_validity", "D3 测量有效性"),
|
|
45
|
+
("D4_temporal_strength", "D4 时间强度"),
|
|
46
|
+
("D5_directness", "D5 直接性"),
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def load_evidence(path: Path) -> list[dict]:
|
|
51
|
+
records = []
|
|
52
|
+
for lineno, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
|
|
53
|
+
line = line.strip()
|
|
54
|
+
if not line:
|
|
55
|
+
continue
|
|
56
|
+
try:
|
|
57
|
+
records.append(json.loads(line))
|
|
58
|
+
except json.JSONDecodeError as exc:
|
|
59
|
+
raise ValueError(f"{path}:{lineno}: invalid JSON: {exc}") from exc
|
|
60
|
+
return records
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# --- per-dimension rubric rules ---------------------------------------------
|
|
64
|
+
|
|
65
|
+
def d1(ev: dict[str, Any]) -> tuple[int, str]:
|
|
66
|
+
study_type = ev.get("study_type", "")
|
|
67
|
+
has_control = bool(ev.get("comparison"))
|
|
68
|
+
if study_type == "rct" and has_control:
|
|
69
|
+
return 2, "RCT 随机对照试验,含对照组,干预与测量分离(rubric D1=2)。"
|
|
70
|
+
if has_control:
|
|
71
|
+
return 1, "有对照但非随机(如自然班对照 / 前后测对照),记 1 分(rubric D1=1)。"
|
|
72
|
+
return 0, "无对照的描述性 / 观察研究,记 0 分(rubric D1=0)。"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def d2(ev: dict[str, Any]) -> tuple[int, str]:
|
|
76
|
+
"""D2 只评样本量 / 流失 / 偏差 / 基线;人群匹配一律归 D5。"""
|
|
77
|
+
n = ev.get("sample_size")
|
|
78
|
+
confounders = " ".join(ev.get("confounders", []))
|
|
79
|
+
limitations = " ".join(ev.get("limitations", []))
|
|
80
|
+
method = ev.get("method", "")
|
|
81
|
+
if "self_selection" in confounders or "consent" in method or "自选" in limitations:
|
|
82
|
+
return 1, "知情同意 / 报名参与引入自选偏差(rubric D2=1:样本有偏差)。"
|
|
83
|
+
if isinstance(n, (int, float)) and n >= 100:
|
|
84
|
+
return 2, (f"样本量充足(n={int(n)},每组 ≥ 30),随机分组并报告基线协变量,"
|
|
85
|
+
"无显著流失(rubric D2=2)。")
|
|
86
|
+
if isinstance(n, (int, float)) and n >= 60 and ev.get("study_type") == "rct":
|
|
87
|
+
return 2, (f"总样本 n={int(n)}(两组各约 30+),随机分组、基线能力已测量并处理"
|
|
88
|
+
"(rubric D2=2,按每组 ≥ 30 判)。")
|
|
89
|
+
if isinstance(n, (int, float)) and n >= 10:
|
|
90
|
+
return 1, f"样本量偏小(n={int(n)},每组 10–30),记 1 分(rubric D2=1)。"
|
|
91
|
+
return 0, "样本量 < 10 或无样本描述,记 0 分(rubric D2=0)。"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def d3(ev: dict[str, Any]) -> tuple[int, str]:
|
|
95
|
+
outcome = ev.get("outcome_type", "")
|
|
96
|
+
measure = ev.get("outcome_measure", "")
|
|
97
|
+
method = ev.get("method", "")
|
|
98
|
+
if outcome == "assignment_score" and "practice" in measure:
|
|
99
|
+
return 2, ("任务表现测量与声称结果(练习任务表现)对应,且同研究以独立闭卷考试"
|
|
100
|
+
"明确区分任务表现与学习效果,未把任务表现当学习效果(rubric D3=2)。")
|
|
101
|
+
if outcome == "retention":
|
|
102
|
+
return 2, "标准延迟保持测试,测量与声称的保持力结果对应(rubric D3=2)。"
|
|
103
|
+
if "exam" in measure and "without" in measure:
|
|
104
|
+
return 2, "独立闭卷考试(无 AI 访问),客观评分,测量与学习效果结果对应(rubric D3=2)。"
|
|
105
|
+
if "modification" in measure:
|
|
106
|
+
return 1, "自编同场次代码修改任务,测量合理但未经验证、无盲评(rubric D3=1)。"
|
|
107
|
+
if "pre-post" in measure or "delayed" in method:
|
|
108
|
+
return 2, "前测-后测-延迟测设计,写作测试有规范评分(rubric D3=2)。"
|
|
109
|
+
if "task" in measure and "time" in measure:
|
|
110
|
+
return 2, "标准代码编写任务 + 客观进度 / 用时指标,评分有规范(rubric D3=2)。"
|
|
111
|
+
return 1, "测量合理但未验证(自编题目 / 自评量表),记 1 分(rubric D3=1)。"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def d4(ev: dict[str, Any]) -> tuple[int, str]:
|
|
115
|
+
""">=8 周干预 或 含 retention/transfer 测量 才给 2 分。"""
|
|
116
|
+
text = " ".join(str(ev.get(k, "")) for k in ("duration", "method", "outcome_measure"))
|
|
117
|
+
if any(sig in text for sig in ("retention", "transfer", "delayed")):
|
|
118
|
+
return 2, "含保持测试 / 迁移测试(满足 >=8 周干预或含 retention/transfer 即给 2,rubric D4=2)。"
|
|
119
|
+
if "in_class" in text or "weeks" in text:
|
|
120
|
+
return 1, "干预 2–8 周(课堂学习时段),有后测但无保持 / 迁移测量(rubric D4=1)。"
|
|
121
|
+
return 0, "单次实验 / 单课时干预,或只有即时效果无后续测量(rubric D4=0)。"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def d5(ev: dict[str, Any]) -> tuple[int, str]:
|
|
125
|
+
edu = ev.get("education_level", "")
|
|
126
|
+
subject = ev.get("subject", "")
|
|
127
|
+
if subject == "academic_writing_esl":
|
|
128
|
+
return 0, "仅变量名称相关:学术写作 vs 编程,结果与目标 Frame 不对应(rubric D5=0)。"
|
|
129
|
+
if edu == "k12_ages_10_17":
|
|
130
|
+
return 1, ("大致对应但有偏差:同为入门编程 + 代码生成工具,但学习者(K-12 10-17 岁)"
|
|
131
|
+
"与课程(Python vs C 语言)不同(rubric D5=1)。")
|
|
132
|
+
if edu == "high_school":
|
|
133
|
+
return 1, ("大致对应但有偏差:同为生成式 AI 工具 + 独立问题解决,但高中(数学)"
|
|
134
|
+
"vs 大学(C 编程)不同(rubric D5=1)。")
|
|
135
|
+
return 2, "干预 x 学习者 x 课程 x 工具 x 结果与目标 Frame 完全对应(rubric D5=2)。"
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
RULES = {"D1_study_design": d1, "D2_sample_quality": d2,
|
|
139
|
+
"D3_measurement_validity": d3, "D4_temporal_strength": d4,
|
|
140
|
+
"D5_directness": d5}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def recompute(ev: dict[str, Any]) -> tuple[dict[str, int], dict[str, str], float]:
|
|
144
|
+
dims: dict[str, int] = {}
|
|
145
|
+
reasons: dict[str, str] = {}
|
|
146
|
+
for key, _ in DIMS:
|
|
147
|
+
score, reason = RULES[key](ev)
|
|
148
|
+
dims[key] = score
|
|
149
|
+
reasons[key] = reason
|
|
150
|
+
return dims, reasons, sum(dims.values())
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def render_report(evidence_list: list[dict]) -> str:
|
|
154
|
+
lines = [
|
|
155
|
+
"Evidence Quality 重算报告(rubric: references/evidence-quality.md)",
|
|
156
|
+
f"目标 Frame: {TARGET_FRAME}",
|
|
157
|
+
"=" * 78,
|
|
158
|
+
]
|
|
159
|
+
for ev in evidence_list:
|
|
160
|
+
dims, reasons, total = recompute(ev)
|
|
161
|
+
lines.append(f"\n{ev['evidence_id']} {ev['claim']}")
|
|
162
|
+
lines.append(f" source={ev.get('source_id')} outcome={ev.get('outcome_type')} "
|
|
163
|
+
f"duration={ev.get('duration')} n={ev.get('sample_size')}")
|
|
164
|
+
for key, label in DIMS:
|
|
165
|
+
lines.append(f" {label} = {dims[key]}/2 ({reasons[key]})")
|
|
166
|
+
level = quality_level(total)
|
|
167
|
+
lines.append(f" -> 总分 {total}/10 = {level}")
|
|
168
|
+
lines.append("\n" + "=" * 78)
|
|
169
|
+
lines.append("等级映射: 8-10 strong | 5-7 moderate | 2-4 weak | 0-1 very_weak (rubric 总分与等级)")
|
|
170
|
+
return "\n".join(lines)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def main() -> int:
|
|
174
|
+
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
175
|
+
parser.add_argument("--evidence", default="examples/ai-coding-assistant/evidence.jsonl",
|
|
176
|
+
help="Path to evidence.jsonl")
|
|
177
|
+
parser.add_argument("--check", action="store_true",
|
|
178
|
+
help="Verify stored quality values match the recomputed ones")
|
|
179
|
+
args = parser.parse_args()
|
|
180
|
+
|
|
181
|
+
evs = load_evidence(Path(args.evidence))
|
|
182
|
+
print(render_report(evs))
|
|
183
|
+
|
|
184
|
+
if args.check:
|
|
185
|
+
mismatches = []
|
|
186
|
+
for ev in evs:
|
|
187
|
+
dims, _, total = recompute(ev)
|
|
188
|
+
stored_dims = ev.get("quality_dimensions", {})
|
|
189
|
+
stored_score = ev.get("quality_score")
|
|
190
|
+
if stored_dims != dims:
|
|
191
|
+
mismatches.append(f"{ev['evidence_id']}: stored dims {stored_dims} != recomputed {dims}")
|
|
192
|
+
if stored_score != total:
|
|
193
|
+
mismatches.append(f"{ev['evidence_id']}: stored score {stored_score} != recomputed {total}")
|
|
194
|
+
if mismatches:
|
|
195
|
+
print("\nCHECK FAILED:")
|
|
196
|
+
for m in mismatches:
|
|
197
|
+
print(" " + m)
|
|
198
|
+
return 1
|
|
199
|
+
print(f"\nCHECK PASSED: {len(evs)} 条证据存储的 quality_dimensions / quality_score "
|
|
200
|
+
"与 rubric 重算结果一致。")
|
|
201
|
+
return 0
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
if __name__ == "__main__":
|
|
205
|
+
sys.exit(main())
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""render_report.py — Render a Research & Decision Pack (plan section 29) to Markdown.
|
|
3
|
+
|
|
4
|
+
The final output is not a single essay but a Research & Decision Pack:
|
|
5
|
+
|
|
6
|
+
01 Executive Decision 06 Conflict Analysis 11 Claim-Evidence Trace
|
|
7
|
+
02 Education Research Frame 07 Evidence Tribunal 12 Sources
|
|
8
|
+
03 Evidence Summary 08 Applicability
|
|
9
|
+
04 Evidence Matrix 09 Teaching Intervention
|
|
10
|
+
05 Methodology Audit 10 Evaluation Plan
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
python scripts/render_report.py \
|
|
14
|
+
--frame frame.json \
|
|
15
|
+
--evidence evidence.jsonl \
|
|
16
|
+
--verdict verdict.json \
|
|
17
|
+
--intervention intervention.json \
|
|
18
|
+
--evaluation evaluation.json \
|
|
19
|
+
--methodology methodology.json \
|
|
20
|
+
--out REPORT.md
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import argparse
|
|
25
|
+
import json
|
|
26
|
+
import sys
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
from evidence_matrix import evidence_matrix, render_markdown
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def load_json(path: Path | None) -> dict | None:
|
|
33
|
+
if path is None or not path.exists():
|
|
34
|
+
return None
|
|
35
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def load_jsonl(path: Path | None) -> list[dict]:
|
|
39
|
+
if path is None or not path.exists():
|
|
40
|
+
return []
|
|
41
|
+
records = []
|
|
42
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
43
|
+
line = line.strip()
|
|
44
|
+
if line:
|
|
45
|
+
records.append(json.loads(line))
|
|
46
|
+
return records
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def section(title: str, body: str) -> str:
|
|
50
|
+
return f"## {title}\n\n{body}\n"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def render_frame(frame: dict) -> str:
|
|
54
|
+
if not frame:
|
|
55
|
+
return "_no frame provided_"
|
|
56
|
+
lines = [f"**问题**: {frame.get('question', '')}",
|
|
57
|
+
f"**决策目标**: {frame.get('decision_target', '')}"]
|
|
58
|
+
for key in ("learner", "course", "intervention", "context", "scope"):
|
|
59
|
+
if frame.get(key):
|
|
60
|
+
lines.append(f"**{key}**: " + ", ".join(
|
|
61
|
+
f"{k}={v}" for k, v in frame[key].items()))
|
|
62
|
+
if frame.get("comparison"):
|
|
63
|
+
lines.append(f"**对照**: {frame['comparison']}")
|
|
64
|
+
if frame.get("success_condition"):
|
|
65
|
+
lines.append(f"**成功条件**: {frame['success_condition']}")
|
|
66
|
+
return "\n".join(lines)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def render_evidence_summary(evidence: list[dict]) -> str:
|
|
70
|
+
if not evidence:
|
|
71
|
+
return "_no evidence provided_"
|
|
72
|
+
by_outcome: dict[str, list[dict]] = {}
|
|
73
|
+
for ev in evidence:
|
|
74
|
+
by_outcome.setdefault(ev.get("outcome_type", "unknown"), []).append(ev)
|
|
75
|
+
lines = []
|
|
76
|
+
for outcome, evs in sorted(by_outcome.items()):
|
|
77
|
+
lines.append(f"### {outcome} ({len(evs)})")
|
|
78
|
+
for ev in evs:
|
|
79
|
+
direction = ev.get("direction", "neutral")
|
|
80
|
+
lines.append(f"- [{direction}] {ev.get('claim', '')} "
|
|
81
|
+
f"(source={ev.get('source_id', '?')}, quality={ev.get('quality_score', '?')})")
|
|
82
|
+
return "\n".join(lines)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def render_verdict(verdict: dict) -> str:
|
|
86
|
+
if not verdict:
|
|
87
|
+
return "_no verdict provided_"
|
|
88
|
+
action = verdict.get("recommended_action", "")
|
|
89
|
+
lines = [
|
|
90
|
+
f"**决策**: **{action.upper()}**",
|
|
91
|
+
f"**置信度**: {verdict.get('confidence', '')}",
|
|
92
|
+
]
|
|
93
|
+
if verdict.get("what_can_be_claimed"):
|
|
94
|
+
lines.append("\n**可以主张**:\n" + "\n".join(f"- {c}" for c in verdict["what_can_be_claimed"]))
|
|
95
|
+
if verdict.get("what_cannot_be_claimed"):
|
|
96
|
+
lines.append("\n**不能主张**:\n" + "\n".join(f"- {c}" for c in verdict["what_cannot_be_claimed"]))
|
|
97
|
+
if verdict.get("missing_evidence"):
|
|
98
|
+
lines.append("\n**缺失证据**:\n" + "\n".join(f"- {m}" for m in verdict["missing_evidence"]))
|
|
99
|
+
if verdict.get("exceeds_evidence_boundary"):
|
|
100
|
+
lines.append("\n**超出证据边界**:\n" + "\n".join(f"- {e}" for e in verdict["exceeds_evidence_boundary"]))
|
|
101
|
+
if verdict.get("decision_rationale"):
|
|
102
|
+
lines.append(f"\n**理由**: {verdict['decision_rationale']}")
|
|
103
|
+
return "\n".join(lines)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def render_intervention(intervention: dict) -> str:
|
|
107
|
+
if not intervention:
|
|
108
|
+
return "_no intervention provided_"
|
|
109
|
+
lines = [f"**决策**: {intervention.get('decision', '')}",
|
|
110
|
+
f"**目标学习者**: {intervention.get('target_learners', '')}",
|
|
111
|
+
f"**试点时长**: {intervention.get('pilot_duration', '')}"]
|
|
112
|
+
if intervention.get("ai_usage_policy"):
|
|
113
|
+
lines.append(f"\n**AI 使用规则**: {intervention['ai_usage_policy']}")
|
|
114
|
+
for phase in ("phase_1", "phase_2", "phase_3"):
|
|
115
|
+
if intervention.get(phase):
|
|
116
|
+
p = intervention[phase]
|
|
117
|
+
name = p.get("name", phase)
|
|
118
|
+
lines.append(f"\n**{name}**: {p.get('ai_usage_rule', '')}")
|
|
119
|
+
if p.get("activities"):
|
|
120
|
+
lines.append("\n".join(f"- {a}" for a in p["activities"]))
|
|
121
|
+
if intervention.get("stop_conditions"):
|
|
122
|
+
lines.append("\n**停止条件**:\n" + "\n".join(f"- {s}" for s in intervention["stop_conditions"]))
|
|
123
|
+
return "\n".join(lines)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def render_evaluation(evaluation: dict) -> str:
|
|
127
|
+
if not evaluation:
|
|
128
|
+
return "_no evaluation plan provided_"
|
|
129
|
+
lines = [f"**研究问题**: {evaluation.get('research_question', '')}"]
|
|
130
|
+
groups = evaluation.get("groups", {})
|
|
131
|
+
if groups:
|
|
132
|
+
lines.append(f"**组**: 干预={groups.get('treatment', '')} / 对照={groups.get('comparison', '')}")
|
|
133
|
+
for key, label in (("baseline", "基线"), ("post_test", "后测"), ("retention_test", "保持测试"),
|
|
134
|
+
("transfer_test", "迁移测试")):
|
|
135
|
+
if evaluation.get(key):
|
|
136
|
+
lines.append(f"**{label}**: {evaluation[key]}")
|
|
137
|
+
if evaluation.get("success_threshold"):
|
|
138
|
+
lines.append(f"**成功阈值**: {evaluation['success_threshold']}")
|
|
139
|
+
if evaluation.get("stop_conditions"):
|
|
140
|
+
lines.append("\n**停止条件**:\n" + "\n".join(f"- {s}" for s in evaluation["stop_conditions"]))
|
|
141
|
+
return "\n".join(lines)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def render_conflict_analysis(verdict: dict) -> str:
|
|
145
|
+
"""Section 06 — why different studies reach different conclusions."""
|
|
146
|
+
if not verdict:
|
|
147
|
+
return "_no verdict provided_"
|
|
148
|
+
lines = []
|
|
149
|
+
if verdict.get("reason_for_disagreement"):
|
|
150
|
+
lines.append(f"**冲突来源**: {verdict['reason_for_disagreement']}")
|
|
151
|
+
if verdict.get("outcome_specific_findings"):
|
|
152
|
+
lines.append("\n**按 Outcome 的发现**:")
|
|
153
|
+
for outcome, finding in verdict["outcome_specific_findings"].items():
|
|
154
|
+
lines.append(f"- {outcome}: {finding}")
|
|
155
|
+
if verdict.get("exceeds_evidence_boundary"):
|
|
156
|
+
lines.append("\n**越过证据边界的结论**:")
|
|
157
|
+
lines.extend(f"- {e}" for e in verdict["exceeds_evidence_boundary"])
|
|
158
|
+
return "\n".join(lines) if lines else "_no conflict analysis data_"
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def render_tribunal(verdict: dict) -> str:
|
|
162
|
+
"""Section 07 — Evidence Tribunal: what the evidence supports and cannot support."""
|
|
163
|
+
if not verdict:
|
|
164
|
+
return "_no verdict provided_"
|
|
165
|
+
lines = [f"**决策**: {verdict.get('recommended_action', '').upper()}",
|
|
166
|
+
f"**置信度**: {verdict.get('confidence', '')}"]
|
|
167
|
+
for key, label in (("supported_claims", "支持的结论"), ("uncertain_claims", "不确定的结论"),
|
|
168
|
+
("contradicted_claims", "被反驳的结论")):
|
|
169
|
+
if verdict.get(key):
|
|
170
|
+
lines.append(f"\n**{label}**:")
|
|
171
|
+
lines.extend(f"- {c}" for c in verdict[key])
|
|
172
|
+
return "\n".join(lines)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def render_applicability(verdict: dict) -> str:
|
|
176
|
+
"""Section 08 — Applicability: for whom, for which course, under what conditions."""
|
|
177
|
+
if not verdict:
|
|
178
|
+
return "_no verdict provided_"
|
|
179
|
+
app = verdict.get("applicability", {})
|
|
180
|
+
if not app:
|
|
181
|
+
return "_no applicability data_"
|
|
182
|
+
lines = []
|
|
183
|
+
for key, label in (("suitable_for", "适合"), ("not_suitable_for", "不适合"),
|
|
184
|
+
("required_conditions", "必要条件")):
|
|
185
|
+
value = app.get(key)
|
|
186
|
+
if isinstance(value, list):
|
|
187
|
+
lines.append(f"**{label}**: " + "; ".join(value))
|
|
188
|
+
elif value:
|
|
189
|
+
lines.append(f"**{label}**: {value}")
|
|
190
|
+
return "\n".join(lines)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def render_trace(evidence: list[dict]) -> str:
|
|
194
|
+
"""Section 11 — Claim-Evidence Trace: every conclusion traceable to a source."""
|
|
195
|
+
if not evidence:
|
|
196
|
+
return "_no evidence provided_"
|
|
197
|
+
lines = []
|
|
198
|
+
for ev in evidence:
|
|
199
|
+
lines.append(f"- `{ev.get('evidence_id', '?')}` {ev.get('claim', '')} "
|
|
200
|
+
f"→ `{ev.get('source_id', '?')}` [{ev.get('source_location', '?')}]")
|
|
201
|
+
return "\n".join(lines)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def render_sources(evidence: list[dict]) -> str:
|
|
205
|
+
"""Section 12 — deduplicated source list with verifiable locations."""
|
|
206
|
+
seen: dict[str, dict] = {}
|
|
207
|
+
for ev in evidence:
|
|
208
|
+
sid = ev.get("source_id", "?")
|
|
209
|
+
if sid not in seen:
|
|
210
|
+
seen[sid] = ev
|
|
211
|
+
lines = []
|
|
212
|
+
for sid, ev in sorted(seen.items()):
|
|
213
|
+
title = ev.get("title", "")
|
|
214
|
+
loc = ev.get("source_location", "")
|
|
215
|
+
year = ev.get("year", "")
|
|
216
|
+
lines.append(f"- **{sid}** ({year}): {title} — {loc}")
|
|
217
|
+
return "\n".join(lines) if lines else "_no sources_"
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def render_pack(frame, evidence, methodology, verdict, intervention, evaluation) -> str:
|
|
221
|
+
matrix = evidence_matrix(evidence)
|
|
222
|
+
matrix_md = render_markdown(matrix)
|
|
223
|
+
|
|
224
|
+
parts = [
|
|
225
|
+
"# EduEvidence Research & Decision Pack\n",
|
|
226
|
+
section("01 Executive Decision", render_verdict(verdict)),
|
|
227
|
+
section("02 Education Research Frame", render_frame(frame)),
|
|
228
|
+
section("03 Evidence Summary", render_evidence_summary(evidence)),
|
|
229
|
+
section("04 Evidence Matrix", matrix_md),
|
|
230
|
+
section("05 Methodology Audit",
|
|
231
|
+
f"```json\n{json.dumps(methodology, ensure_ascii=False, indent=2)}\n```"
|
|
232
|
+
if methodology else "_no methodology audit provided_"),
|
|
233
|
+
section("06 Conflict Analysis", render_conflict_analysis(verdict)),
|
|
234
|
+
section("07 Evidence Tribunal", render_tribunal(verdict)),
|
|
235
|
+
section("08 Applicability", render_applicability(verdict)),
|
|
236
|
+
section("09 Teaching Intervention", render_intervention(intervention)),
|
|
237
|
+
section("10 Evaluation Plan", render_evaluation(evaluation)),
|
|
238
|
+
section("11 Claim-Evidence Trace", render_trace(evidence)),
|
|
239
|
+
section("12 Sources", render_sources(evidence)),
|
|
240
|
+
]
|
|
241
|
+
return "\n".join(parts) + "\n"
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def main() -> int:
|
|
245
|
+
parser = argparse.ArgumentParser(description="Render the Research & Decision Pack to Markdown")
|
|
246
|
+
parser.add_argument("--frame")
|
|
247
|
+
parser.add_argument("--evidence", required=True)
|
|
248
|
+
parser.add_argument("--methodology")
|
|
249
|
+
parser.add_argument("--verdict")
|
|
250
|
+
parser.add_argument("--intervention")
|
|
251
|
+
parser.add_argument("--evaluation")
|
|
252
|
+
parser.add_argument("--out", required=True)
|
|
253
|
+
args = parser.parse_args()
|
|
254
|
+
|
|
255
|
+
evidence = load_jsonl(Path(args.evidence))
|
|
256
|
+
md = render_pack(
|
|
257
|
+
load_json(Path(args.frame)) if args.frame else None,
|
|
258
|
+
evidence,
|
|
259
|
+
load_json(Path(args.methodology)) if args.methodology else None,
|
|
260
|
+
load_json(Path(args.verdict)) if args.verdict else None,
|
|
261
|
+
load_json(Path(args.intervention)) if args.intervention else None,
|
|
262
|
+
load_json(Path(args.evaluation)) if args.evaluation else None,
|
|
263
|
+
)
|
|
264
|
+
Path(args.out).write_text(md, encoding="utf-8")
|
|
265
|
+
print(f"wrote {args.out}")
|
|
266
|
+
return 0
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
if __name__ == "__main__":
|
|
270
|
+
sys.exit(main())
|