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,371 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""benchmark_evaluator.py - gold-based evaluator for Layer B empirical runs (v3).
|
|
3
|
+
|
|
4
|
+
Metrics are computed against gold annotations (benchmarks/annotations/gold-<id>.json),
|
|
5
|
+
never against the model's own claims:
|
|
6
|
+
|
|
7
|
+
outcome_separation_accuracy Jaccard(response outcomes, gold correct_outcome_types)
|
|
8
|
+
decision_calibration 1 if response action in gold expected_decision_range else 0
|
|
9
|
+
contradiction_recall fraction of gold known_contradictions detected in response
|
|
10
|
+
contradiction_precision fraction of response contradiction units that match a gold item
|
|
11
|
+
citation_support_recall fraction of gold key_supporting_sources mentioned in response
|
|
12
|
+
scope_calibration 1 if response scope section bounds claims (can/cannot/boundary)
|
|
13
|
+
|
|
14
|
+
Matching is deterministic token-overlap (CJK bigram + word) - no LLM judge is
|
|
15
|
+
required; method:heuristic is recorded on every metric so the report never
|
|
16
|
+
overstates precision. Means are reported with a normal-approximation 95% CI.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import math
|
|
22
|
+
import re
|
|
23
|
+
import sys
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
28
|
+
|
|
29
|
+
from benchmark import OUTCOME_SET # noqa: E402
|
|
30
|
+
|
|
31
|
+
ACTION_TOKENS = ("adopt", "pilot", "reject", "insufficient_evidence")
|
|
32
|
+
ACTION_ZH = {"adopt": "采用", "pilot": "试点", "reject": "拒绝",
|
|
33
|
+
"insufficient_evidence": "证据不足"}
|
|
34
|
+
|
|
35
|
+
# Chinese result descriptors -> Outcome Taxonomy token (evaluator language
|
|
36
|
+
# fairness: responses are mostly Chinese; matching English tokens only would
|
|
37
|
+
# systematically under-score both baselines).
|
|
38
|
+
ZH_OUTCOME_MAP = {
|
|
39
|
+
"正确率": "accuracy", "答题准确率": "accuracy", "准确性": "accuracy",
|
|
40
|
+
"完成时间": "completion_time", "速度": "completion_time",
|
|
41
|
+
"保持率": "retention", "记忆保持": "retention", "保持": "retention",
|
|
42
|
+
"长期保持": "retention", "保留": "retention",
|
|
43
|
+
"迁移": "transfer", "远迁移": "transfer", "近迁移": "transfer",
|
|
44
|
+
"独立解题": "independent_problem_solving", "独立问题解决": "independent_problem_solving",
|
|
45
|
+
"独立调试": "independent_problem_solving", "独立能力": "independent_problem_solving",
|
|
46
|
+
"概念理解": "concept_understanding",
|
|
47
|
+
"知识获得": "knowledge_gain", "知识获取": "knowledge_gain",
|
|
48
|
+
"期末成绩": "assignment_score", "考试成绩": "assignment_score",
|
|
49
|
+
"作业成绩": "assignment_score","学业成绩": "assignment_score",
|
|
50
|
+
"动机": "motivation", "学习动机": "motivation",
|
|
51
|
+
"参与度": "engagement","课堂参与": "engagement",
|
|
52
|
+
"认知负荷": "cognitive_load", "负荷": "cognitive_load",
|
|
53
|
+
"元认知": "metacognition",
|
|
54
|
+
"AI依赖": "ai_dependency","工具依赖": "ai_dependency",
|
|
55
|
+
"过度依赖": "over_reliance", "过度信任": "over_reliance",
|
|
56
|
+
"努力投入": "reduced_effort", "减少努力": "reduced_effort", "努力": "reduced_effort",
|
|
57
|
+
"迁移受损": "reduced_transfer",
|
|
58
|
+
"学术诚信": "academic_integrity_risk", "诚信": "academic_integrity_risk",
|
|
59
|
+
"虚假自信": "false_confidence", "过度自信": "false_confidence", "虚假信心": "false_confidence",
|
|
60
|
+
"代码质量": "code_quality",
|
|
61
|
+
"求助行为": "help_seeking", "求助": "help_seeking",
|
|
62
|
+
}
|
|
63
|
+
SCOPE_BOUNDARY_MARKERS = ("不能主张", "不能", "不适用", "范围", "超出", "仅限",
|
|
64
|
+
"cannot", "not extend", "beyond", "boundary", "only",
|
|
65
|
+
"不扩展到", "不推断")
|
|
66
|
+
CONTRADICTION_HINT = (
|
|
67
|
+
"contradict", "negative", "null", "但", "然而", "未发现", "没有显著", "下降",
|
|
68
|
+
"负向", "不一致", "反方", "however", "no significant", "did not",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
_ID_RE = re.compile(r"\b([A-Za-z][A-Za-z-]{1,40})\b")
|
|
72
|
+
_YEAR_RE = re.compile(r"\b(19|20)\d{2}\b")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _cjk_bigrams(text: str) -> set[str]:
|
|
76
|
+
chars = [c for c in text if "\u4e00" <= c <= "\u9fff"]
|
|
77
|
+
return {chars[i] + chars[i + 1] for i in range(len(chars) - 1)}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _words(text: str) -> set[str]:
|
|
81
|
+
return {w.lower() for w in _ID_RE.findall(text) if len(w) > 2}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _tokenize(text: str) -> set[str]:
|
|
85
|
+
return _cjk_bigrams(text) | _words(text)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _overlap(a: str, b: str) -> float:
|
|
89
|
+
ta, tb = _tokenize(a), _tokenize(b)
|
|
90
|
+
if not ta or not tb:
|
|
91
|
+
return 0.0
|
|
92
|
+
return len(ta & tb) / max(1, min(len(ta), len(tb)))
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def extract_json_block(text: str) -> dict[str, Any] | None:
|
|
96
|
+
"""Best-effort JSON extraction: prefers a fenced json block, else the
|
|
97
|
+
first balanced {...} block (Chinese responses often embed prose before
|
|
98
|
+
the JSON)."""
|
|
99
|
+
fence = text.find("```json")
|
|
100
|
+
start = text.find("{", fence + 7 if fence >= 0 else 0)
|
|
101
|
+
if start < 0:
|
|
102
|
+
return None
|
|
103
|
+
depth = 0
|
|
104
|
+
for i in range(start, len(text)):
|
|
105
|
+
if text[i] == "{":
|
|
106
|
+
depth += 1
|
|
107
|
+
elif text[i] == "}":
|
|
108
|
+
depth -= 1
|
|
109
|
+
if depth == 0:
|
|
110
|
+
block = text[start:i + 1]
|
|
111
|
+
try:
|
|
112
|
+
data = json.loads(block)
|
|
113
|
+
return data if isinstance(data, dict) else None
|
|
114
|
+
except json.JSONDecodeError:
|
|
115
|
+
return None
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _flatten(obj: Any) -> str:
|
|
120
|
+
if isinstance(obj, str):
|
|
121
|
+
return obj
|
|
122
|
+
if isinstance(obj, list):
|
|
123
|
+
return " ".join(_flatten(x) for x in obj)
|
|
124
|
+
if isinstance(obj, dict):
|
|
125
|
+
return " ".join(_flatten(v) for v in obj.values())
|
|
126
|
+
return str(obj)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def extract_outcomes(text: str) -> set[str]:
|
|
130
|
+
found = {o for o in OUTCOME_SET if o in text}
|
|
131
|
+
# Chinese result descriptors -> taxonomy tokens (language fairness)
|
|
132
|
+
for zh, token in ZH_OUTCOME_MAP.items():
|
|
133
|
+
if zh in text:
|
|
134
|
+
found.add(token)
|
|
135
|
+
data = extract_json_block(text)
|
|
136
|
+
if data:
|
|
137
|
+
for c in data.get("claims") or []:
|
|
138
|
+
ot = (c or {}).get("outcome_type")
|
|
139
|
+
if ot in OUTCOME_SET:
|
|
140
|
+
found.add(ot)
|
|
141
|
+
elif isinstance(ot, str) and ot in ZH_OUTCOME_MAP:
|
|
142
|
+
found.add(ZH_OUTCOME_MAP[ot])
|
|
143
|
+
return found
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def extract_action(text: str) -> str | None:
|
|
147
|
+
data = extract_json_block(text)
|
|
148
|
+
if data:
|
|
149
|
+
for key in ("recommended_action", "final_recommendation", "decision"):
|
|
150
|
+
val = data.get(key)
|
|
151
|
+
if isinstance(val, str) and val in ACTION_TOKENS:
|
|
152
|
+
return val
|
|
153
|
+
low = text.lower()
|
|
154
|
+
for token in ACTION_TOKENS:
|
|
155
|
+
if token in low:
|
|
156
|
+
return token
|
|
157
|
+
for token, zh in ACTION_ZH.items():
|
|
158
|
+
if zh in text:
|
|
159
|
+
return token
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _split_contradiction_units(text: str) -> list[str]:
|
|
164
|
+
"""Heuristic units: JSON contradictions list, else hint sentences."""
|
|
165
|
+
data = extract_json_block(text)
|
|
166
|
+
if data:
|
|
167
|
+
for key in ("contradictions", "contradictory_evidence", "risks"):
|
|
168
|
+
val = data.get(key)
|
|
169
|
+
if isinstance(val, list):
|
|
170
|
+
units = [_flatten(x).strip() for x in val]
|
|
171
|
+
if any(units):
|
|
172
|
+
return [u for u in units if u]
|
|
173
|
+
sentences = re.split(r"[。!?!?;;\n]", text)
|
|
174
|
+
return [s.strip() for s in sentences
|
|
175
|
+
if s.strip() and any(h in s.lower() for h in CONTRADICTION_HINT)]
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _mentions_source(resp_text: str, gold_source: str) -> bool:
|
|
179
|
+
"""Does the response plausibly cite this gold source? Heuristic: strong
|
|
180
|
+
token overlap with the source string, or author name + year co-occurrence."""
|
|
181
|
+
if _overlap(resp_text, gold_source) >= 0.5:
|
|
182
|
+
return True
|
|
183
|
+
years = _YEAR_RE.findall(gold_source)
|
|
184
|
+
names = [w for w in _words(gold_source) if w not in ("the", "and", "et", "al", "study")]
|
|
185
|
+
if names and years:
|
|
186
|
+
return any(n in resp_text.lower() for n in names) and any(y in resp_text for y in years)
|
|
187
|
+
return False
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def evaluate_attempt(response_text: str, gold: dict[str, Any]) -> dict[str, Any]:
|
|
191
|
+
resp = response_text or ""
|
|
192
|
+
gold_outcomes = set(gold.get("correct_outcome_types") or [])
|
|
193
|
+
resp_outcomes = extract_outcomes(resp)
|
|
194
|
+
# Outcome separation accuracy = gold coverage (recall): did the response
|
|
195
|
+
# identify the gold outcome types? Jaccard would punish thorough analyses
|
|
196
|
+
# that also mention adjacent outcomes (see docs/benchmark.md fairness note).
|
|
197
|
+
if gold_outcomes:
|
|
198
|
+
outcome_jaccard = len(gold_outcomes & resp_outcomes) / len(gold_outcomes)
|
|
199
|
+
else:
|
|
200
|
+
outcome_jaccard = 1.0 if not resp_outcomes else 0.0
|
|
201
|
+
|
|
202
|
+
action = extract_action(resp)
|
|
203
|
+
decision_ok = action in set(gold.get("expected_decision_range") or [])
|
|
204
|
+
|
|
205
|
+
gold_contra = [str(x) for x in (gold.get("known_contradictions") or []) if str(x).strip()]
|
|
206
|
+
resp_units = _split_contradiction_units(resp)
|
|
207
|
+
|
|
208
|
+
# Chinese sentence matching: bigram overlap OR shared 4+ char phrase
|
|
209
|
+
# (language fairness; a strict 0.35 bigram gate misses paraphrases).
|
|
210
|
+
def _contra_hit(unit: str, g: str) -> bool:
|
|
211
|
+
if _overlap(unit, g) >= 0.20:
|
|
212
|
+
return True
|
|
213
|
+
for n in (6, 5, 4):
|
|
214
|
+
gs = set(g[i:i + n] for i in range(len(g) - n + 1))
|
|
215
|
+
if any(p in unit for p in gs):
|
|
216
|
+
return True
|
|
217
|
+
return False
|
|
218
|
+
|
|
219
|
+
detected = [g for g in gold_contra if any(_contra_hit(u, g) for u in resp_units)]
|
|
220
|
+
contra_recall = len(detected) / len(gold_contra) if gold_contra else 1.0
|
|
221
|
+
contra_precision = (
|
|
222
|
+
sum(1 for u in resp_units if any(_contra_hit(u, g) for g in gold_contra))
|
|
223
|
+
/ len(resp_units) if resp_units else 1.0)
|
|
224
|
+
|
|
225
|
+
gold_sources = [str(x) for x in (gold.get("key_supporting_sources") or []) if str(x).strip()]
|
|
226
|
+
cited = [s for s in gold_sources if _mentions_source(resp, s)]
|
|
227
|
+
citation_recall = len(cited) / len(gold_sources) if gold_sources else 1.0
|
|
228
|
+
|
|
229
|
+
scope_text = resp.lower()
|
|
230
|
+
scope_ok = any(m in scope_text for m in SCOPE_BOUNDARY_MARKERS)
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
"outcome_separation_accuracy": round(outcome_jaccard, 4),
|
|
234
|
+
"decision_calibration": 1.0 if decision_ok else 0.0,
|
|
235
|
+
"contradiction_recall": round(contra_recall, 4),
|
|
236
|
+
"contradiction_precision": round(contra_precision, 4),
|
|
237
|
+
"citation_support_recall": round(citation_recall, 4),
|
|
238
|
+
"scope_calibration": 1.0 if scope_ok else 0.0,
|
|
239
|
+
"detected_outcomes": sorted(resp_outcomes),
|
|
240
|
+
"detected_action": action,
|
|
241
|
+
"method": "heuristic",
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
METRIC_KEYS = ("outcome_separation_accuracy", "decision_calibration",
|
|
246
|
+
"contradiction_recall", "contradiction_precision",
|
|
247
|
+
"citation_support_recall", "scope_calibration")
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _mean(values: list[float]) -> float:
|
|
251
|
+
return sum(values) / len(values) if values else 0.0
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _ci95(values: list[float]) -> float:
|
|
255
|
+
n = len(values)
|
|
256
|
+
if n < 2:
|
|
257
|
+
return 0.0
|
|
258
|
+
m = _mean(values)
|
|
259
|
+
sd = math.sqrt(sum((v - m) ** 2 for v in values) / (n - 1))
|
|
260
|
+
return 1.96 * sd / math.sqrt(n)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def evaluate_run(run_dir: Path, manifest: dict[str, Any], annotations_dir: Path) -> dict[str, Any]:
|
|
264
|
+
golds: dict[str, dict[str, Any]] = {}
|
|
265
|
+
for path in sorted(Path(annotations_dir).glob("gold-*.json")):
|
|
266
|
+
g = json.loads(path.read_text(encoding="utf-8"))
|
|
267
|
+
golds[g.get("id", path.stem.replace("gold-", ""))] = g
|
|
268
|
+
|
|
269
|
+
per_attempt: list[dict[str, Any]] = []
|
|
270
|
+
for entry in manifest.get("attempts", []):
|
|
271
|
+
row = {"attempt_id": entry["attempt_id"], "question_id": entry["question_id"],
|
|
272
|
+
"baseline": entry["baseline"], "attempt": entry["attempt"],
|
|
273
|
+
"status": entry["status"]}
|
|
274
|
+
if entry["status"] != "completed":
|
|
275
|
+
per_attempt.append(row)
|
|
276
|
+
continue
|
|
277
|
+
artifact_name = (entry.get("artifacts") or [None])[0]
|
|
278
|
+
if not artifact_name:
|
|
279
|
+
per_attempt.append(row)
|
|
280
|
+
continue
|
|
281
|
+
artifact = json.loads((run_dir / artifact_name).read_text(encoding="utf-8"))
|
|
282
|
+
gold = golds.get(entry["question_id"])
|
|
283
|
+
if not gold:
|
|
284
|
+
row["metrics"] = None
|
|
285
|
+
row["error"] = "no gold annotation"
|
|
286
|
+
per_attempt.append(row)
|
|
287
|
+
continue
|
|
288
|
+
metrics = evaluate_attempt(artifact.get("response", ""), gold)
|
|
289
|
+
row["metrics"] = {k: metrics[k] for k in METRIC_KEYS}
|
|
290
|
+
row["detected_action"] = metrics["detected_action"]
|
|
291
|
+
row["cost_usd"] = entry.get("cost_usd")
|
|
292
|
+
per_attempt.append(row)
|
|
293
|
+
|
|
294
|
+
by_baseline: dict[str, list[dict[str, Any]]] = {}
|
|
295
|
+
for row in per_attempt:
|
|
296
|
+
if row["status"] == "completed" and row.get("metrics"):
|
|
297
|
+
by_baseline.setdefault(row["baseline"], []).append(row)
|
|
298
|
+
|
|
299
|
+
per_baseline: dict[str, dict[str, Any]] = {}
|
|
300
|
+
for baseline, rows in by_baseline.items():
|
|
301
|
+
metrics: dict[str, dict[str, float]] = {}
|
|
302
|
+
for key in METRIC_KEYS:
|
|
303
|
+
values = [r["metrics"][key] for r in rows]
|
|
304
|
+
metrics[key] = {"mean": round(_mean(values), 4),
|
|
305
|
+
"ci95": round(_ci95(values), 4), "n": len(values)}
|
|
306
|
+
per_baseline[baseline] = {
|
|
307
|
+
"metrics": metrics,
|
|
308
|
+
"n": len(rows),
|
|
309
|
+
"total_cost_usd": round(sum(r.get("cost_usd") or 0.0 for r in rows), 4),
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
"run_id": manifest.get("run_id"),
|
|
314
|
+
"run_mode": manifest.get("run_mode"),
|
|
315
|
+
"environment": manifest.get("environment"),
|
|
316
|
+
"per_baseline": per_baseline,
|
|
317
|
+
"per_attempt": per_attempt,
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def report_from_run(run_dir: Path, manifest: dict[str, Any], out_path: Path) -> str:
|
|
322
|
+
"""Render the empirical benchmark report. The header states the run mode
|
|
323
|
+
explicitly so SIMULATED data can never be read as real performance."""
|
|
324
|
+
eval_path = run_dir / "evaluation.json"
|
|
325
|
+
if eval_path.is_file():
|
|
326
|
+
summary = json.loads(eval_path.read_text(encoding="utf-8"))
|
|
327
|
+
else:
|
|
328
|
+
summary = evaluate_run(run_dir, manifest, run_dir.parent.parent / "annotations")
|
|
329
|
+
|
|
330
|
+
mode = summary.get("run_mode", "unknown")
|
|
331
|
+
env = summary.get("environment", {}) or {}
|
|
332
|
+
attempts = manifest.get("attempts", [])
|
|
333
|
+
n_failed = sum(1 for a in attempts if a.get("status") == "failed")
|
|
334
|
+
n_budget = sum(1 for a in attempts if a.get("status") == "budget_stopped")
|
|
335
|
+
notes = manifest.get("notes") or ""
|
|
336
|
+
lines = [
|
|
337
|
+
"# EduEvidence Benchmark Report (v3)",
|
|
338
|
+
"",
|
|
339
|
+
f"- run_id: {summary.get('run_id')}",
|
|
340
|
+
f"- mode: **{'SIMULATED - harness validation only, NOT model performance' if mode == 'simulated' else 'EMPIRICAL'}**",
|
|
341
|
+
f"- driver: {env.get('driver')} | model: {env.get('model_family')} "
|
|
342
|
+
f"({env.get('model_version')}) | temperature: {env.get('temperature')}",
|
|
343
|
+
f"- tools: {', '.join(env.get('tools') or []) or 'none'} | "
|
|
344
|
+
f"search_provider: {env.get('search_provider')} | agent_mcp_used: {env.get('agent_mcp_used')}",
|
|
345
|
+
f"- attempts: {len(attempts)} total | failed: {n_failed} | budget_stopped: {n_budget}",
|
|
346
|
+
f"- notes: {notes or 'none'}",
|
|
347
|
+
"- cost: usage not metered by the cli/api driver (reported as 0.0 = NOT CAPTURED, not free)",
|
|
348
|
+
"",
|
|
349
|
+
"| Baseline | n | outcome_sep | decision_cal | contra_recall | contra_precision | citation_recall | scope_cal | cost_usd |",
|
|
350
|
+
"|---|---|---|---|---|---|---|---|---|",
|
|
351
|
+
]
|
|
352
|
+
for baseline in sorted(summary.get("per_baseline", {})):
|
|
353
|
+
m = summary["per_baseline"][baseline]["metrics"]
|
|
354
|
+
line = (f"| {baseline} | {m['outcome_separation_accuracy']['n']} "
|
|
355
|
+
f"| {m['outcome_separation_accuracy']['mean']:.3f}+-{m['outcome_separation_accuracy']['ci95']:.3f} "
|
|
356
|
+
f"| {m['decision_calibration']['mean']:.3f}+-{m['decision_calibration']['ci95']:.3f} "
|
|
357
|
+
f"| {m['contradiction_recall']['mean']:.3f}+-{m['contradiction_recall']['ci95']:.3f} "
|
|
358
|
+
f"| {m['contradiction_precision']['mean']:.3f}+-{m['contradiction_precision']['ci95']:.3f} "
|
|
359
|
+
f"| {m['citation_support_recall']['mean']:.3f}+-{m['citation_support_recall']['ci95']:.3f} "
|
|
360
|
+
f"| {m['scope_calibration']['mean']:.3f}+-{m['scope_calibration']['ci95']:.3f} "
|
|
361
|
+
f"| {summary['per_baseline'][baseline]['total_cost_usd']} |")
|
|
362
|
+
lines.append(line)
|
|
363
|
+
if mode == "simulated":
|
|
364
|
+
lines += [
|
|
365
|
+
"",
|
|
366
|
+
"> **SIMULATED**: deterministic synthetic data. This report validates the harness only "
|
|
367
|
+
"and must never be presented as model performance (docs/benchmark.md Layer A vs Layer B).",
|
|
368
|
+
]
|
|
369
|
+
markdown = "\n".join(lines) + "\n"
|
|
370
|
+
out_path.write_text(markdown, encoding="utf-8")
|
|
371
|
+
return markdown
|