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,397 @@
|
|
|
1
|
+
"""Conservative V1 → V2 migration.
|
|
2
|
+
|
|
3
|
+
Existing V1 runs/example packs are immutable historical artifacts: this
|
|
4
|
+
importer reads them, never rewrites them. One V1 Evidence Object becomes at
|
|
5
|
+
least one Finding + one EvidenceLink; `relation_to_claim` maps to the link,
|
|
6
|
+
`effect_direction` maps to the Finding, `decision_relation` (or its
|
|
7
|
+
deterministic fallback) maps to the link's decision_implication. Legacy
|
|
8
|
+
missing Study identity becomes an explicit unresolved Study placeholder —
|
|
9
|
+
never silently an independent study. Every inference, downgrade and
|
|
10
|
+
unresolved identity is recorded in migration_report.json.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from datetime import datetime, timezone
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from engine.contracts import validate_record
|
|
21
|
+
from engine.graph_store import GraphStore, GraphMutation
|
|
22
|
+
from engine.ids import new_local_id
|
|
23
|
+
from engine.project import ProjectWorkspace
|
|
24
|
+
from engine.run import start_run, finish_run
|
|
25
|
+
from engine.versions import (
|
|
26
|
+
ENGINE_VERSION,
|
|
27
|
+
GRAPH_SCHEMA_VERSION,
|
|
28
|
+
METHODOLOGY_POLICY_VERSION,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
OUTCOME_TYPES = ("learning", "task_performance", "process", "risk")
|
|
32
|
+
|
|
33
|
+
_CLAIM_TO_IMPLICATION = {
|
|
34
|
+
"support": "support_adoption",
|
|
35
|
+
"contradict": "oppose_adoption",
|
|
36
|
+
"neutral": "neutral",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _now_iso() -> str:
|
|
41
|
+
return datetime.now(timezone.utc).isoformat()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True)
|
|
45
|
+
class MigrationResult:
|
|
46
|
+
project_id: str
|
|
47
|
+
graph_revision: int
|
|
48
|
+
migration_report_path: Path
|
|
49
|
+
warnings: tuple[str, ...]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _v1_outcome_type(raw: str) -> str:
|
|
53
|
+
"""Map a V1 outcome_type into the four-category taxonomy (conservative)."""
|
|
54
|
+
if raw in OUTCOME_TYPES:
|
|
55
|
+
return raw
|
|
56
|
+
return "learning" # most V1 education outcomes are learning-related
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _map_effect_direction(raw) -> str:
|
|
60
|
+
if raw in ("positive", "negative", "null"):
|
|
61
|
+
return raw
|
|
62
|
+
return "null"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _map_relation(raw) -> str:
|
|
66
|
+
if raw in ("support", "contradict", "neutral"):
|
|
67
|
+
return raw
|
|
68
|
+
return "neutral"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _map_decision_implication(ev: dict) -> str:
|
|
72
|
+
raw = ev.get("decision_relation")
|
|
73
|
+
if raw in ("support_adoption", "oppose_adoption", "conditional", "neutral"):
|
|
74
|
+
return raw
|
|
75
|
+
return _CLAIM_TO_IMPLICATION[_map_relation(ev.get("relation_to_claim") or ev.get("direction"))]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def migrate_v1_pack(pack_dir: Path, *, home: Path,
|
|
79
|
+
title: str | None = None) -> MigrationResult:
|
|
80
|
+
"""Import a V1 pack directory into a new V2 Project graph.
|
|
81
|
+
|
|
82
|
+
Reads `result.json` from `pack_dir`. Returns a MigrationResult with the
|
|
83
|
+
created Project and the migration report path.
|
|
84
|
+
"""
|
|
85
|
+
pack_dir = Path(pack_dir).expanduser().resolve()
|
|
86
|
+
result_path = pack_dir / "result.json"
|
|
87
|
+
if not result_path.is_file():
|
|
88
|
+
raise FileNotFoundError(f"V1 pack has no result.json at {pack_dir}")
|
|
89
|
+
|
|
90
|
+
data = json.loads(result_path.read_text(encoding="utf-8"))
|
|
91
|
+
question = (data.get("research_frame") or {}).get("education_question") or (
|
|
92
|
+
data.get("meta") or {}).get("question") or title or "Migrated V1 research"
|
|
93
|
+
title = title or "V1 migrated research"
|
|
94
|
+
|
|
95
|
+
ws = ProjectWorkspace.create(
|
|
96
|
+
home, question=question, title=title, research_mode="evidence_review")
|
|
97
|
+
run = start_run(ws, purpose="V1 migration import",
|
|
98
|
+
capabilities=["migration"],
|
|
99
|
+
execution_backend="sequential_main_agent")
|
|
100
|
+
|
|
101
|
+
warnings: list[str] = []
|
|
102
|
+
|
|
103
|
+
report: dict = {
|
|
104
|
+
"source_pack": str(pack_dir),
|
|
105
|
+
"created_project_id": ws.project_id,
|
|
106
|
+
"preserved_ids": [],
|
|
107
|
+
"generated_ids": [],
|
|
108
|
+
"unresolved_studies": [],
|
|
109
|
+
"downgrades": [],
|
|
110
|
+
"warnings": warnings,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# ---- sources ---------------------------------------------------------
|
|
114
|
+
sources: list[dict] = []
|
|
115
|
+
for src in data.get("sources", []):
|
|
116
|
+
src_id = src.get("source_id") or new_local_id("SRC", set())
|
|
117
|
+
locator = (src.get("canonical_url") or src.get("source_location")
|
|
118
|
+
or f"project://{ws.project_id}/legacy-source/{src_id}")
|
|
119
|
+
status = "valid" if src.get("authority_level") not in (None, "") else "accepted_partial"
|
|
120
|
+
rec = {
|
|
121
|
+
"source_id": src_id,
|
|
122
|
+
"origin": "external",
|
|
123
|
+
"source_type": src.get("authority_level") or "legacy",
|
|
124
|
+
"canonical_locator": locator,
|
|
125
|
+
"validation_status": status,
|
|
126
|
+
"content_hash": None,
|
|
127
|
+
"extensions": {
|
|
128
|
+
"v1_legacy": True,
|
|
129
|
+
"v1_title": src.get("title"),
|
|
130
|
+
"v1_year": src.get("year"),
|
|
131
|
+
},
|
|
132
|
+
}
|
|
133
|
+
sources.append(rec)
|
|
134
|
+
report["preserved_ids"].append(src_id)
|
|
135
|
+
source_ids = {s["source_id"] for s in sources}
|
|
136
|
+
|
|
137
|
+
def _study_source_refs(ev: dict) -> list[str]:
|
|
138
|
+
"""Resolve a V1 evidence's source reference; never silently empty.
|
|
139
|
+
|
|
140
|
+
An unknown/missing source_id gets an explicit placeholder Source
|
|
141
|
+
(validation_status=failed, project origin) so provenance stays
|
|
142
|
+
visible instead of being dropped.
|
|
143
|
+
"""
|
|
144
|
+
sid = ev.get("source_id")
|
|
145
|
+
if sid and sid in source_ids:
|
|
146
|
+
return [sid]
|
|
147
|
+
placeholder_id = new_local_id("SRC", {s["source_id"] for s in sources})
|
|
148
|
+
sources.append({
|
|
149
|
+
"source_id": placeholder_id,
|
|
150
|
+
"origin": "project",
|
|
151
|
+
"source_type": "legacy_unresolved",
|
|
152
|
+
"canonical_locator": f"project://{ws.project_id}/legacy-source/{placeholder_id}",
|
|
153
|
+
"validation_status": "failed",
|
|
154
|
+
"content_hash": None,
|
|
155
|
+
"extensions": {"v1_legacy": True,
|
|
156
|
+
"v1_original_source_id": sid,
|
|
157
|
+
"v1_evidence_id": ev.get("evidence_id")},
|
|
158
|
+
})
|
|
159
|
+
source_ids.add(placeholder_id)
|
|
160
|
+
report["generated_ids"].append(placeholder_id)
|
|
161
|
+
warnings.append(
|
|
162
|
+
f"evidence {ev.get('evidence_id')} references unknown source "
|
|
163
|
+
f"{sid!r}; created placeholder Source {placeholder_id} "
|
|
164
|
+
f"(validation_status=failed)"
|
|
165
|
+
)
|
|
166
|
+
return [placeholder_id]
|
|
167
|
+
|
|
168
|
+
# ---- studies ---------------------------------------------------------
|
|
169
|
+
# V1 Evidence Objects carry study_id; group by it. Missing study_id gets
|
|
170
|
+
# an explicit unresolved placeholder Study (one per evidence object).
|
|
171
|
+
used_ids = {s["source_id"] for s in sources} | {s["study_id"] for s in
|
|
172
|
+
data.get("evidence", [])
|
|
173
|
+
if s.get("study_id")}
|
|
174
|
+
studies: list[dict] = []
|
|
175
|
+
study_by_v1: dict[str, str] = {}
|
|
176
|
+
evidence_by_study: dict[str, list[dict]] = {}
|
|
177
|
+
legacy_count = 0
|
|
178
|
+
for ev in data.get("evidence", []):
|
|
179
|
+
vid = ev.get("study_id")
|
|
180
|
+
if vid:
|
|
181
|
+
evidence_by_study.setdefault(vid, []).append(ev)
|
|
182
|
+
else:
|
|
183
|
+
legacy_count += 1
|
|
184
|
+
legacy_id = new_local_id("STU", used_ids)
|
|
185
|
+
studies.append({
|
|
186
|
+
"study_id": legacy_id,
|
|
187
|
+
"source_ids": _study_source_refs(ev),
|
|
188
|
+
"study_design": (ev.get("study_type") or "unknown").lower(),
|
|
189
|
+
"population": ev.get("population") or ev.get("education_level") or "unknown",
|
|
190
|
+
"sample_ids": [ev.get("sample_id")] if ev.get("sample_id") else [],
|
|
191
|
+
"sample_size": ev.get("sample_size"),
|
|
192
|
+
"intervention": ev.get("intervention"),
|
|
193
|
+
"comparison": ev.get("comparison"),
|
|
194
|
+
"independence_key": f"legacy:{ev.get('evidence_id')}",
|
|
195
|
+
"identity_status": "unresolved",
|
|
196
|
+
"extensions": {"v1_legacy": True},
|
|
197
|
+
})
|
|
198
|
+
study_by_v1[ev["evidence_id"]] = legacy_id
|
|
199
|
+
report["unresolved_studies"].append(legacy_id)
|
|
200
|
+
report["generated_ids"].append(legacy_id)
|
|
201
|
+
warnings.append(
|
|
202
|
+
f"evidence {ev.get('evidence_id')} lacks study_id; created "
|
|
203
|
+
f"explicit unresolved Study {legacy_id}"
|
|
204
|
+
)
|
|
205
|
+
for vid, evs in evidence_by_study.items():
|
|
206
|
+
first = evs[0]
|
|
207
|
+
studies.append({
|
|
208
|
+
"study_id": vid,
|
|
209
|
+
"source_ids": _study_source_refs(first),
|
|
210
|
+
"study_design": (first.get("study_type") or "unknown").lower(),
|
|
211
|
+
"population": first.get("population") or first.get("education_level") or "unknown",
|
|
212
|
+
"sample_ids": [first.get("sample_id")] if first.get("sample_id") else [],
|
|
213
|
+
"sample_size": first.get("sample_size"),
|
|
214
|
+
"intervention": first.get("intervention"),
|
|
215
|
+
"comparison": first.get("comparison"),
|
|
216
|
+
"independence_key": f"v1:{vid}",
|
|
217
|
+
"identity_status": "legacy",
|
|
218
|
+
"extensions": {"v1_legacy": True},
|
|
219
|
+
})
|
|
220
|
+
report["preserved_ids"].append(vid)
|
|
221
|
+
study_ids = {s["study_id"] for s in studies}
|
|
222
|
+
|
|
223
|
+
# ---- outcomes --------------------------------------------------------
|
|
224
|
+
outcomes: dict[str, dict] = {}
|
|
225
|
+
for ev in data.get("evidence", []):
|
|
226
|
+
otype = _v1_outcome_type(ev.get("outcome_type") or "learning")
|
|
227
|
+
oid = f"OUT-{otype}-{ev['evidence_id']}"
|
|
228
|
+
outcomes[oid] = {
|
|
229
|
+
"outcome_id": oid,
|
|
230
|
+
"name": ev.get("outcome_type") or otype,
|
|
231
|
+
"outcome_type": otype,
|
|
232
|
+
"extensions": {"v1_legacy": True},
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
# ---- claim identity mapping (V1 claims carry claim_id + evidence_ids) --
|
|
236
|
+
claim_by_evidence: dict[str, str] = {}
|
|
237
|
+
v1_claims = []
|
|
238
|
+
for c in data.get("claims", []):
|
|
239
|
+
cid = c.get("claim_id") or new_local_id("CLM", set())
|
|
240
|
+
v1_claims.append(c)
|
|
241
|
+
for eid in c.get("evidence_ids", []) or []:
|
|
242
|
+
claim_by_evidence[eid] = cid
|
|
243
|
+
report["preserved_ids"].append(cid)
|
|
244
|
+
|
|
245
|
+
# ---- findings + links -------------------------------------------------
|
|
246
|
+
findings: list[dict] = []
|
|
247
|
+
links: list[dict] = []
|
|
248
|
+
used_local = set()
|
|
249
|
+
for ev in data.get("evidence", []):
|
|
250
|
+
fnd_id = f"FND-{ev['evidence_id']}"
|
|
251
|
+
used_local.add(fnd_id)
|
|
252
|
+
study_id = study_by_v1.get(ev["evidence_id"]) or ev.get("study_id")
|
|
253
|
+
if study_id not in study_ids:
|
|
254
|
+
# a V1 study_id with no study row (should not happen) -> placeholder
|
|
255
|
+
sid = new_local_id("STU", used_ids)
|
|
256
|
+
studies.append({
|
|
257
|
+
"study_id": sid, "source_ids": _study_source_refs(ev),
|
|
258
|
+
"study_design": "unknown", "population": "unknown",
|
|
259
|
+
"sample_ids": [], "sample_size": None, "intervention": None,
|
|
260
|
+
"comparison": None, "independence_key": f"v1-missing:{ev['evidence_id']}",
|
|
261
|
+
"identity_status": "unresolved",
|
|
262
|
+
"extensions": {"v1_legacy": True},
|
|
263
|
+
})
|
|
264
|
+
study_ids.add(sid)
|
|
265
|
+
study_id = sid
|
|
266
|
+
report["unresolved_studies"].append(sid)
|
|
267
|
+
oid = f"OUT-{_v1_outcome_type(ev.get('outcome_type') or 'learning')}-{ev['evidence_id']}"
|
|
268
|
+
findings.append({
|
|
269
|
+
"finding_id": fnd_id,
|
|
270
|
+
"study_id": study_id,
|
|
271
|
+
"finding_type": "quantitative_effect",
|
|
272
|
+
"outcome_id": oid,
|
|
273
|
+
"measure": ev.get("outcome_type") or "outcome",
|
|
274
|
+
"timepoint": None,
|
|
275
|
+
"effect_direction": _map_effect_direction(ev.get("effect_direction")),
|
|
276
|
+
"effect_estimate": None,
|
|
277
|
+
"raw_result_text": ev.get("claim") or "unavailable",
|
|
278
|
+
"source_locator": ev.get("source_location") or "unavailable",
|
|
279
|
+
"extensions": {"v1_legacy": True},
|
|
280
|
+
})
|
|
281
|
+
claim_id = claim_by_evidence.get(ev["evidence_id"], f"CLM-{ev['evidence_id']}")
|
|
282
|
+
link_id = f"LNK-{ev['evidence_id']}"
|
|
283
|
+
|
|
284
|
+
links.append({
|
|
285
|
+
|
|
286
|
+
"evidence_link_id": link_id,
|
|
287
|
+
"finding_id": fnd_id,
|
|
288
|
+
"claim_id": claim_id,
|
|
289
|
+
"relation_to_claim": _map_relation(
|
|
290
|
+
ev.get("relation_to_claim") or ev.get("direction")),
|
|
291
|
+
"decision_implication": _map_decision_implication(ev),
|
|
292
|
+
"directness": 1,
|
|
293
|
+
"applicability": {"scope_match": "direct"},
|
|
294
|
+
"reasoning_note": "migrated from V1 Evidence Object",
|
|
295
|
+
"created_in_revision": 1,
|
|
296
|
+
"extensions": {"v1_legacy": True},
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
# ---- claims ----------------------------------------------------------
|
|
300
|
+
# Emit claims from the V1 claim records (preserving their text and
|
|
301
|
+
# evidence bindings); evidence-derived rows only for unmapped evidence.
|
|
302
|
+
claims: list[dict] = []
|
|
303
|
+
emitted_claim_ids: set[str] = set()
|
|
304
|
+
|
|
305
|
+
for c in data.get("claims", []):
|
|
306
|
+
cid = c.get("claim_id") or new_local_id("CLM", set())
|
|
307
|
+
if cid in emitted_claim_ids:
|
|
308
|
+
continue
|
|
309
|
+
emitted_claim_ids.add(cid)
|
|
310
|
+
eids = c.get("evidence_ids") or []
|
|
311
|
+
outcome_ids = []
|
|
312
|
+
for eid in eids:
|
|
313
|
+
ev = next((e for e in data.get("evidence", [])
|
|
314
|
+
if e.get("evidence_id") == eid), None)
|
|
315
|
+
if ev is not None:
|
|
316
|
+
outcome_ids.append(
|
|
317
|
+
f"OUT-{_v1_outcome_type(ev.get('outcome_type') or 'learning')}-{eid}")
|
|
318
|
+
claims.append({
|
|
319
|
+
"claim_id": cid,
|
|
320
|
+
"text": c.get("claim") or "unavailable",
|
|
321
|
+
"claim_type": "effectiveness",
|
|
322
|
+
"primary_outcome_ids": outcome_ids,
|
|
323
|
+
"scope": "legacy V1 migration",
|
|
324
|
+
"created_in_revision": 1,
|
|
325
|
+
"status": "active",
|
|
326
|
+
"extensions": {"v1_legacy": True},
|
|
327
|
+
})
|
|
328
|
+
for ev in data.get("evidence", []):
|
|
329
|
+
cid = claim_by_evidence.get(ev["evidence_id"])
|
|
330
|
+
if cid is None:
|
|
331
|
+
cid = f"CLM-{ev['evidence_id']}"
|
|
332
|
+
report["generated_ids"].append(cid)
|
|
333
|
+
warnings.append(
|
|
334
|
+
f"evidence {ev['evidence_id']} has no V1 claim; generated Claim {cid}"
|
|
335
|
+
)
|
|
336
|
+
claims.append({
|
|
337
|
+
"claim_id": cid,
|
|
338
|
+
"text": ev.get("claim") or "unavailable",
|
|
339
|
+
"claim_type": "effectiveness",
|
|
340
|
+
"primary_outcome_ids": [
|
|
341
|
+
f"OUT-{_v1_outcome_type(ev.get('outcome_type') or 'learning')}-{ev['evidence_id']}"],
|
|
342
|
+
"scope": "legacy V1 migration",
|
|
343
|
+
"created_in_revision": 1,
|
|
344
|
+
"status": "active",
|
|
345
|
+
"extensions": {"v1_legacy": True},
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
# ---- audits ----------------------------------------------------------
|
|
349
|
+
audits: list[dict] = []
|
|
350
|
+
for s in studies:
|
|
351
|
+
audits.append({
|
|
352
|
+
"audit_id": new_local_id("AUD", set()),
|
|
353
|
+
"study_id": s["study_id"],
|
|
354
|
+
"policy_version": METHODOLOGY_POLICY_VERSION,
|
|
355
|
+
"design_quality": 1,
|
|
356
|
+
"sample_quality": 1,
|
|
357
|
+
"measurement_validity": 1,
|
|
358
|
+
"temporal_strength": 1,
|
|
359
|
+
"bias_checks": [],
|
|
360
|
+
"confounders": [],
|
|
361
|
+
"limitations": ["V1 legacy migration; audit quality not re-derived"],
|
|
362
|
+
"overall_status": "concern",
|
|
363
|
+
"audited_at": _now_iso(),
|
|
364
|
+
"extensions": {"v1_legacy": True},
|
|
365
|
+
})
|
|
366
|
+
report["downgrades"].append({
|
|
367
|
+
"study_id": s["study_id"],
|
|
368
|
+
"from": "V1 quality_score",
|
|
369
|
+
"to": "concern (legacy, not re-audited)",
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
# ---- commit ----------------------------------------------------------
|
|
373
|
+
mutation = GraphMutation(
|
|
374
|
+
upserts={
|
|
375
|
+
"sources": sources, "studies": studies,
|
|
376
|
+
"outcomes": list(outcomes.values()),
|
|
377
|
+
"findings": findings, "claims": claims,
|
|
378
|
+
"evidence_links": links, "audits": audits,
|
|
379
|
+
},
|
|
380
|
+
retire_ids={},
|
|
381
|
+
)
|
|
382
|
+
store = GraphStore.create(ws)
|
|
383
|
+
rev = store.commit(run_id=run["run_id"], reason="V1 pack migration",
|
|
384
|
+
mutation=mutation)
|
|
385
|
+
finish_run(ws, run["run_id"], status="completed", graph_revision_after=rev.revision)
|
|
386
|
+
|
|
387
|
+
# ---- report ----------------------------------------------------------
|
|
388
|
+
report_path = ws.path / "migration_report.json"
|
|
389
|
+
report_path.write_text(
|
|
390
|
+
json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
391
|
+
|
|
392
|
+
return MigrationResult(
|
|
393
|
+
project_id=ws.project_id,
|
|
394
|
+
graph_revision=rev.revision,
|
|
395
|
+
migration_report_path=report_path,
|
|
396
|
+
warnings=tuple(warnings),
|
|
397
|
+
)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Deterministic research-mode recommendation from a structured ResearchIntent.
|
|
2
|
+
|
|
3
|
+
The engine never classifies natural language; the Skill/model produces a
|
|
4
|
+
schema-valid ResearchIntent and the router maps its flags to a mode
|
|
5
|
+
deterministically. `decision_target` controls the requested deliverable;
|
|
6
|
+
`research_mode` controls lifecycle depth.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class ModeRecommendation:
|
|
16
|
+
mode: str
|
|
17
|
+
reasons: tuple[str, ...]
|
|
18
|
+
requires_grounding_review: bool
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def recommend_mode(intent: dict, *, explicit_mode: str | None = None,
|
|
22
|
+
project_has_grounding: bool = False) -> ModeRecommendation:
|
|
23
|
+
"""Recommend evidence_review vs full_research_cycle from intent flags.
|
|
24
|
+
|
|
25
|
+
- explicit_mode wins, except when the intent already asks for data
|
|
26
|
+
analysis: the data intent is preserved in a warning reason instead of
|
|
27
|
+
being silently discarded.
|
|
28
|
+
- A Full Research Cycle requires evidence grounding: when the project has
|
|
29
|
+
no grounding yet, `requires_grounding_review` is True so the executor
|
|
30
|
+
runs at least a minimum Evidence Review before designing a study.
|
|
31
|
+
"""
|
|
32
|
+
reasons: list[str] = []
|
|
33
|
+
wants_design = bool(intent.get("wants_study_design"))
|
|
34
|
+
has_data = bool(intent.get("has_user_data"))
|
|
35
|
+
wants_analysis = bool(intent.get("wants_data_analysis"))
|
|
36
|
+
wants_update = bool(intent.get("wants_decision_update"))
|
|
37
|
+
wants_existing = bool(intent.get("wants_existing_evidence"))
|
|
38
|
+
|
|
39
|
+
full_cycle_flags = wants_design or has_data or wants_analysis or wants_update
|
|
40
|
+
|
|
41
|
+
if explicit_mode is not None:
|
|
42
|
+
if explicit_mode == "evidence_review" and wants_analysis:
|
|
43
|
+
reasons.append(
|
|
44
|
+
"explicit evidence_review conflicts with wants_data_analysis; "
|
|
45
|
+
"data-analysis intent preserved — run it in a later full cycle"
|
|
46
|
+
)
|
|
47
|
+
mode = explicit_mode
|
|
48
|
+
else:
|
|
49
|
+
if full_cycle_flags:
|
|
50
|
+
mode = "full_research_cycle"
|
|
51
|
+
if wants_design:
|
|
52
|
+
reasons.append("study design requested")
|
|
53
|
+
if has_data or wants_analysis:
|
|
54
|
+
reasons.append("user data analysis requested")
|
|
55
|
+
if wants_update:
|
|
56
|
+
reasons.append("decision update requested")
|
|
57
|
+
else:
|
|
58
|
+
mode = "evidence_review"
|
|
59
|
+
reasons.append("existing evidence requested")
|
|
60
|
+
|
|
61
|
+
if mode == "full_research_cycle":
|
|
62
|
+
requires_grounding = not project_has_grounding
|
|
63
|
+
if requires_grounding:
|
|
64
|
+
reasons.append(
|
|
65
|
+
"no project evidence grounding yet; minimum Evidence Review "
|
|
66
|
+
"required before study design (no new study design without "
|
|
67
|
+
"evidence grounding)")
|
|
68
|
+
else:
|
|
69
|
+
requires_grounding = False
|
|
70
|
+
|
|
71
|
+
return ModeRecommendation(mode=mode, reasons=tuple(reasons),
|
|
72
|
+
requires_grounding_review=requires_grounding)
|
package/engine/paths.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Workspace-root resolution.
|
|
2
|
+
|
|
3
|
+
EDUEVIDENCE_HOME (default `~/.eduevidence`) is the root that owns the Shared
|
|
4
|
+
Research Library and all Projects. An explicit path always wins.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def resolve_home(explicit: Path | None = None) -> Path:
|
|
12
|
+
if explicit is not None:
|
|
13
|
+
return Path(explicit).expanduser().resolve()
|
|
14
|
+
raw = os.environ.get("EDUEVIDENCE_HOME", "~/.eduevidence")
|
|
15
|
+
return Path(raw).expanduser().resolve()
|