eduevidence 5.2.0 → 6.0.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/README.md +54 -42
- package/README.zh-CN.md +51 -28
- package/SKILL.md +390 -133
- package/agents/openai.yaml +4 -0
- package/assets/readme/controlled-execution.svg +34 -0
- package/assets/readme/logo.png +0 -0
- package/assets/readme/research-workflow.svg +56 -0
- package/assets/readme/studio-graph.png +0 -0
- package/assets/readme/studio-overview.png +0 -0
- package/assets/readme/studio-reports.png +0 -0
- package/autoevolve/config.yaml +17 -0
- package/autoevolve/program.md +25 -0
- package/autoevolve/protected.manifest.yaml +34 -0
- package/benchmarks/adversarial/cases.jsonl +7 -0
- package/benchmarks/evidence-library.json +5268 -0
- package/benchmarks/partitions.json +8 -0
- package/docs/architecture.md +220 -0
- package/docs/autoresearch-evolution-plan.md +2903 -0
- package/docs/autoresearch-implementation-status.md +101 -0
- package/docs/demo-storyboard.md +20 -0
- package/docs/demo-workplace-ai.md +92 -0
- package/docs/demo.md +32 -0
- package/docs/install-guide.md +150 -0
- package/docs/orchestration-role-model.md +1254 -0
- package/docs/release-closeout/README.md +17 -0
- package/docs/release-closeout/frontend-acceptance.md +23 -0
- package/docs/release-closeout/issues.md +19 -0
- package/docs/release-closeout/verification.md +28 -0
- package/docs/release-contract.md +108 -0
- package/docs/research-studio-guide.zh-CN.md +166 -0
- package/eduevidence_cli.py +17 -11
- package/engine/_resources.py +13 -0
- package/engine/autoevolve/__init__.py +3 -0
- package/engine/autoevolve/agent_view.py +167 -0
- package/engine/autoevolve/core.py +357 -0
- package/engine/autoevolve/events.py +11 -0
- package/engine/autoevolve/git_workspace.py +77 -0
- package/engine/autoevolve/projection.py +23 -0
- package/engine/autoevolve/runner.py +413 -0
- package/engine/autoevolve/trust.py +146 -0
- package/engine/autoresearch/__init__.py +6 -0
- package/engine/autoresearch/commit.py +132 -0
- package/engine/autoresearch/contracts.py +126 -0
- package/engine/autoresearch/controller.py +207 -0
- package/engine/autoresearch/events.py +12 -0
- package/engine/autoresearch/gap_priority.py +168 -0
- package/engine/autoresearch/projection.py +30 -0
- package/engine/autoresearch/research_memory.py +59 -0
- package/engine/autoresearch/saturation.py +91 -0
- package/engine/briefs.py +2 -1
- package/engine/capabilities.py +1 -0
- package/engine/contracts.py +3 -1
- package/engine/evidencecore.py +7 -5
- package/engine/gaps.py +90 -51
- package/engine/judge_pack.py +65 -0
- package/engine/library_builtin.py +3 -1
- package/engine/living.py +2 -1
- package/engine/meta_synthesis.py +3 -1
- package/engine/orchestration.py +460 -0
- package/engine/pilot.py +2 -1
- package/engine/project.py +2 -2
- package/engine/research_service.py +113 -0
- package/engine/studio_read_model.py +400 -0
- package/engine/tribunal.py +1 -2
- package/engine/update.py +1 -0
- package/engine/versions.py +1 -1
- package/engine/worker_result.py +109 -0
- package/engine/workflows.py +70 -0
- package/examples/ai-coding-assistant-evidence/EduEvidence_Report.html +1720 -0
- package/examples/ai-coding-assistant-evidence/artifact_manifest.json +15 -0
- package/examples/ai-coding-assistant-evidence/citation_check.json +79 -0
- package/examples/ai-coding-assistant-evidence/claims.jsonl +12 -0
- package/examples/ai-coding-assistant-evidence/evaluation.json +35 -0
- package/examples/ai-coding-assistant-evidence/evidence.jsonl +12 -0
- package/examples/ai-coding-assistant-evidence/frame.json +48 -0
- package/examples/ai-coding-assistant-evidence/intervention.json +51 -0
- package/examples/ai-coding-assistant-evidence/methodology.json +36 -0
- package/examples/ai-coding-assistant-evidence/raw_verdict.json +86 -0
- package/examples/ai-coding-assistant-evidence/report_spec.json +219 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_academic.html +2614 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_claude.html +2614 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_datalab-dark.html +2614 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_datalab.html +2614 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_presentation.html +2614 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/report_academic.html +1720 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/report_claude.html +1720 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/report_datalab-dark.html +1720 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/report_datalab.html +1720 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/report_presentation.html +1720 -0
- package/examples/ai-coding-assistant-evidence/result.json +1453 -0
- package/examples/ai-coding-assistant-evidence/result.zh.json +1453 -0
- package/examples/ai-coding-assistant-evidence/sources.jsonl +8 -0
- package/examples/ai-coding-assistant-evidence/verdict.json +103 -0
- package/examples/workplace-ai-assistant/claims.jsonl +4 -0
- package/examples/workplace-ai-assistant/evaluation.json +19 -0
- package/examples/workplace-ai-assistant/evidence.jsonl +4 -0
- package/examples/workplace-ai-assistant/evidence_graph.json +444 -0
- package/examples/workplace-ai-assistant/frame.json +41 -0
- package/examples/workplace-ai-assistant/intervention.json +27 -0
- package/examples/workplace-ai-assistant/legacy-link-check.json +16 -0
- package/examples/workplace-ai-assistant/methodology.json +60 -0
- package/examples/workplace-ai-assistant/report_spec.json +55 -0
- package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_academic.html +2484 -0
- package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_claude.html +2484 -0
- package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_datalab-dark.html +2484 -0
- package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_datalab.html +2484 -0
- package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_presentation.html +2484 -0
- package/examples/workplace-ai-assistant/result.json +553 -0
- package/examples/workplace-ai-assistant/result.zh.json +553 -0
- package/examples/workplace-ai-assistant/search_log.json +19 -0
- package/examples/workplace-ai-assistant/sources.jsonl +3 -0
- package/examples/workplace-ai-assistant/validation_result.json +9 -0
- package/examples/workplace-ai-assistant/verdict.json +52 -0
- package/install.sh +7 -7
- package/integrations/orchestration_dispatch.py +146 -0
- package/package.json +37 -3
- package/pyproject.toml +11 -20
- package/references/autoresearch.md +30 -0
- package/references/evaluation-policy.md +24 -0
- package/references/orchestration.md +22 -0
- package/references/scientific-invariants.md +19 -0
- package/retrieval/audit.py +154 -0
- package/schemas/intervention.schema.json +106 -21
- package/schemas/report-result.schema.json +9 -1
- package/schemas/v2/project.schema.json +2 -2
- package/schemas/v2/run.schema.json +1 -1
- package/schemas/vNext/autoevolve-session.schema.json +1 -0
- package/schemas/vNext/eval-snapshot.schema.json +1 -0
- package/schemas/vNext/execution-plan.schema.json +1 -0
- package/schemas/vNext/gap-priority.schema.json +1 -0
- package/schemas/vNext/negative-search-record.schema.json +1 -0
- package/schemas/vNext/research-iteration.schema.json +1 -0
- package/schemas/vNext/research-strategy.schema.json +1 -0
- package/schemas/vNext/skill-experiment.schema.json +1 -0
- package/schemas/vNext/task-spec.schema.json +1 -0
- package/schemas/vNext/worker-result.schema.json +1 -0
- package/scripts/benchmark_judge.py +2 -2
- package/scripts/benchmark_v3.py +26 -43
- package/scripts/build_esl_artifacts.py +2 -2
- package/scripts/build_evidence_library.py +2 -2
- package/scripts/build_gh_pages.py +98 -0
- package/scripts/build_readme_diagrams.py +72 -0
- package/scripts/build_report_variants.py +85 -0
- package/scripts/check_autoresearch_invariants.py +95 -0
- package/scripts/daily_evolve.py +30 -0
- package/scripts/dashboard_server.py +130 -101
- package/scripts/did_regression.py +5 -30
- package/scripts/enrich_projects_human_and_lieflat.py +1 -1
- package/scripts/generate_metrics.py +4 -3
- package/scripts/generate_new_projects.py +1 -1
- package/scripts/orchestrator.py +172 -18
- package/scripts/rebake_all_5themes.py +1 -2
- package/scripts/research_auto_cli.py +475 -0
- package/scripts/run_workspace.py +17 -7
- package/scripts/search_provenance.py +64 -0
- package/scripts/serve_web.py +9 -10
- package/scripts/skill_lint.py +1 -1
- package/scripts/skill_payload.py +78 -0
- package/scripts/validate_schema.py +15 -1
- package/scripts/vnext_cli.py +133 -0
- package/setup.py +12 -0
- package/skill/roles/registry.yaml +45 -0
- package/skill/sub-skills/report-generation/SKILL.md +12 -6
- package/skill/task-briefs/applicability.md +3 -0
- package/skill/task-briefs/projection.md +3 -0
- package/skill/workflows/decision-and-pilot.md +10 -0
- package/skill/workflows/evaluate-and-update.md +10 -0
- package/skill/workflows/evidence-review.md +13 -0
- package/visualization/eduevidence-report/assets/base.css +2 -2
- package/visualization/eduevidence-report/assets/reader.css +752 -0
- package/visualization/eduevidence-report/assets/reader.js +132 -0
- package/visualization/eduevidence-report/references/chart-selection-catalog.md +109 -0
- package/visualization/eduevidence-report/references/lieflat-composition.md +3 -1
- package/visualization/eduevidence-report/scripts/build_report.py +58 -65
- package/visualization/eduevidence-report/scripts/lieflat_engine.py +24 -100
- package/visualization/eduevidence-report/themes/academic.css +1 -1
- package/visualization/eduevidence-report/themes/claude.css +1 -1
- package/visualization/eduevidence-report/themes/datalab-dark.css +2 -2
- package/visualization/eduevidence-report/themes/datalab.css +2 -2
- package/visualization/eduevidence-report/themes/presentation.css +2 -2
- package/web/README.md +18 -0
- package/web/index.html +53 -0
- package/web/studio/THIRD_PARTY_LICENSES.txt +146 -0
- package/web/studio/assets/index-CzXocaGv.css +1 -0
- package/web/studio/assets/index-pa7jD7n4.js +230 -0
- package/web/studio/config.json +1 -0
- package/web/studio/index.html +14 -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/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/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/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/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
|
@@ -34,8 +34,8 @@ THEME_PALETTES = {
|
|
|
34
34
|
"presentation": {"bg": "#140A08", "card_bg": "#1C0D0A", "text": "#FBBF24", "subtext": "#D97706", "muted": "#9A3412", "grid": "#2D120B", "border": "#4A2218", "primary": "#F59E0B", "secondary": "#F24D29", "accent": "#10B981", "ribbon": "#2A140E"},
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
STAGGER_DOT = 12
|
|
38
|
-
STAGGER_BAR = 100
|
|
37
|
+
STAGGER_DOT = 12
|
|
38
|
+
STAGGER_BAR = 100
|
|
39
39
|
MIN_FONT = 6.5
|
|
40
40
|
|
|
41
41
|
|
|
@@ -77,9 +77,6 @@ def _fmt(v: float, digits: int = 2) -> str:
|
|
|
77
77
|
|
|
78
78
|
|
|
79
79
|
class Audit:
|
|
80
|
-
"""Collects every displayed number as (origin, value) for the
|
|
81
|
-
lieflat_data_bound integrity gate."""
|
|
82
|
-
|
|
83
80
|
def __init__(self, out: Optional[list] = None):
|
|
84
81
|
self.out: list[Tuple[str, Any]] = [] if out is None else out
|
|
85
82
|
|
|
@@ -96,10 +93,6 @@ def _svg_open(p: Dict[str, str], w: int, h: int, aria: str) -> List[str]:
|
|
|
96
93
|
f'style="background:{p["card_bg"]};">']
|
|
97
94
|
|
|
98
95
|
|
|
99
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
100
|
-
# meta.forest — forest plot (Hedges' g + 95% CI)
|
|
101
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
102
|
-
|
|
103
96
|
def render_forest_plot(bundle: dict, theme: str, meta: dict, audit: Optional[list] = None) -> str:
|
|
104
97
|
p = get_theme(theme)
|
|
105
98
|
A = Audit(audit)
|
|
@@ -122,7 +115,6 @@ def render_forest_plot(bundle: dict, theme: str, meta: dict, audit: Optional[lis
|
|
|
122
115
|
out.append(f'<text x="{cx:.0f}" y="{head_y}" text-anchor="middle" font-size="10" font-weight="700" fill="{p["subtext"]}" {lf_fade(60)}>Hedges' g [95% CI]</text>')
|
|
123
116
|
out.append(f'<text x="{w - 24}" y="{head_y}" text-anchor="end" font-size="10" font-weight="700" fill="{p["subtext"]}" {lf_fade(90)}>Effect [95% CI]</text>')
|
|
124
117
|
out.append(f'<line x1="20" y1="{head_y + 8}" x2="{w - 20}" y2="{head_y + 8}" stroke="{p["border"]}" stroke-width="1" {lf_draw(0)}/>')
|
|
125
|
-
# zero line
|
|
126
118
|
out.append(f'<line x1="{cx:.1f}" y1="{head_y + 8}" x2="{cx:.1f}" y2="{h - 56}" stroke="{p["muted"]}" stroke-dasharray="3,3" stroke-width="1" {lf_fade(120)}/>')
|
|
127
119
|
row_y = head_y + 26
|
|
128
120
|
for i, s in enumerate(studies):
|
|
@@ -145,11 +137,11 @@ def render_forest_plot(bundle: dict, theme: str, meta: dict, audit: Optional[lis
|
|
|
145
137
|
out.append(f'<line x1="{xl:.1f}" y1="{row_y}" x2="{xu:.1f}" y2="{row_y}" stroke="{color}" stroke-width="1.5" {lf_draw(i * STAGGER_BAR + 120)}/>')
|
|
146
138
|
out.append(f'<line x1="{xl:.1f}" y1="{row_y - 4}" x2="{xl:.1f}" y2="{row_y + 4}" stroke="{color}" stroke-width="1.5" {lf_fade(i * STAGGER_BAR + 140)}/>')
|
|
147
139
|
out.append(f'<line x1="{xu:.1f}" y1="{row_y - 4}" x2="{xu:.1f}" y2="{row_y + 4}" stroke="{color}" stroke-width="1.5" {lf_fade(i * STAGGER_BAR + 140)}/>')
|
|
148
|
-
|
|
140
|
+
n_suffix = (", N = %.0f" % s["n"]) if s.get("n") else ""
|
|
141
|
+
out.append(f'<rect x="{x_pt - 3.5:.1f}" y="{row_y - 3.5:.1f}" width="7" height="7" fill="{color}" {lf_pop(i * STAGGER_BAR + 150)}><title>{esc(str(s["label"]))} — g = {g:+.2f}{n_suffix}</title></rect>')
|
|
149
142
|
ci_txt = f'{g:+.2f} [{ci_l:+.2f}, {ci_u:+.2f}]' if ci_l is not None and ci_u is not None else f"{g:+.2f}"
|
|
150
143
|
out.append(f'<text x="{w - 24}" y="{row_y + 4}" text-anchor="end" font-size="10" font-weight="800" fill="{color}" {lf_fade(i * STAGGER_BAR + 200)}>{ci_txt}</text>')
|
|
151
144
|
row_y += 27
|
|
152
|
-
# axis
|
|
153
145
|
axis_y = h - 48
|
|
154
146
|
out.append(f'<line x1="{cx - 0.34 * w:.0f}" y1="{axis_y}" x2="{cx + 0.34 * w:.0f}" y2="{axis_y}" stroke="{p["text"]}" stroke-width="1" {lf_draw(200)}/>')
|
|
155
147
|
for tick in (-1.0, -0.5, 0.0, 0.5, 1.0):
|
|
@@ -165,10 +157,6 @@ def render_forest_plot(bundle: dict, theme: str, meta: dict, audit: Optional[lis
|
|
|
165
157
|
return "\n".join(out)
|
|
166
158
|
|
|
167
159
|
|
|
168
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
169
|
-
# evidence.ranked_effects — L2 Dot Cascade
|
|
170
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
171
|
-
|
|
172
160
|
def render_dot_cascade(bundle: dict, theme: str, meta: dict, audit: Optional[list] = None) -> str:
|
|
173
161
|
p = get_theme(theme)
|
|
174
162
|
A = Audit(audit)
|
|
@@ -204,10 +192,6 @@ def render_dot_cascade(bundle: dict, theme: str, meta: dict, audit: Optional[lis
|
|
|
204
192
|
return "\n".join(out)
|
|
205
193
|
|
|
206
194
|
|
|
207
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
208
|
-
# evidence.year_x_dimension — L9 Bubble Almanac
|
|
209
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
210
|
-
|
|
211
195
|
def render_bubble_almanac(bundle: dict, theme: str, meta: dict, audit: Optional[list] = None) -> str:
|
|
212
196
|
p = get_theme(theme)
|
|
213
197
|
A = Audit(audit)
|
|
@@ -247,10 +231,6 @@ def render_bubble_almanac(bundle: dict, theme: str, meta: dict, audit: Optional[
|
|
|
247
231
|
return "\n".join(out)
|
|
248
232
|
|
|
249
233
|
|
|
250
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
251
|
-
# evidence.grouped_distribution — G15 Jitter Strip
|
|
252
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
253
|
-
|
|
254
234
|
def render_jitter_strip(bundle: dict, theme: str, meta: dict, audit: Optional[list] = None) -> str:
|
|
255
235
|
p = get_theme(theme)
|
|
256
236
|
A = Audit(audit)
|
|
@@ -280,10 +260,6 @@ def render_jitter_strip(bundle: dict, theme: str, meta: dict, audit: Optional[li
|
|
|
280
260
|
return "\n".join(out)
|
|
281
261
|
|
|
282
262
|
|
|
283
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
284
|
-
# evidence.multidim_top — L20 Parallel Coordinates
|
|
285
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
286
|
-
|
|
287
263
|
def render_parallel_coordinates(bundle: dict, theme: str, meta: dict, audit: Optional[list] = None) -> str:
|
|
288
264
|
p = get_theme(theme)
|
|
289
265
|
A = Audit(audit)
|
|
@@ -329,16 +305,13 @@ def render_parallel_coordinates(bundle: dict, theme: str, meta: dict, audit: Opt
|
|
|
329
305
|
if len(d_parts) < 2:
|
|
330
306
|
continue
|
|
331
307
|
title = f'{esc(str(r["label"]))} — ' + ", ".join(f'{ax["key"]}={r.get(ax["key"])}' for ax in axes)
|
|
332
|
-
|
|
308
|
+
points_text = " ".join(d_parts)
|
|
309
|
+
out.append(f'<polyline points="{points_text}" fill="none" stroke="{p["primary"]}" stroke-width="1" opacity="0.45" {lf_draw(r_i * STAGGER_BAR + 120)}><title>{title}</title></polyline>')
|
|
333
310
|
out.append(f'<circle cx="{d_parts[0].split()[0]}" cy="{d_parts[0].split()[1]}" r="2.4" fill="{p["primary"]}" {lf_pop(r_i * STAGGER_BAR + 140)}/>')
|
|
334
311
|
out.append("</svg>")
|
|
335
312
|
return "\n".join(out)
|
|
336
313
|
|
|
337
314
|
|
|
338
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
339
|
-
# evidence.study_type_composition / wwc — L14 Hundred Field
|
|
340
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
341
|
-
|
|
342
315
|
def render_hundred_field(bundle: dict, theme: str, meta: dict, audit: Optional[list] = None) -> str:
|
|
343
316
|
p = get_theme(theme)
|
|
344
317
|
A = Audit(audit)
|
|
@@ -363,10 +336,6 @@ def render_hundred_field(bundle: dict, theme: str, meta: dict, audit: Optional[l
|
|
|
363
336
|
return "\n".join(out)
|
|
364
337
|
|
|
365
338
|
|
|
366
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
367
|
-
# evidence.study_type_composition / wwc — F4 Tick Donut
|
|
368
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
369
|
-
|
|
370
339
|
def render_tick_donut(bundle: dict, theme: str, meta: dict, audit: Optional[list] = None) -> str:
|
|
371
340
|
p = get_theme(theme)
|
|
372
341
|
A = Audit(audit)
|
|
@@ -376,7 +345,6 @@ def render_tick_donut(bundle: dict, theme: str, meta: dict, audit: Optional[list
|
|
|
376
345
|
out = _svg_open(p, w, h, meta.get("title", "tick donut"))
|
|
377
346
|
cx, cy, r0, r1 = 200, 158, 88, 104
|
|
378
347
|
colors = [p["primary"], p["secondary"], p["accent"], p["muted"]]
|
|
379
|
-
# largest-remainder share of 100 ticks
|
|
380
348
|
ticks_per = []
|
|
381
349
|
assigned = 0
|
|
382
350
|
for c_i, c in enumerate(cats):
|
|
@@ -385,13 +353,13 @@ def render_tick_donut(bundle: dict, theme: str, meta: dict, audit: Optional[list
|
|
|
385
353
|
n = int(round(share))
|
|
386
354
|
ticks_per.append((c_i, n))
|
|
387
355
|
assigned += n
|
|
388
|
-
while assigned > 100:
|
|
356
|
+
while assigned > 100:
|
|
389
357
|
c_i, n = max(ticks_per, key=lambda t: t[1])
|
|
390
358
|
ticks_per[ticks_per.index((c_i, n))] = (c_i, n - 1)
|
|
391
359
|
assigned -= 1
|
|
392
|
-
while assigned < 100:
|
|
393
|
-
c_i,
|
|
394
|
-
ticks_per[ticks_per.index((c_i,
|
|
360
|
+
while assigned < 100:
|
|
361
|
+
c_i, n = max(ticks_per, key=lambda t: t[1])
|
|
362
|
+
ticks_per[ticks_per.index((c_i, n))] = (c_i, n + 1)
|
|
395
363
|
assigned += 1
|
|
396
364
|
tick_i = 0
|
|
397
365
|
for c_i, n in ticks_per:
|
|
@@ -408,10 +376,6 @@ def render_tick_donut(bundle: dict, theme: str, meta: dict, audit: Optional[list
|
|
|
408
376
|
return "\n".join(out)
|
|
409
377
|
|
|
410
378
|
|
|
411
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
412
|
-
# outcomes.direction_counts — F5 Tick Rows
|
|
413
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
414
|
-
|
|
415
379
|
def _dir_colors(p: Dict[str, str]) -> Dict[str, str]:
|
|
416
380
|
return {"positive": p["secondary"], "negative": p["primary"], "null": p["muted"]}
|
|
417
381
|
|
|
@@ -448,10 +412,6 @@ def render_tick_rows(bundle: dict, theme: str, meta: dict, audit: Optional[list]
|
|
|
448
412
|
return "\n".join(out)
|
|
449
413
|
|
|
450
414
|
|
|
451
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
452
|
-
# outcomes.direction_counts — F1 Rung Bars
|
|
453
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
454
|
-
|
|
455
415
|
def render_rung_bars(bundle: dict, theme: str, meta: dict, audit: Optional[list] = None) -> str:
|
|
456
416
|
p = get_theme(theme)
|
|
457
417
|
A = Audit(audit)
|
|
@@ -461,8 +421,6 @@ def render_rung_bars(bundle: dict, theme: str, meta: dict, audit: Optional[list]
|
|
|
461
421
|
colors = _dir_colors(p)
|
|
462
422
|
x_start, x_end, y_base = 60, 516, 240
|
|
463
423
|
bar_w = min(52, (x_end - x_start) / max(1, len(rows)) * 0.62)
|
|
464
|
-
total_max = max((r["positive"] + r["negative"] + r["null"]) for r in rows) or 1
|
|
465
|
-
rung_h = 5.5
|
|
466
424
|
for r_i, r in enumerate(rows):
|
|
467
425
|
cx = x_start + (r_i + 0.5) * (x_end - x_start) / len(rows)
|
|
468
426
|
label = r.get("label_" + meta.get("lang", "en"), r.get("label_en", r["label"]))
|
|
@@ -474,8 +432,8 @@ def render_rung_bars(bundle: dict, theme: str, meta: dict, audit: Optional[list]
|
|
|
474
432
|
for _ in range(count):
|
|
475
433
|
if y_base - y > 118:
|
|
476
434
|
break
|
|
477
|
-
y -=
|
|
478
|
-
out.append(f'<rect x="{cx - bar_w / 2:.1f}" y="{y:.1f}" width="{bar_w:.1f}" height="
|
|
435
|
+
y -= 7.7
|
|
436
|
+
out.append(f'<rect x="{cx - bar_w / 2:.1f}" y="{y:.1f}" width="{bar_w:.1f}" height="5.5" rx="2.6" fill="{colors[key]}" {lf_fade(r_i * STAGGER_BAR + shown * STAGGER_DOT + 60)}><title>{esc(str(label))} — {key} evidence</title></rect>')
|
|
479
437
|
shown += 1
|
|
480
438
|
if shown < count:
|
|
481
439
|
out.append(f'<text x="{cx:.1f}" y="{y - 4:.1f}" text-anchor="middle" font-size="{MIN_FONT + 1.5}" font-weight="800" fill="{p["subtext"]}" {lf_fade(r_i * STAGGER_BAR + 500)}>+{count - shown}</text>')
|
|
@@ -486,10 +444,6 @@ def render_rung_bars(bundle: dict, theme: str, meta: dict, audit: Optional[list]
|
|
|
486
444
|
return "\n".join(out)
|
|
487
445
|
|
|
488
446
|
|
|
489
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
490
|
-
# outcomes.paired_counts — F6 Paired Rungs
|
|
491
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
492
|
-
|
|
493
447
|
def render_paired_rungs(bundle: dict, theme: str, meta: dict, audit: Optional[list] = None) -> str:
|
|
494
448
|
p = get_theme(theme)
|
|
495
449
|
A = Audit(audit)
|
|
@@ -506,6 +460,7 @@ def render_paired_rungs(bundle: dict, theme: str, meta: dict, audit: Optional[li
|
|
|
506
460
|
for r_i, r in enumerate(rows):
|
|
507
461
|
cx = 60 + (r_i + 0.5) * group_w
|
|
508
462
|
label = r.get("label_" + lang, r.get("label_en", r["label"]))
|
|
463
|
+
last_y = y_base
|
|
509
464
|
for col, key in ((0, "positive"), (1, "negative")):
|
|
510
465
|
count = int(r.get(key) or 0)
|
|
511
466
|
A.log(f"rows[{r_i}].{key}", count)
|
|
@@ -514,23 +469,20 @@ def render_paired_rungs(bundle: dict, theme: str, meta: dict, audit: Optional[li
|
|
|
514
469
|
for _ in range(count):
|
|
515
470
|
if y_base - y > 130:
|
|
516
471
|
break
|
|
517
|
-
y -=
|
|
472
|
+
y -= 7.7
|
|
518
473
|
x = cx - col_w - 4 + col * (col_w + 8)
|
|
519
474
|
out.append(f'<rect x="{x:.1f}" y="{y:.1f}" width="{col_w}" height="5.5" rx="2.6" fill="{colors[key]}" {lf_fade(r_i * STAGGER_BAR + shown * STAGGER_DOT + 60)}><title>{esc(str(label))} — {key}</title></rect>')
|
|
520
475
|
shown += 1
|
|
476
|
+
last_y = min(last_y, y)
|
|
521
477
|
if shown < count:
|
|
522
478
|
out.append(f'<text x="{cx:.1f}" y="{y - 4:.1f}" text-anchor="middle" font-size="{MIN_FONT + 1.5}" font-weight="800" fill="{p["subtext"]}" {lf_fade(r_i * STAGGER_BAR + 500)}>+{count - shown}</text>')
|
|
523
479
|
out.append(f'<text x="{cx:.1f}" y="{y_base + 16}" text-anchor="middle" font-size="{MIN_FONT + 1.5}" font-weight="600" fill="{p["subtext"]}" {lf_fade(r_i * STAGGER_BAR + 100)}>{esc(str(label)[:10])}</text>')
|
|
524
|
-
out.append(f'<text x="{cx:.1f}" y="{
|
|
480
|
+
out.append(f'<text x="{cx:.1f}" y="{last_y - 8:.1f}" text-anchor="middle" font-size="8" font-weight="800" fill="{p["text"]}" {lf_fade(r_i * STAGGER_BAR + 520)}>{r["positive"]} / {r["negative"]}</text>')
|
|
525
481
|
out.append(f'<line x1="46" y1="{y_base}" x2="512" y2="{y_base}" stroke="{p["text"]}" stroke-width="1.2" {lf_draw(120)}/>')
|
|
526
482
|
out.append("</svg>")
|
|
527
483
|
return "\n".join(out)
|
|
528
484
|
|
|
529
485
|
|
|
530
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
531
|
-
# outcomes.bipolar_axes — L7 Brand Spectrum
|
|
532
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
533
|
-
|
|
534
486
|
def render_brand_spectrum(bundle: dict, theme: str, meta: dict, audit: Optional[list] = None) -> str:
|
|
535
487
|
p = get_theme(theme)
|
|
536
488
|
A = Audit(audit)
|
|
@@ -545,11 +497,12 @@ def render_brand_spectrum(bundle: dict, theme: str, meta: dict, audit: Optional[
|
|
|
545
497
|
out.append(f'<text x="{x0 - 12}" y="62" text-anchor="end" font-size="9" font-weight="700" fill="{p["subtext"]}" {lf_fade(40)}>{esc(left_t)}</text>')
|
|
546
498
|
out.append(f'<text x="{x1 + 12}" y="62" font-size="9" font-weight="700" fill="{p["subtext"]}" {lf_fade(80)}>{esc(right_t)}</text>')
|
|
547
499
|
px = lambda t: x0 + t * (x1 - x0)
|
|
548
|
-
ribbon = [f"{px(axes[0]['position']):.1f} {y0}"]
|
|
500
|
+
ribbon = [f"{px(axes[0]['position']):.1f} {y0}"] if axes else []
|
|
549
501
|
for i in range(1, len(axes)):
|
|
550
502
|
ribbon.append(f"C {px(axes[i-1]['position']):.1f} {y0 + (i-1)*gap + gap/2:.1f}, {px(axes[i]['position']):.1f} {y0 + i*gap - gap/2:.1f}, {px(axes[i]['position']):.1f} {y0 + i*gap}")
|
|
551
|
-
|
|
552
|
-
|
|
503
|
+
if ribbon:
|
|
504
|
+
ribbon_d = f"M {ribbon[0]} " + " ".join(ribbon[1:])
|
|
505
|
+
out.append(f'<path d="{ribbon_d}" fill="none" stroke="{p["ribbon"]}" stroke-width="26" stroke-linecap="round" stroke-linejoin="round" opacity="0.95" {lf_draw(60)}/>')
|
|
553
506
|
for i, ax in enumerate(axes):
|
|
554
507
|
y = y0 + i * gap
|
|
555
508
|
pos = float(ax["position"])
|
|
@@ -573,10 +526,6 @@ def render_brand_spectrum(bundle: dict, theme: str, meta: dict, audit: Optional[
|
|
|
573
526
|
return "\n".join(out)
|
|
574
527
|
|
|
575
528
|
|
|
576
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
577
|
-
# intervention.phase_weeks — L3 Barcode Lollipop
|
|
578
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
579
|
-
|
|
580
529
|
def render_barcode_lollipop(bundle: dict, theme: str, meta: dict, audit: Optional[list] = None) -> str:
|
|
581
530
|
p = get_theme(theme)
|
|
582
531
|
A = Audit(audit)
|
|
@@ -601,7 +550,7 @@ def render_barcode_lollipop(bundle: dict, theme: str, meta: dict, audit: Optiona
|
|
|
601
550
|
out.append(f'<line x1="{x:.1f}" y1="{y_base}" x2="{x:.1f}" y2="{y:.1f}" stroke="{p["grid"]}" stroke-width="0.7" {lf_fade(60)}/>')
|
|
602
551
|
is_peak = wk["week"] in peak_weeks
|
|
603
552
|
out.append(f'<line x1="{x:.1f}" y1="{y:.1f}" x2="{x:.1f}" y2="{min(y_base, y + 12 + ((i * 7) % 12)):.1f}" stroke="{p["text"]}" stroke-width="{1.4 if is_peak else 0.9}" {lf_fade(min(i * STAGGER_BAR, 1200) + 80)}/>')
|
|
604
|
-
out.append(f'<circle cx="{x:.1f}" cy="{y:.1f}" r="{4.8 if is_peak else 2.5:.1f}" fill="{p["primary"] if is_peak else p["text"]}" {lf_pop(min(i * STAGGER_DOT, 700) + 160)}
|
|
553
|
+
out.append(f'<circle cx="{x:.1f}" cy="{y:.1f}" r="{4.8 if is_peak else 2.5:.1f}" fill="{p["primary"] if is_peak else p["text"]}" {lf_pop(min(i * STAGGER_DOT, 700) + 160)}/>')
|
|
605
554
|
if is_peak:
|
|
606
555
|
pk = next((q for q in peaks if q["week"] == wk["week"]), None)
|
|
607
556
|
pk_txt = (pk or {}).get("label_zh" if lang == "zh" else "label_en", "")
|
|
@@ -616,10 +565,6 @@ def render_barcode_lollipop(bundle: dict, theme: str, meta: dict, audit: Optiona
|
|
|
616
565
|
return "\n".join(out)
|
|
617
566
|
|
|
618
567
|
|
|
619
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
620
|
-
# intervention.activity_weights — L1 Launch Fan
|
|
621
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
622
|
-
|
|
623
568
|
def render_launch_fan(bundle: dict, theme: str, meta: dict, audit: Optional[list] = None) -> str:
|
|
624
569
|
p = get_theme(theme)
|
|
625
570
|
A = Audit(audit)
|
|
@@ -647,10 +592,6 @@ def render_launch_fan(bundle: dict, theme: str, meta: dict, audit: Optional[list
|
|
|
647
592
|
return "\n".join(out)
|
|
648
593
|
|
|
649
594
|
|
|
650
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
651
|
-
# intervention.phase_groups — L8 Dotty Matrix
|
|
652
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
653
|
-
|
|
654
595
|
def render_dotty_matrix(bundle: dict, theme: str, meta: dict, audit: Optional[list] = None) -> str:
|
|
655
596
|
p = get_theme(theme)
|
|
656
597
|
A = Audit(audit)
|
|
@@ -661,6 +602,7 @@ def render_dotty_matrix(bundle: dict, theme: str, meta: dict, audit: Optional[li
|
|
|
661
602
|
|
|
662
603
|
def P(c, r, k):
|
|
663
604
|
return 236 + (c - r) * 17, 244 + (c + r) * 8.4 - k * 52
|
|
605
|
+
|
|
664
606
|
for k in range(len(layers)):
|
|
665
607
|
cs = [P(-0.6, -0.6, k), P(5.6, -0.6, k), P(5.6, 5.6, k), P(-0.6, 5.6, k)]
|
|
666
608
|
poly_d = "M " + " L ".join(f"{x:.1f} {y:.1f}" for x, y in cs) + " Z"
|
|
@@ -679,10 +621,6 @@ def render_dotty_matrix(bundle: dict, theme: str, meta: dict, audit: Optional[li
|
|
|
679
621
|
return "\n".join(out)
|
|
680
622
|
|
|
681
623
|
|
|
682
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
683
|
-
# decision.confidence_score — F11 Tick Gauge
|
|
684
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
685
|
-
|
|
686
624
|
def render_tick_gauge(bundle: dict, theme: str, meta: dict, audit: Optional[list] = None) -> str:
|
|
687
625
|
p = get_theme(theme)
|
|
688
626
|
A = Audit(audit)
|
|
@@ -705,10 +643,6 @@ def render_tick_gauge(bundle: dict, theme: str, meta: dict, audit: Optional[list
|
|
|
705
643
|
return "\n".join(out)
|
|
706
644
|
|
|
707
645
|
|
|
708
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
709
|
-
# methodology.flag_rates — L15 Ballot Tally
|
|
710
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
711
|
-
|
|
712
646
|
def render_ballot_tally(bundle: dict, theme: str, meta: dict, audit: Optional[list] = None) -> str:
|
|
713
647
|
p = get_theme(theme)
|
|
714
648
|
A = Audit(audit)
|
|
@@ -730,17 +664,14 @@ def render_ballot_tally(bundle: dict, theme: str, meta: dict, audit: Optional[li
|
|
|
730
664
|
x = 162 + t * 11
|
|
731
665
|
if x > 470:
|
|
732
666
|
break
|
|
733
|
-
|
|
667
|
+
flag_suffix = " (flagged)" if t < flagged else ""
|
|
668
|
+
out.append(f'<rect x="{x:.1f}" y="{yc - 4:.1f}" width="8" height="8" rx="2" fill="{p["primary"] if t < flagged else p["grid"]}" {lf_pop(min(r_i * STAGGER_BAR + t * STAGGER_DOT, 1400))}><title>{esc(str(label))} — verdict {t + 1}/{total}{flag_suffix}</title></rect>')
|
|
734
669
|
out.append(f'<text x="512" y="{yc + 3:.1f}" text-anchor="end" font-size="9" font-weight="800" fill="{p["text"]}" {lf_fade(r_i * STAGGER_BAR + 300)}>{flagged}/{total}</text>')
|
|
735
670
|
out.append(f'<text x="30" y="280" font-size="7.5" font-weight="600" fill="{p["muted"]}" {lf_fade(500)}>{esc(str(bundle.get("derived", ""))[:70])}</text>')
|
|
736
671
|
out.append("</svg>")
|
|
737
672
|
return "\n".join(out)
|
|
738
673
|
|
|
739
674
|
|
|
740
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
741
|
-
# evidence.year_x_outcome_counts — L16 Matrix Heat
|
|
742
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
743
|
-
|
|
744
675
|
def render_matrix_heat(bundle: dict, theme: str, meta: dict, audit: Optional[list] = None) -> str:
|
|
745
676
|
p = get_theme(theme)
|
|
746
677
|
A = Audit(audit)
|
|
@@ -772,15 +703,10 @@ def render_matrix_heat(bundle: dict, theme: str, meta: dict, audit: Optional[lis
|
|
|
772
703
|
else:
|
|
773
704
|
out.append(f'<rect x="{x + 2:.1f}" y="{y + 2:.1f}" width="{cell_w - 4:.1f}" height="{cell_h - 4}" rx="4" fill="{p["grid"]}" fill-opacity="0.5" {lf_fade((i + j) * STAGGER_DOT)}/>')
|
|
774
705
|
out.append(f'<text x="{x + cell_w / 2:.1f}" y="{y + cell_h / 2 + 3:.1f}" text-anchor="middle" font-size="{MIN_FONT + 1.5}" font-weight="800" fill="{p["text"]}" {lf_pop(min((i + j) * STAGGER_DOT + 60, 900))}>{v}</text>')
|
|
775
|
-
out.append(f'<title>{esc(str(label))} × {esc(str(years[j]))} — {v} studies</title>')
|
|
776
706
|
out.append("</svg>")
|
|
777
707
|
return "\n".join(out)
|
|
778
708
|
|
|
779
709
|
|
|
780
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
781
|
-
# Registry + dispatcher
|
|
782
|
-
# ══════════════════════════════════════════════════════════════════════════
|
|
783
|
-
|
|
784
710
|
REGISTRY: Dict[str, Dict[str, Any]] = {
|
|
785
711
|
"forest_plot": {"catalog_ref": "FOREST-PLOT (publication figure)", "source": "meta.forest",
|
|
786
712
|
"extractor": CD.extract_meta_forest, "renderer": render_forest_plot,
|
|
@@ -844,8 +770,6 @@ ACADEMIC_FIGURE_KEYS = ("outcome-comparison.svg", "benchmark-citation-support.sv
|
|
|
844
770
|
|
|
845
771
|
def render_figure(fig_type: str, bundle: dict, theme: str, meta: dict,
|
|
846
772
|
audit: Optional[list] = None) -> str:
|
|
847
|
-
"""Dispatch to the registered renderer. Unknown types raise ValueError —
|
|
848
|
-
the caller records the reason and never silently falls back."""
|
|
849
773
|
entry = REGISTRY.get(fig_type)
|
|
850
774
|
if entry is None:
|
|
851
775
|
raise ValueError(f"unregistered lieflat chart type: {fig_type!r}")
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
:root[data-theme="academic"] body { font-size:15px; line-height:1.58; }
|
|
14
14
|
:root[data-theme="academic"] .report-shell { max-width:980px; padding-top:22px; }
|
|
15
15
|
:root[data-theme="academic"] .report-header { border-bottom:2px solid #222; margin-bottom:34px; }
|
|
16
|
-
:root[data-theme="academic"] .report-header h1 { font-size:
|
|
16
|
+
:root[data-theme="academic"] .report-header h1 { font-size:clamp(1.5rem,2.2vw,1.7rem); line-height:1.18; text-align:center; }
|
|
17
17
|
:root[data-theme="academic"] .report-header .meta { text-align:center; }
|
|
18
18
|
:root[data-theme="academic"] .report-section { background:transparent; border:0; box-shadow:none; padding:0; margin-bottom:34px; }
|
|
19
19
|
:root[data-theme="academic"] .report-section>h2 { font-size:1.08rem; border-bottom:1px solid #444; border-radius:0; padding:0 0 5px; }
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
}
|
|
13
13
|
:root[data-theme="claude"] .report-shell { max-width:1240px; padding-top:34px; }
|
|
14
14
|
:root[data-theme="claude"] .report-header { max-width:900px; margin:0 auto 42px; padding-bottom:22px; }
|
|
15
|
-
:root[data-theme="claude"] .report-header h1 { font-size:clamp(1.
|
|
15
|
+
:root[data-theme="claude"] .report-header h1 { font-size:clamp(1.55rem,2.8vw,2.25rem); line-height:1.18; font-weight:500; }
|
|
16
16
|
:root[data-theme="claude"] .report-section { background:transparent; border:0; box-shadow:none; padding:0; }
|
|
17
17
|
:root[data-theme="claude"] .report-section>h2 { border:0; font-size:1.4rem; padding:0; margin-bottom:7px; }
|
|
18
18
|
:root[data-theme="claude"] .section-lead { background:transparent; border:0; padding:0; max-width:760px; margin-bottom:24px; }
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
width:100%; max-width:none; margin:0 0 22px; padding:18px 20px;
|
|
23
23
|
border:1px solid var(--border); border-radius:12px; background:var(--surface); box-shadow:var(--shadow);
|
|
24
24
|
}
|
|
25
|
-
:root[data-theme="datalab-dark"] .report-header h1 { max-width:1100px; font-size:clamp(1.
|
|
25
|
+
:root[data-theme="datalab-dark"] .report-header h1 { max-width:1100px; font-size:clamp(1.4rem,2vw,1.75rem); line-height:1.22; }
|
|
26
26
|
:root[data-theme="datalab-dark"] .report-header .meta { margin:8px 0 0; font-size:.85rem; color:var(--text-muted); }
|
|
27
27
|
:root[data-theme="datalab-dark"] .report-brand-row { margin-bottom:8px; }
|
|
28
28
|
:root[data-theme="datalab-dark"] .report-brand { color:var(--primary); text-transform:uppercase; letter-spacing:.08em; font-size:.83rem; }
|
|
@@ -134,7 +134,7 @@
|
|
|
134
134
|
:root[data-theme="datalab-dark"] .controls { width:calc(100% - 24px); padding:8px 10px; }
|
|
135
135
|
:root[data-theme="datalab-dark"] .report-shell { padding-left:14px; padding-right:14px; }
|
|
136
136
|
:root[data-theme="datalab-dark"] .report-header { padding:15px 14px; }
|
|
137
|
-
:root[data-theme="datalab-dark"] .report-header h1 { font-size:1.
|
|
137
|
+
:root[data-theme="datalab-dark"] .report-header h1 { font-size:1.3rem; }
|
|
138
138
|
:root[data-theme="datalab-dark"] .brief-block { padding:15px 14px; }
|
|
139
139
|
:root[data-theme="datalab-dark"] .brief-decision { padding:0; }
|
|
140
140
|
:root[data-theme="datalab-dark"] .brief-decision .brief-block-header { padding:15px 14px 0; }
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
width:100%; max-width:none; margin:0 0 22px; padding:18px 20px;
|
|
24
24
|
border:1px solid var(--border); border-radius:12px; background:var(--surface); box-shadow:var(--shadow);
|
|
25
25
|
}
|
|
26
|
-
:root[data-theme="datalab"] .report-header h1 { max-width:1100px; font-size:clamp(1.
|
|
26
|
+
:root[data-theme="datalab"] .report-header h1 { max-width:1100px; font-size:clamp(1.4rem,2vw,1.75rem); line-height:1.22; }
|
|
27
27
|
:root[data-theme="datalab"] .report-header .meta { margin:8px 0 0; font-size:.85rem; }
|
|
28
28
|
:root[data-theme="datalab"] .report-brand-row { margin-bottom:8px; }
|
|
29
29
|
:root[data-theme="datalab"] .report-brand { color:var(--primary); text-transform:uppercase; letter-spacing:.08em; font-size:.83rem; }
|
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
:root[data-theme="datalab"] .controls { width:calc(100% - 24px); padding:8px 10px; }
|
|
139
139
|
:root[data-theme="datalab"] .report-shell { padding-left:14px; padding-right:14px; }
|
|
140
140
|
:root[data-theme="datalab"] .report-header { padding:15px 14px; }
|
|
141
|
-
:root[data-theme="datalab"] .report-header h1 { font-size:1.
|
|
141
|
+
:root[data-theme="datalab"] .report-header h1 { font-size:1.3rem; }
|
|
142
142
|
:root[data-theme="datalab"] .brief-block { padding:15px 14px; }
|
|
143
143
|
:root[data-theme="datalab"] .brief-decision { padding:0; }
|
|
144
144
|
:root[data-theme="datalab"] .brief-decision .brief-block-header { padding:15px 14px 0; }
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
:root[data-theme="presentation"] .report-brand-row { margin-bottom:12px; }
|
|
32
32
|
:root[data-theme="presentation"] .report-brand { color:var(--primary); text-transform:uppercase; letter-spacing:.1em; font-size:.8rem; }
|
|
33
33
|
:root[data-theme="presentation"] .generated-theme-chip { background:#242A33; border-color:#3C4450; color:#B8C0CB; }
|
|
34
|
-
:root[data-theme="presentation"] .report-header h1 { max-width:980px; font-size:clamp(1.
|
|
34
|
+
:root[data-theme="presentation"] .report-header h1 { max-width:980px; font-size:clamp(1.35rem,2vw,1.7rem); line-height:1.08; letter-spacing:-.022em; }
|
|
35
35
|
:root[data-theme="presentation"] .report-header .meta { margin:10px 0 0; color:var(--text-muted); font-size:.86rem; overflow-wrap:anywhere; }
|
|
36
36
|
:root[data-theme="presentation"] .report-view-switcher { display:inline-flex; margin-top:18px; padding:4px; border:1px solid var(--border); border-radius:999px; background:#12151A; }
|
|
37
37
|
:root[data-theme="presentation"] .report-view-btn { min-height:36px; border:0; background:transparent; color:#BBC2CC; padding:7px 15px; }
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
:root[data-theme="presentation"] .controls { width:calc(100% - 24px); border-radius:10px; }
|
|
115
115
|
:root[data-theme="presentation"] .report-shell { padding-left:14px; padding-right:14px; }
|
|
116
116
|
:root[data-theme="presentation"] .report-header { padding:18px 16px; border-radius:12px; }
|
|
117
|
-
:root[data-theme="presentation"] .report-header h1 { font-size:1.
|
|
117
|
+
:root[data-theme="presentation"] .report-header h1 { font-size:1.2rem; }
|
|
118
118
|
:root[data-theme="presentation"] .brief-block { padding:18px 16px; }
|
|
119
119
|
:root[data-theme="presentation"] .brief-decision { padding:0; }
|
|
120
120
|
:root[data-theme="presentation"] .brief-decision .decision-hero { min-height:0; padding:24px 18px; border-radius:13px; }
|
package/web/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# web/ — 前端分发与兼容边界
|
|
2
|
+
|
|
3
|
+
EduEvidence 当前正式控制台是 **Research Studio**。开发源与运行时分发物必须明确区分:
|
|
4
|
+
|
|
5
|
+
- **唯一权威可编辑控制台源**:`studio/`(React + TypeScript + Vite)。新功能、交互、可访问性、移动端和数据契约只在这里开发。
|
|
6
|
+
- **正式运行时控制台**:`web/studio/`。它是 `studio/` 的可复现构建产物,由 Python 本地服务、Skill 上架包和 GitHub Pages 直接提供;不要手工编辑其中的 bundle。
|
|
7
|
+
- `web/landing.html/.css/.js`:公开介绍页,保持独立视觉叙事;它只负责介绍和进入 Research Studio,不拥有研究状态。
|
|
8
|
+
- `web/index.html`、`web/js/`、`web/styles.css`:v5 时代三页控制台的**兼容层 / 历史实现**。现有兼容 API 或旧链接在确有需要时可以继续读取它们,但不得把它们当作新 UI 的开发入口,也不要向其中增加新的产品功能。
|
|
9
|
+
- `web/showcase_v2/`:冻结展示页。除安全或断链修复外不再与 Research Studio 同步演进。
|
|
10
|
+
|
|
11
|
+
## 约定
|
|
12
|
+
|
|
13
|
+
1. 新的 Research Studio 页面、组件和交互只能进入 `studio/src/`;禁止继续扩展旧 `web/js/` 控制台。
|
|
14
|
+
2. `npm run build --prefix studio` 必须可复现 `web/studio/`;CI 使用 `git diff --exit-code -- web/studio` 守护这一点。
|
|
15
|
+
3. 本地正式入口是 `http://127.0.0.1:<port>/studio/`;`dashboard_server.py` 的 `/`、`/console` 也应指向同一构建。
|
|
16
|
+
4. GitHub Pages 根页保留介绍页,`/studio/` 是公开只读 Research Studio;静态导出只包含仓库示例,不包含本地研究项目、运行事件或 Autoevolve 会话。
|
|
17
|
+
5. 五种报告主题属于 `visualization/eduevidence-report/` 的**生成时视觉身份**,不是 Research Studio 的运行时换肤;同一研究版本的科学内容不得因主题改变。
|
|
18
|
+
6. 报告本体保持 single-file / zero-CDN;营销介绍页可以使用外部字体等展示资源,但不应把它们引入研究报告的离线能力声明。
|
package/web/index.html
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>EduEvidence Research Studio · Build unavailable</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root { color-scheme: light dark; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
|
|
9
|
+
* { box-sizing: border-box; }
|
|
10
|
+
body { margin: 0; min-height: 100vh; display: grid; place-items: center; background: #f7f6f3; color: #242321; padding: 24px; }
|
|
11
|
+
main { width: min(680px, 100%); background: #fff; border: 1px solid #dedbd5; border-radius: 18px; padding: clamp(28px, 6vw, 52px); box-shadow: 0 16px 55px rgba(42, 37, 31, .08); }
|
|
12
|
+
.eyebrow { margin: 0 0 18px; font-size: 12px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; color: #827d74; }
|
|
13
|
+
h1 { margin: 0; font-family: Georgia, "Times New Roman", serif; font-size: clamp(2rem, 7vw, 3.4rem); line-height: 1.02; font-weight: 500; letter-spacing: -.035em; }
|
|
14
|
+
p { margin: 22px 0 0; max-width: 56ch; line-height: 1.8; color: #625e57; }
|
|
15
|
+
code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .9em; background: #f2f0ec; border-radius: 6px; padding: .15em .4em; color: #393631; }
|
|
16
|
+
.actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 28px; }
|
|
17
|
+
a { display: inline-flex; align-items: center; min-height: 42px; padding: 0 15px; border-radius: 9px; border: 1px solid #ccc7bf; color: inherit; text-decoration: none; font-weight: 650; font-size: 14px; }
|
|
18
|
+
a.primary { background: #242321; color: #fff; border-color: #242321; }
|
|
19
|
+
small { display: block; margin-top: 30px; color: #8b867e; line-height: 1.65; }
|
|
20
|
+
@media (prefers-color-scheme: dark) {
|
|
21
|
+
body { background: #181817; color: #efede8; }
|
|
22
|
+
main { background: #211f1d; border-color: #3b3833; box-shadow: none; }
|
|
23
|
+
p, small, .eyebrow { color: #aaa49a; }
|
|
24
|
+
code { background: #302d29; color: #eeeae3; }
|
|
25
|
+
a { border-color: #504b44; }
|
|
26
|
+
a.primary { background: #efede8; color: #211f1d; border-color: #efede8; }
|
|
27
|
+
}
|
|
28
|
+
</style>
|
|
29
|
+
</head>
|
|
30
|
+
<body>
|
|
31
|
+
<main>
|
|
32
|
+
<p class="eyebrow">EduEvidence · Compatibility boundary</p>
|
|
33
|
+
<h1>Research Studio build unavailable.</h1>
|
|
34
|
+
<p>
|
|
35
|
+
旧版 v5 三页控制台已经退出正式产品路径。看到这个页面说明当前分发目录缺少
|
|
36
|
+
<code>web/studio/</code> 的可复现构建产物;服务不会再静默降级到旧控制台。
|
|
37
|
+
</p>
|
|
38
|
+
<p>
|
|
39
|
+
开发仓库请运行 <code>npm ci --prefix studio</code> 与
|
|
40
|
+
<code>npm run build --prefix studio</code>,然后重新启动本地 Python 服务。
|
|
41
|
+
正常入口始终是 <code>/studio/</code>。
|
|
42
|
+
</p>
|
|
43
|
+
<div class="actions">
|
|
44
|
+
<a class="primary" href="/studio/">重新检查 Research Studio</a>
|
|
45
|
+
<a href="/api/studio/catalog">检查只读数据接口</a>
|
|
46
|
+
</div>
|
|
47
|
+
<small>
|
|
48
|
+
This page is intentionally a fail-closed compatibility tombstone. It contains no research UI,
|
|
49
|
+
no legacy dashboard runtime, and no scientific fallback data.
|
|
50
|
+
</small>
|
|
51
|
+
</main>
|
|
52
|
+
</body>
|
|
53
|
+
</html>
|