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,257 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""validate.py — Fetch Validation Gate (Smart Web Fetch 方案 v3 §7-8).
|
|
3
|
+
|
|
4
|
+
A successful fetch is NOT automatically evidence. Checks:
|
|
5
|
+
|
|
6
|
+
HTTP success / body length / title match / URL match
|
|
7
|
+
login page? / error page? / captcha? / navigation only? / too short?
|
|
8
|
+
scheme whitelist (http/https) / private-network target (incl. redirects)
|
|
9
|
+
|
|
10
|
+
Output: passed(bool) + per-check results. FETCH_FAILED content must never be
|
|
11
|
+
used for Evidence Extraction (v3 §8).
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import ipaddress
|
|
16
|
+
import re
|
|
17
|
+
import socket
|
|
18
|
+
from typing import Any
|
|
19
|
+
from urllib.parse import urlparse
|
|
20
|
+
|
|
21
|
+
ERROR_PATTERNS = [
|
|
22
|
+
r"(?i)404 not found",
|
|
23
|
+
r"(?i)page not found",
|
|
24
|
+
r"(?i)access denied",
|
|
25
|
+
r"(?i)forbidden",
|
|
26
|
+
r"(?i)service unavailable",
|
|
27
|
+
]
|
|
28
|
+
LOGIN_PATTERNS = [
|
|
29
|
+
r"(?i)sign in to continue",
|
|
30
|
+
r"(?i)please log in",
|
|
31
|
+
r"(?i)login required",
|
|
32
|
+
]
|
|
33
|
+
CAPTCHA_PATTERNS = [
|
|
34
|
+
r"(?i)captcha",
|
|
35
|
+
r"(?i)verify you are human",
|
|
36
|
+
r"(?i)cloudflare",
|
|
37
|
+
r"(?i)robot check",
|
|
38
|
+
]
|
|
39
|
+
NAV_ONLY_MARKERS = ["menu", "home", "about us", "contact us", "privacy policy"]
|
|
40
|
+
|
|
41
|
+
ALLOWED_SCHEMES = ("http", "https")
|
|
42
|
+
|
|
43
|
+
# Third-party cleaning providers fetch a wrapped URL (r.jina.ai/<url>), not the
|
|
44
|
+
# original — a resolved-vs-original URL equality check is meaningless for them.
|
|
45
|
+
WRAPPER_PROVIDERS = ("jina_reader", "markdown_new")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _count(text: str, patterns: list[str]) -> int:
|
|
49
|
+
return sum(1 for p in patterns if re.search(p, text))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# ---------------------------------------------------------------- private URL
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
_CGNAT_NET = ipaddress.ip_network("100.64.0.0/10")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _ip_is_private(addr: ipaddress._BaseAddress) -> bool:
|
|
59
|
+
"""Loopback / private / link-local / CGNAT / reserved / multicast / unspecified."""
|
|
60
|
+
return (
|
|
61
|
+
addr.is_loopback
|
|
62
|
+
or addr.is_private
|
|
63
|
+
or addr.is_link_local
|
|
64
|
+
or addr.is_reserved
|
|
65
|
+
or addr.is_multicast
|
|
66
|
+
or addr.is_unspecified
|
|
67
|
+
or addr in _CGNAT_NET
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def is_private_url(url: str) -> bool:
|
|
72
|
+
"""True if the URL targets a private/local/unsafe location.
|
|
73
|
+
|
|
74
|
+
Host-based judgement only (urllib.parse.urlparse + ipaddress): loopback,
|
|
75
|
+
private, link-local, CGNAT, reserved, multicast and unspecified address
|
|
76
|
+
ranges, plus local hostnames (localhost / *.localhost / *.local). URL path
|
|
77
|
+
content — e.g. a DOI's "10." prefix — never influences the verdict.
|
|
78
|
+
|
|
79
|
+
Non-http(s) schemes (file://, ftp://, data:, ...) also count as private:
|
|
80
|
+
such URLs must never reach third-party cleaning providers.
|
|
81
|
+
"""
|
|
82
|
+
if not url:
|
|
83
|
+
return True
|
|
84
|
+
try:
|
|
85
|
+
parsed = urlparse(url)
|
|
86
|
+
except ValueError:
|
|
87
|
+
return True
|
|
88
|
+
scheme = (parsed.scheme or "").lower()
|
|
89
|
+
if scheme not in ALLOWED_SCHEMES:
|
|
90
|
+
return True
|
|
91
|
+
host = (parsed.hostname or "").lower()
|
|
92
|
+
if not host:
|
|
93
|
+
return True
|
|
94
|
+
if host == "localhost" or host.endswith(".localhost") or host.endswith(".local"):
|
|
95
|
+
return True
|
|
96
|
+
try:
|
|
97
|
+
addr = ipaddress.ip_address(host) # IPv4 literals and bracketed IPv6
|
|
98
|
+
except ValueError:
|
|
99
|
+
return False # a normal DNS name is not private by itself
|
|
100
|
+
return _ip_is_private(addr)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def resolves_to_private(url: str) -> bool | None:
|
|
104
|
+
"""Best-effort DNS re-check: does the URL's host resolve to a private IP?
|
|
105
|
+
|
|
106
|
+
Guards against redirect targets whose hostname resolves to a loopback /
|
|
107
|
+
private address (e.g. DNS-rebinding style redirects). Returns None when
|
|
108
|
+
the host cannot be resolved — that is treated as "not private" by callers.
|
|
109
|
+
"""
|
|
110
|
+
try:
|
|
111
|
+
parsed = urlparse(url)
|
|
112
|
+
except ValueError:
|
|
113
|
+
return True
|
|
114
|
+
host = parsed.hostname
|
|
115
|
+
if not host:
|
|
116
|
+
return True
|
|
117
|
+
try:
|
|
118
|
+
ipaddress.ip_address(host)
|
|
119
|
+
except ValueError:
|
|
120
|
+
pass
|
|
121
|
+
else:
|
|
122
|
+
return is_private_url(url) # literal IP: judge directly
|
|
123
|
+
try:
|
|
124
|
+
infos = socket.getaddrinfo(host, parsed.port or 80, proto=socket.IPPROTO_TCP)
|
|
125
|
+
except OSError:
|
|
126
|
+
return None
|
|
127
|
+
for info in infos:
|
|
128
|
+
try:
|
|
129
|
+
addr = ipaddress.ip_address(info[4][0])
|
|
130
|
+
except ValueError:
|
|
131
|
+
continue
|
|
132
|
+
if _ip_is_private(addr):
|
|
133
|
+
return True
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# ------------------------------------------------------------- URL comparison
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _normalize_url(url: str) -> str:
|
|
141
|
+
"""Normalize for comparison: lowercase scheme/host, drop default port,
|
|
142
|
+
strip fragment and trailing slash."""
|
|
143
|
+
try:
|
|
144
|
+
p = urlparse(url)
|
|
145
|
+
except ValueError:
|
|
146
|
+
return url
|
|
147
|
+
scheme = (p.scheme or "").lower()
|
|
148
|
+
host = (p.hostname or "").lower()
|
|
149
|
+
if p.port is None or p.port == {"http": 80, "https": 443}.get(scheme):
|
|
150
|
+
port_str = ""
|
|
151
|
+
else:
|
|
152
|
+
port_str = f":{p.port}"
|
|
153
|
+
path = p.path.rstrip("/") or "/"
|
|
154
|
+
query = f"?{p.query}" if p.query else ""
|
|
155
|
+
return f"{scheme}://{host}{port_str}{path}{query}"
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _same_path_and_query(resolved: str, original: str) -> bool:
|
|
159
|
+
"""True when both URLs denote the same resource path/query even if the host
|
|
160
|
+
changed — the canonical doi.org -> publisher redirect pattern."""
|
|
161
|
+
try:
|
|
162
|
+
rp = urlparse(resolved)
|
|
163
|
+
op = urlparse(original)
|
|
164
|
+
except ValueError:
|
|
165
|
+
return False
|
|
166
|
+
return (rp.path.rstrip("/") or "/") == (op.path.rstrip("/") or "/") and rp.query == op.query
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _same_doi(resolved: str, original: str) -> bool:
|
|
170
|
+
"""True when both URLs reference the same DOI (doi.org -> publisher hops)."""
|
|
171
|
+
from retrieval.source import parse_doi_from_url
|
|
172
|
+
|
|
173
|
+
r_doi = parse_doi_from_url(resolved)
|
|
174
|
+
o_doi = parse_doi_from_url(original)
|
|
175
|
+
return bool(r_doi and o_doi and r_doi.lower() == o_doi.lower())
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
# ---------------------------------------------------------------- validation
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def validate_fetch_result(result: dict[str, Any], *, expect_title: str | None = None) -> dict[str, Any]:
|
|
182
|
+
"""Run the Fetch Validation Gate over a fetch result dict.
|
|
183
|
+
|
|
184
|
+
Returns {"passed": bool, "checks": {...}, "issues": [...]}.
|
|
185
|
+
"""
|
|
186
|
+
content = result.get("content", "") or ""
|
|
187
|
+
status = result.get("fetch_status", "FETCH_FAILED")
|
|
188
|
+
checks: dict[str, Any] = {}
|
|
189
|
+
issues: list[str] = []
|
|
190
|
+
|
|
191
|
+
if status == "FETCH_FAILED":
|
|
192
|
+
checks["http_success"] = False
|
|
193
|
+
checks["body_length_ok"] = False
|
|
194
|
+
return {"passed": False, "checks": checks, "issues": ["FETCH_FAILED: no content"]}
|
|
195
|
+
|
|
196
|
+
checks["http_success"] = True
|
|
197
|
+
checks["body_length_ok"] = len(content) >= 200
|
|
198
|
+
if not checks["body_length_ok"]:
|
|
199
|
+
issues.append("body too short")
|
|
200
|
+
|
|
201
|
+
checks["is_error_page"] = _count(content, ERROR_PATTERNS) >= 2
|
|
202
|
+
checks["is_login_page"] = _count(content, LOGIN_PATTERNS) >= 1
|
|
203
|
+
checks["is_captcha_page"] = _count(content, CAPTCHA_PATTERNS) >= 1
|
|
204
|
+
checks["navigation_only"] = all(m in content.lower() for m in NAV_ONLY_MARKERS) and len(content) < 1000
|
|
205
|
+
|
|
206
|
+
if expect_title:
|
|
207
|
+
title = content.splitlines()[0].strip() if content.splitlines() else ""
|
|
208
|
+
checks["title_matches"] = expect_title.lower() in (title.lower() or content[:200].lower())
|
|
209
|
+
if not checks["title_matches"]:
|
|
210
|
+
issues.append("expected title not found in content head")
|
|
211
|
+
else:
|
|
212
|
+
checks["title_matches"] = None
|
|
213
|
+
|
|
214
|
+
# URL match: real comparison between the resolved URL (resp.geturl(), after
|
|
215
|
+
# redirects) and the original URL. Wrapper providers fetch a different URL
|
|
216
|
+
# by design, so the check is not applicable (None) for them.
|
|
217
|
+
provider = result.get("fetch_provider", "")
|
|
218
|
+
resolved_url = result.get("resolved_url") or ""
|
|
219
|
+
original_url = result.get("original_url") or ""
|
|
220
|
+
if provider in WRAPPER_PROVIDERS or not resolved_url or not original_url:
|
|
221
|
+
checks["url_matches"] = None
|
|
222
|
+
else:
|
|
223
|
+
checks["url_matches"] = (
|
|
224
|
+
_normalize_url(resolved_url) == _normalize_url(original_url)
|
|
225
|
+
or _same_path_and_query(resolved_url, original_url)
|
|
226
|
+
or _same_doi(resolved_url, original_url)
|
|
227
|
+
)
|
|
228
|
+
if not checks["url_matches"]:
|
|
229
|
+
issues.append("resolved URL does not match original URL")
|
|
230
|
+
|
|
231
|
+
# Scheme whitelist: only http/https may enter Evidence Extraction.
|
|
232
|
+
if original_url:
|
|
233
|
+
checks["scheme_allowed"] = (urlparse(original_url).scheme or "").lower() in ALLOWED_SCHEMES
|
|
234
|
+
if not checks["scheme_allowed"]:
|
|
235
|
+
issues.append("unsupported URL scheme (only http/https)")
|
|
236
|
+
else:
|
|
237
|
+
checks["scheme_allowed"] = None
|
|
238
|
+
|
|
239
|
+
# Private-target re-check after redirects/DNS. A public request that landed
|
|
240
|
+
# on a private network is a security event and must fail; a local request
|
|
241
|
+
# (original already private, fetched natively) stays usable.
|
|
242
|
+
if resolved_url and original_url:
|
|
243
|
+
original_private = is_private_url(original_url)
|
|
244
|
+
checks["private_target"] = is_private_url(resolved_url)
|
|
245
|
+
if checks["private_target"] and not original_private:
|
|
246
|
+
issues.append("fetch resolved to a private/local network")
|
|
247
|
+
elif resolved_url:
|
|
248
|
+
checks["private_target"] = is_private_url(resolved_url)
|
|
249
|
+
else:
|
|
250
|
+
checks["private_target"] = None
|
|
251
|
+
|
|
252
|
+
failed_hard = any(checks[k] for k in ("is_error_page", "is_login_page", "is_captcha_page", "navigation_only"))
|
|
253
|
+
if failed_hard:
|
|
254
|
+
issues.append("blocked/error-like page detected")
|
|
255
|
+
|
|
256
|
+
passed = not failed_hard and checks["body_length_ok"] and not issues
|
|
257
|
+
return {"passed": passed, "checks": checks, "issues": issues}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://github.com/37chengshan/eduevidence/schemas/agent-mcp-approval.schema.json",
|
|
4
|
+
"title": "AgentMcpApproval",
|
|
5
|
+
"description": "User approval record for the Agent MCP role -> CLI -> model mapping (Phase 8 Approval Gate). Only an explicit, hash-verified record permits spawning via safe_spawn(). Any change to the mapping (new CLI / replaced model / new role / modified mapping / raised budget / new provider) invalidates the record and requires re-confirmation.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["approved", "approved_at", "allowed_clis", "role_mapping_hash", "roles"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"approved": {
|
|
11
|
+
"type": "boolean",
|
|
12
|
+
"const": true,
|
|
13
|
+
"description": "Explicit user confirmation. A mere agent-mcp installation is NOT approval."
|
|
14
|
+
},
|
|
15
|
+
"approved_at": { "type": "string", "format": "date-time" },
|
|
16
|
+
"allowed_clis": {
|
|
17
|
+
"type": "array",
|
|
18
|
+
"description": "CLI set the user allowed for dispatch (scan scope + gate check).",
|
|
19
|
+
"items": { "type": "string" },
|
|
20
|
+
"minItems": 1,
|
|
21
|
+
"uniqueItems": true
|
|
22
|
+
},
|
|
23
|
+
"role_mapping_hash": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"description": "SHA-256 of the canonical 'roles' object; integrity check against tampering.",
|
|
26
|
+
"pattern": "^[0-9a-f]{64}$"
|
|
27
|
+
},
|
|
28
|
+
"roles": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"description": "role -> {cli, model} mapping the user explicitly approved; enforced verbatim by safe_spawn().",
|
|
31
|
+
"additionalProperties": {
|
|
32
|
+
"type": "object",
|
|
33
|
+
"additionalProperties": false,
|
|
34
|
+
"required": ["cli", "model"],
|
|
35
|
+
"properties": {
|
|
36
|
+
"cli": { "type": "string" },
|
|
37
|
+
"model": { "type": "string" }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"budget": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"description": "Approved cost/budget envelope (per-run or per-task); raising it requires re-confirmation.",
|
|
44
|
+
"additionalProperties": true
|
|
45
|
+
},
|
|
46
|
+
"provider": {
|
|
47
|
+
"type": ["string", "null"],
|
|
48
|
+
"description": "Approved provider endpoint; a new provider requires re-confirmation."
|
|
49
|
+
},
|
|
50
|
+
"schema_version": { "type": "integer", "const": 1 },
|
|
51
|
+
"extensions": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"description": "结构化扩展字段的统一容器(P1-01)。未列入本 schema 的字段必须放在这里,禁止在顶层新增属性。",
|
|
54
|
+
"additionalProperties": true
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://github.com/37chengshan/eduevidence/schemas/chart-spec.schema.json",
|
|
4
|
+
"title": "ChartSpec",
|
|
5
|
+
"description": "Chart spec produced by visualization build_charts.py. integrity is a per-check map with PASS/FAIL/NOT_CHECKED strings (P0-2 rewrite to match the real producer).",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": [
|
|
9
|
+
"chart_id",
|
|
10
|
+
"purpose",
|
|
11
|
+
"engine",
|
|
12
|
+
"chart_type"
|
|
13
|
+
],
|
|
14
|
+
"properties": {
|
|
15
|
+
"chart_id": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"minLength": 1
|
|
18
|
+
},
|
|
19
|
+
"purpose": {
|
|
20
|
+
"type": "string"
|
|
21
|
+
},
|
|
22
|
+
"engine": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"enum": [
|
|
25
|
+
"echarts",
|
|
26
|
+
"svg"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"chart_type": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"minLength": 1
|
|
32
|
+
},
|
|
33
|
+
"semantic_basis": {
|
|
34
|
+
"type": "string"
|
|
35
|
+
},
|
|
36
|
+
"title": {
|
|
37
|
+
"type": "string"
|
|
38
|
+
},
|
|
39
|
+
"option": {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"additionalProperties": true
|
|
42
|
+
},
|
|
43
|
+
"summary_text": {
|
|
44
|
+
"type": [
|
|
45
|
+
"string",
|
|
46
|
+
"null"
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
"integrity": {
|
|
50
|
+
"type": "object",
|
|
51
|
+
"additionalProperties": false,
|
|
52
|
+
"properties": {
|
|
53
|
+
"numbers_match_result": {
|
|
54
|
+
"type": "string",
|
|
55
|
+
"enum": [
|
|
56
|
+
"PASS",
|
|
57
|
+
"FAIL",
|
|
58
|
+
"NOT_CHECKED"
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
"no_axis_distortion": {
|
|
62
|
+
"type": "string",
|
|
63
|
+
"enum": [
|
|
64
|
+
"PASS",
|
|
65
|
+
"FAIL",
|
|
66
|
+
"NOT_CHECKED"
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
"no_false_precision": {
|
|
70
|
+
"type": "string",
|
|
71
|
+
"enum": [
|
|
72
|
+
"PASS",
|
|
73
|
+
"FAIL",
|
|
74
|
+
"NOT_CHECKED"
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
"colorblind_safe": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"enum": [
|
|
80
|
+
"PASS",
|
|
81
|
+
"FAIL",
|
|
82
|
+
"NOT_CHECKED"
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://github.com/37chengshan/eduevidence/schemas/cross-model-review.schema.json",
|
|
4
|
+
"title": "CrossModelReview",
|
|
5
|
+
"description": "Output contract of the independent-model cross review (总体实施计划 §25). An independent model (different model/CLI than the primary analysis) verifies a Draft Verdict before the Final Judge rules. 扩展字段一律放在 extensions 内。",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["agreement", "final_recommendation"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"agreement": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "To what degree the reviewer agrees with the draft verdict (e.g. 'high/partial/low agreement on the four-state decision')."
|
|
13
|
+
},
|
|
14
|
+
"disagreements": { "type": "array", "items": { "type": "string" } },
|
|
15
|
+
"unsupported_claims": { "type": "array", "items": { "type": "string" } },
|
|
16
|
+
"missed_counterevidence": { "type": "array", "items": { "type": "string" } },
|
|
17
|
+
"scope_violations": { "type": "array", "items": { "type": "string" } },
|
|
18
|
+
"methodology_issues": { "type": "array", "items": { "type": "string" } },
|
|
19
|
+
"confidence_adjustment": { "type": "string", "enum": ["upgrade", "downgrade", "no_change"] },
|
|
20
|
+
"required_revision": { "type": "boolean", "default": false },
|
|
21
|
+
"final_recommendation": { "type": "string" },
|
|
22
|
+
"extensions": {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"description": "结构化扩展字段的统一容器(P1-01)。未列入本 schema 的字段必须放在这里,禁止在顶层新增属性。",
|
|
25
|
+
"additionalProperties": true
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://github.com/37chengshan/eduevidence/schemas/education-frame.schema.json",
|
|
4
|
+
"title": "EducationResearchFrame",
|
|
5
|
+
"description": "Structured framing of an education research question. Framing MUST be completed before any final teaching recommendation is generated (EduEvidence EvidenceFlow Protocol, step 1). 扩展字段一律放在 extensions 内。",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["question", "decision_target"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"question": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"minLength": 5,
|
|
13
|
+
"description": "The teaching/education question, e.g. 'Should first-year C programming students be allowed to use generative AI coding assistants?'"
|
|
14
|
+
},
|
|
15
|
+
"decision_target": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"enum": ["evidence_review", "teaching_decision", "pilot_design", "evaluation_design"],
|
|
18
|
+
"description": "What the user wants to produce. Default for the skill: evidence_review."
|
|
19
|
+
},
|
|
20
|
+
"learner": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"additionalProperties": true,
|
|
23
|
+
"properties": {
|
|
24
|
+
"education_level": { "type": "string", "examples": ["undergraduate_year_1"] },
|
|
25
|
+
"major": { "type": "string", "examples": ["computer_science"] },
|
|
26
|
+
"prior_knowledge": { "type": "string", "examples": ["first_programming_course"] },
|
|
27
|
+
"special_characteristics": { "type": "string", "examples": ["mixed_ability", "large_class"] }
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"course": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"additionalProperties": true,
|
|
33
|
+
"properties": {
|
|
34
|
+
"subject": { "type": "string", "examples": ["C_programming"] },
|
|
35
|
+
"course_type": { "type": "string", "examples": ["lecture_lab", "lab_only", "flipped"] },
|
|
36
|
+
"duration": { "type": "string", "examples": ["16_weeks", "one_semester"] }
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"intervention": {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"additionalProperties": true,
|
|
42
|
+
"properties": {
|
|
43
|
+
"teaching_method": { "type": "string", "examples": ["paired_programming", "problem_based_learning"] },
|
|
44
|
+
"ai_tool": { "type": "string", "examples": ["github_copilot", "chatgpt", "generic_generative_ai"] },
|
|
45
|
+
"allowed_usage": { "type": "string", "examples": ["explain_errors_only", "full_code_generation"] },
|
|
46
|
+
"frequency": { "type": "string", "examples": ["every_class", "weekly"] },
|
|
47
|
+
"duration": { "type": "string", "examples": ["8_weeks"] }
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"comparison": {
|
|
51
|
+
"type": "string",
|
|
52
|
+
"description": "Comparison condition, e.g. 'no AI usage', 'textbook-only', 'TA support only'."
|
|
53
|
+
},
|
|
54
|
+
"outcomes": {
|
|
55
|
+
"type": "object",
|
|
56
|
+
"additionalProperties": true,
|
|
57
|
+
"properties": {
|
|
58
|
+
"primary": {
|
|
59
|
+
"type": "array",
|
|
60
|
+
"items": { "type": "string", "enum": [
|
|
61
|
+
"knowledge_gain", "concept_understanding", "retention", "transfer",
|
|
62
|
+
"independent_problem_solving", "completion_time", "accuracy",
|
|
63
|
+
"code_quality", "assignment_score", "engagement", "motivation",
|
|
64
|
+
"cognitive_load", "help_seeking", "metacognition", "ai_dependency",
|
|
65
|
+
"over_reliance", "reduced_effort", "reduced_transfer",
|
|
66
|
+
"academic_integrity_risk", "false_confidence"
|
|
67
|
+
] }
|
|
68
|
+
},
|
|
69
|
+
"secondary": {
|
|
70
|
+
"type": "array",
|
|
71
|
+
"items": { "type": "string" }
|
|
72
|
+
},
|
|
73
|
+
"risk": {
|
|
74
|
+
"type": "array",
|
|
75
|
+
"items": { "type": "string" }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"context": {
|
|
80
|
+
"type": "object",
|
|
81
|
+
"additionalProperties": true,
|
|
82
|
+
"properties": {
|
|
83
|
+
"teacher_support": { "type": "string", "examples": ["TA_supported", "trained_instructors"] },
|
|
84
|
+
"class_size": { "type": "string", "examples": ["60_students"] },
|
|
85
|
+
"online_or_offline": { "type": "string", "enum": ["online", "offline", "hybrid"] }
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"scope": {
|
|
89
|
+
"type": "object",
|
|
90
|
+
"additionalProperties": true,
|
|
91
|
+
"properties": {
|
|
92
|
+
"time_range": { "type": "string", "examples": ["2019-2025"] },
|
|
93
|
+
"geography": { "type": "string", "examples": ["worldwide"] },
|
|
94
|
+
"study_types": { "type": "array", "items": { "type": "string" } }
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"inclusion_criteria": { "type": "array", "items": { "type": "string" } },
|
|
98
|
+
"exclusion_criteria": { "type": "array", "items": { "type": "string" } },
|
|
99
|
+
"success_condition": { "type": "string", "description": "What would count as a successful outcome for this decision." },
|
|
100
|
+
"extensions": {
|
|
101
|
+
"type": "object",
|
|
102
|
+
"description": "结构化扩展字段的统一容器(P1-01)。未列入本 schema 的字段必须放在这里,禁止在顶层新增属性。",
|
|
103
|
+
"additionalProperties": true
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://github.com/37chengshan/eduevidence/schemas/evaluation.schema.json",
|
|
4
|
+
"title": "EvaluationPlan",
|
|
5
|
+
"description": "Evaluation design attached to any PILOT / ADOPT recommendation. Must separate Task Performance from Learning, and Immediate from Retention/Transfer. 扩展字段一律放在 extensions 内。",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["research_question", "groups", "analysis_plan"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"research_question": { "type": "string" },
|
|
11
|
+
"groups": {
|
|
12
|
+
"type": "object",
|
|
13
|
+
"additionalProperties": true,
|
|
14
|
+
"properties": {
|
|
15
|
+
"treatment": { "type": "string" },
|
|
16
|
+
"comparison": { "type": "string" }
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"baseline": { "type": "string", "description": "Pre-test / baseline measurement." },
|
|
20
|
+
"post_test": { "type": "string" },
|
|
21
|
+
"retention_test": { "type": ["string", "null"], "description": "Delayed test (e.g. 4-8 weeks after intervention)." },
|
|
22
|
+
"transfer_test": { "type": ["string", "null"], "description": "New task in a no-AI environment to verify real transfer." },
|
|
23
|
+
"process_metrics": { "type": "array", "items": { "type": "string" } },
|
|
24
|
+
"learning_metrics": { "type": "array", "items": { "type": "string" } },
|
|
25
|
+
"risk_metrics": { "type": "array", "items": { "type": "string" } },
|
|
26
|
+
"analysis_plan": { "type": "string" },
|
|
27
|
+
"success_threshold": { "type": "string" },
|
|
28
|
+
"stop_conditions": { "type": "array", "items": { "type": "string" } },
|
|
29
|
+
"extensions": {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"description": "结构化扩展字段的统一容器(P1-01)。未列入本 schema 的字段必须放在这里,禁止在顶层新增属性。",
|
|
32
|
+
"additionalProperties": true
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://github.com/37chengshan/eduevidence/schemas/evidence.schema.json",
|
|
4
|
+
"title": "EvidenceObject",
|
|
5
|
+
"description": "Claim-level evidence object. MANDATORY fields (schema v1.1): evidence_id, source_id, study_id, sample_id, claim_id, claim, outcome_type, relation_to_claim, effect_direction, source_location. Missing any mandatory field marks the object as UNSUPPORTED. 三种方向语义严格分开:relation_to_claim(支持/反驳某条 claim)、effect_direction(研究观察到的效应方向)、decision_relation(对最终教学决策的意义)。direction 为旧契约字段,仅向后兼容,已废弃(deprecated)。扩展字段一律放在 extensions 内。",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["evidence_id", "source_id", "study_id", "sample_id", "claim_id", "claim", "outcome_type", "relation_to_claim", "effect_direction", "source_location"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"evidence_id": { "type": "string", "examples": ["E-001"] },
|
|
11
|
+
"source_id": { "type": "string", "examples": ["S-2023-kazemitabaar"] },
|
|
12
|
+
"study_id": { "type": "string", "description": "所属研究的稳定标识,用于独立研究计数(独立于 evidence_id,防止同一研究的多条 Evidence Object 重复放大信心)。" },
|
|
13
|
+
"sample_id": { "type": "string", "description": "所属样本的稳定标识,用于独立样本计数。无法区分样本时可显式使用 UNKNOWN-SAMPLE-<study_id>,但不能静默缺失。" },
|
|
14
|
+
"claim_id": { "type": "string", "description": "该证据所绑定 Claim 的稳定标识(Claim Registry 一等字段,Claim -> Evidence -> Source 不再依赖渲染阶段临时编号)。", "examples": ["C-003"] },
|
|
15
|
+
"title": { "type": "string" },
|
|
16
|
+
"year": { "type": ["integer", "null"] },
|
|
17
|
+
"study_type": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"enum": ["rct", "quasi_experimental", "observational", "survey", "qualitative", "meta_analysis", "literature_review", "case_study", "mixed_methods"]
|
|
20
|
+
},
|
|
21
|
+
"education_level": { "type": "string", "examples": ["undergraduate", "k12", "graduate"] },
|
|
22
|
+
"subject": { "type": "string", "examples": ["programming", "mathematics", "writing"] },
|
|
23
|
+
"population": { "type": "string", "examples": ["first_year_CS_students"] },
|
|
24
|
+
"sample_size": { "type": ["integer", "null"], "minimum": 0 },
|
|
25
|
+
"intervention": { "type": "string", "examples": ["copilot_allowed_in_lab"] },
|
|
26
|
+
"comparison": { "type": "string", "examples": ["no_ai_control_group"] },
|
|
27
|
+
"outcome_type": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"enum": [
|
|
30
|
+
"knowledge_gain", "concept_understanding", "retention", "transfer",
|
|
31
|
+
"independent_problem_solving", "completion_time", "accuracy",
|
|
32
|
+
"code_quality", "assignment_score", "engagement", "motivation",
|
|
33
|
+
"cognitive_load", "help_seeking", "metacognition", "ai_dependency",
|
|
34
|
+
"over_reliance", "reduced_effort", "reduced_transfer",
|
|
35
|
+
"academic_integrity_risk", "false_confidence"
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
"outcome_measure": { "type": "string", "examples": ["final_exam_score", "post_test_transfer_task"] },
|
|
39
|
+
"claim": { "type": "string", "description": "The specific claim this evidence supports or contradicts." },
|
|
40
|
+
"direction": { "type": "string", "enum": ["support", "contradict", "neutral"], "description": "DEPRECATED: legacy direction field, kept for backward compatibility only. Use relation_to_claim instead." },
|
|
41
|
+
"relation_to_claim": { "type": "string", "enum": ["support", "contradict", "neutral"], "description": "该证据与 claim 的关系(支持/反驳/中性)。Claim Audit 只依据此字段判断支持/反驳,不得用 effect_direction 或 legacy direction 替代。" },
|
|
42
|
+
"effect_direction": { "type": "string", "enum": ["positive", "negative", "null"], "description": "研究观察到的效应方向。Outcome 可视化 / 聚合只依据此字段。" },
|
|
43
|
+
"decision_relation": { "type": "string", "enum": ["support_adoption", "oppose_adoption", "conditional", "neutral"], "description": "该证据对最终教学决策的意义(支持采纳/反对采纳/有条件采纳/中性)。跨研究一致性(Consistency)与 Tribunal 依据此字段计算。" },
|
|
44
|
+
"effect": { "type": "string", "examples": ["positive_medium", "null", "negative_small"] },
|
|
45
|
+
"duration": { "type": "string", "examples": ["8_weeks"] },
|
|
46
|
+
"method": { "type": "string", "examples": ["quasi_experimental_pre_post"] },
|
|
47
|
+
"strengths": { "type": "array", "items": { "type": "string" } },
|
|
48
|
+
"limitations": { "type": "array", "items": { "type": "string" } },
|
|
49
|
+
"confounders": { "type": "array", "items": { "type": "string" } },
|
|
50
|
+
"source_location": { "type": "string", "description": "Verifiable pointer to the source (DOI / URL / database identifier / page)." },
|
|
51
|
+
"quality_dimensions": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"additionalProperties": true,
|
|
54
|
+
"description": "D1 Study Design, D2 Sample Quality, D3 Measurement Validity, D4 Temporal Strength, D5 Directness. Each scored 0/1/2.",
|
|
55
|
+
"properties": {
|
|
56
|
+
"D1_study_design": { "type": "integer", "minimum": 0, "maximum": 2 },
|
|
57
|
+
"D2_sample_quality": { "type": "integer", "minimum": 0, "maximum": 2 },
|
|
58
|
+
"D3_measurement_validity": { "type": "integer", "minimum": 0, "maximum": 2 },
|
|
59
|
+
"D4_temporal_strength": { "type": "integer", "minimum": 0, "maximum": 2 },
|
|
60
|
+
"D5_directness": { "type": "integer", "minimum": 0, "maximum": 2 }
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"quality_score": { "type": ["number", "null"], "minimum": 0, "maximum": 10 },
|
|
64
|
+
"evidence_level": {
|
|
65
|
+
"type": "string",
|
|
66
|
+
"enum": ["strong", "moderate", "weak", "very_weak"]
|
|
67
|
+
},
|
|
68
|
+
"applicability": {
|
|
69
|
+
"type": "object",
|
|
70
|
+
"additionalProperties": true,
|
|
71
|
+
"description": "Match against the target context: learner match, subject match, tool match, usage match, teacher support match."
|
|
72
|
+
},
|
|
73
|
+
"confidence": { "type": ["number", "null"], "minimum": 0, "maximum": 1 },
|
|
74
|
+
"status": { "type": "string", "enum": ["SUPPORTED", "UNSUPPORTED", "DOWNGRADE_CONFIDENCE", "CONTRADICT"], "default": "SUPPORTED" },
|
|
75
|
+
"extensions": {
|
|
76
|
+
"type": "object",
|
|
77
|
+
"description": "结构化扩展字段的统一容器(P1-01)。未列入本 schema 的字段必须放在这里,禁止在顶层新增属性。",
|
|
78
|
+
"additionalProperties": true
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|