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,192 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import math
|
|
3
|
+
from PIL import Image, ImageDraw, ImageFont, ImageFilter
|
|
4
|
+
|
|
5
|
+
def create_promo_image(output_png="assets/eduevidence-promo.png", output_jpg="assets/eduevidence-promo.jpg"):
|
|
6
|
+
os.makedirs(os.path.dirname(output_png), exist_ok=True)
|
|
7
|
+
|
|
8
|
+
# 2K Resolution Banner / Card (1920x1080)
|
|
9
|
+
width, height = 1920, 1080
|
|
10
|
+
|
|
11
|
+
# Base background (Deep Cyber Slate / Navy)
|
|
12
|
+
base = Image.new("RGBA", (width, height), (10, 15, 29, 255))
|
|
13
|
+
draw = ImageDraw.Draw(base)
|
|
14
|
+
|
|
15
|
+
# Glowing Ambient Orbs
|
|
16
|
+
glow_overlay = Image.new("RGBA", (width, height), (0, 0, 0, 0))
|
|
17
|
+
glow_draw = ImageDraw.Draw(glow_overlay)
|
|
18
|
+
|
|
19
|
+
# 1. Cyan Glow (Top-Left)
|
|
20
|
+
for r in range(450, 0, -15):
|
|
21
|
+
alpha = int(40 * (1 - r / 450))
|
|
22
|
+
glow_draw.ellipse([220 - r, 160 - r, 220 + r, 160 + r], fill=(14, 165, 233, alpha))
|
|
23
|
+
|
|
24
|
+
# 2. Indigo/Violet Glow (Bottom-Right)
|
|
25
|
+
for r in range(550, 0, -15):
|
|
26
|
+
alpha = int(35 * (1 - r / 550))
|
|
27
|
+
glow_draw.ellipse([1650 - r, 820 - r, 1650 + r, 820 + r], fill=(99, 102, 241, alpha))
|
|
28
|
+
|
|
29
|
+
# 3. Emerald/Sky Glow (Center Accent)
|
|
30
|
+
for r in range(350, 0, -15):
|
|
31
|
+
alpha = int(25 * (1 - r / 350))
|
|
32
|
+
glow_draw.ellipse([980 - r, 540 - r, 980 + r, 540 + r], fill=(16, 185, 129, alpha))
|
|
33
|
+
|
|
34
|
+
glow_overlay = glow_overlay.filter(ImageFilter.GaussianBlur(35))
|
|
35
|
+
base = Image.alpha_composite(base, glow_overlay)
|
|
36
|
+
draw = ImageDraw.Draw(base)
|
|
37
|
+
|
|
38
|
+
# Subtle Background Grid
|
|
39
|
+
grid_color = (255, 255, 255, 10)
|
|
40
|
+
grid_step = 80
|
|
41
|
+
for x in range(0, width, grid_step):
|
|
42
|
+
draw.line([(x, 0), (x, height)], fill=grid_color, width=1)
|
|
43
|
+
for y in range(0, height, grid_step):
|
|
44
|
+
draw.line([(0, y), (width, y)], fill=grid_color, width=1)
|
|
45
|
+
|
|
46
|
+
# Fonts Setup
|
|
47
|
+
font_path_zh = "/System/Library/Fonts/Hiragino Sans GB.ttc"
|
|
48
|
+
font_path_en_bold = "/System/Library/Fonts/Supplemental/Arial Bold.ttf"
|
|
49
|
+
font_path_en_reg = "/System/Library/Fonts/Supplemental/Arial.ttf"
|
|
50
|
+
|
|
51
|
+
def get_font(path, size, index=0):
|
|
52
|
+
try:
|
|
53
|
+
return ImageFont.truetype(path, size, index=index)
|
|
54
|
+
except:
|
|
55
|
+
return ImageFont.load_default()
|
|
56
|
+
|
|
57
|
+
font_badge = get_font(font_path_zh, 21)
|
|
58
|
+
font_title = get_font(font_path_en_bold, 78)
|
|
59
|
+
font_subtitle_en = get_font(font_path_en_bold, 30)
|
|
60
|
+
font_subtitle_zh = get_font(font_path_zh, 24)
|
|
61
|
+
font_card_title = get_font(font_path_zh, 24)
|
|
62
|
+
font_card_desc = get_font(font_path_zh, 19)
|
|
63
|
+
font_tag = get_font(font_path_zh, 17)
|
|
64
|
+
font_stat_val = get_font(font_path_en_bold, 44)
|
|
65
|
+
font_stat_lbl = get_font(font_path_zh, 20)
|
|
66
|
+
font_code = get_font(font_path_en_bold, 20)
|
|
67
|
+
|
|
68
|
+
# ================= LEFT HERO SECTION =================
|
|
69
|
+
left_x = 110
|
|
70
|
+
|
|
71
|
+
# 1. Pill Badge
|
|
72
|
+
badge_x, badge_y = left_x, 100
|
|
73
|
+
badge_w, badge_h = 430, 46
|
|
74
|
+
draw.rounded_rectangle([badge_x, badge_y, badge_x + badge_w, badge_y + badge_h], radius=23, fill=(30, 41, 59, 210), outline=(56, 189, 248, 140), width=1)
|
|
75
|
+
draw.ellipse([badge_x + 18, badge_y + 17, badge_x + 30, badge_y + 29], fill=(56, 189, 248, 255))
|
|
76
|
+
draw.text((badge_x + 40, badge_y + 11), "AI Agent Skill · 循证教育决策引擎", font=font_badge, fill=(186, 230, 253, 255))
|
|
77
|
+
|
|
78
|
+
# 2. Main Title: EduEvidence
|
|
79
|
+
title_y = 180
|
|
80
|
+
draw.text((left_x, title_y), "EduEvidence", font=font_title, fill=(255, 255, 255, 255))
|
|
81
|
+
|
|
82
|
+
# 3. Subtitles
|
|
83
|
+
sub_y = title_y + 100
|
|
84
|
+
draw.text((left_x, sub_y), "From Education Questions to Evidence-Based Decisions", font=font_subtitle_en, fill=(56, 189, 248, 255))
|
|
85
|
+
draw.text((left_x, sub_y + 46), "让每一项教育与教学决策,都有顶刊真实学术证据与科学法庭支撑", font=font_subtitle_zh, fill=(203, 213, 225, 255))
|
|
86
|
+
|
|
87
|
+
# 4. Description Paragraph
|
|
88
|
+
desc_y = sub_y + 98
|
|
89
|
+
desc_lines = [
|
|
90
|
+
"• 拒绝 LLM 幻觉与凭空臆测,告别「能不能用/好不好用」的二元简单回答",
|
|
91
|
+
"• 深度融合 PNAS / CHI / ACL / 教育实证研究与经典元分析知识库",
|
|
92
|
+
"• 提供「四态决策模型 + 多智能体证据法庭 + 实验设计 + 数据分析」全闭环"
|
|
93
|
+
]
|
|
94
|
+
for i, line in enumerate(desc_lines):
|
|
95
|
+
draw.text((left_x, desc_y + i * 36), line, font=font_card_desc, fill=(148, 163, 184, 255))
|
|
96
|
+
|
|
97
|
+
# 5. Three Key Value Pillars (Cards at bottom left)
|
|
98
|
+
pillars = [
|
|
99
|
+
("⚖️ 四态科学决策", "ADOPT / PILOT", "REJECT / INSUFFICIENT", (34, 197, 94, 200)),
|
|
100
|
+
("🔬 多智能体法庭", "提炼 · 辩护 · 质询", "合议仲裁 4 重严谨校验", (56, 189, 248, 200)),
|
|
101
|
+
("🔄 全周期研究闭环", "文献综述 → 缺口识别", "实验设计 → 实证分析", (168, 85, 247, 200)),
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
pillar_y = 550
|
|
105
|
+
card_w = 275
|
|
106
|
+
card_h = 160
|
|
107
|
+
card_gap = 25
|
|
108
|
+
|
|
109
|
+
for idx, (p_title, line1, line2, border_c) in enumerate(pillars):
|
|
110
|
+
cx = left_x + idx * (card_w + card_gap)
|
|
111
|
+
draw.rounded_rectangle([cx, pillar_y, cx + card_w, pillar_y + card_h], radius=16, fill=(15, 23, 42, 230), outline=border_c, width=1)
|
|
112
|
+
draw.rounded_rectangle([cx + 16, pillar_y + 18, cx + 22, pillar_y + 44], radius=3, fill=border_c)
|
|
113
|
+
draw.text((cx + 32, pillar_y + 18), p_title, font=font_card_title, fill=(248, 250, 252, 255))
|
|
114
|
+
|
|
115
|
+
draw.text((cx + 18, pillar_y + 72), line1, font=font_tag, fill=(203, 213, 225, 255))
|
|
116
|
+
draw.text((cx + 18, pillar_y + 104), line2, font=font_tag, fill=(148, 163, 184, 255))
|
|
117
|
+
|
|
118
|
+
# ================= RIGHT DASHBOARD / FEATURE PREVIEW =================
|
|
119
|
+
right_x = 1060
|
|
120
|
+
right_y = 100
|
|
121
|
+
right_w = 750
|
|
122
|
+
right_h = 610
|
|
123
|
+
|
|
124
|
+
# Outer Glassmorphism Card
|
|
125
|
+
draw.rounded_rectangle([right_x, right_y, right_x + right_w, right_y + right_h], radius=22, fill=(17, 24, 39, 220), outline=(75, 85, 99, 120), width=1)
|
|
126
|
+
|
|
127
|
+
# Header of Right Panel
|
|
128
|
+
header_h = 64
|
|
129
|
+
draw.rounded_rectangle([right_x, right_y, right_x + right_w, right_y + header_h], radius=22, fill=(30, 41, 59, 180))
|
|
130
|
+
draw.ellipse([right_x + 24, right_y + 25, right_x + 38, right_y + 39], fill=(239, 68, 68, 220))
|
|
131
|
+
draw.ellipse([right_x + 48, right_y + 25, right_x + 62, right_y + 39], fill=(245, 158, 11, 220))
|
|
132
|
+
draw.ellipse([right_x + 72, right_y + 25, right_x + 86, right_y + 39], fill=(34, 197, 94, 220))
|
|
133
|
+
draw.text((right_x + 110, right_y + 20), "EduEvidence Research Framework & Ecosystem", font=get_font(font_path_en_bold, 19), fill=(226, 232, 240, 255))
|
|
134
|
+
|
|
135
|
+
# Inner Content Items
|
|
136
|
+
item_y = right_y + 85
|
|
137
|
+
items = [
|
|
138
|
+
("⚡ 零三方依赖原生架构 (Native Core)", "纯 Python 3 标准库打造,零额外依赖包,免配置、极速启动、离线可用", "0 Deps"),
|
|
139
|
+
("🤖 全主流 AI Agent 平台支持", "一键适配 Claude Code / Codex / OMP / OpenCode / Kimi / Grok / Copilot 等", "10+ Hosts"),
|
|
140
|
+
("📑 决策与研究双层交互报告", "面向教育管理者的速览建议 + 面向学术研究者的完整实证证据链下钻", "Dual-Layer"),
|
|
141
|
+
("🔒 冻结式科学铁律 (Frozen Science Rule)", "严格要求实证依据支撑,严禁凭空生成实验方案,全面关联 Gap ID 溯源", "Auditable")
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
for idx, (title_str, sub_str, badge_str) in enumerate(items):
|
|
145
|
+
iy = item_y + idx * 122
|
|
146
|
+
# Mini Card
|
|
147
|
+
draw.rounded_rectangle([right_x + 20, iy, right_x + right_w - 20, iy + 106], radius=14, fill=(11, 17, 32, 230), outline=(51, 65, 85, 130), width=1)
|
|
148
|
+
|
|
149
|
+
draw.text((right_x + 36, iy + 18), title_str, font=font_card_title, fill=(241, 245, 249, 255))
|
|
150
|
+
draw.text((right_x + 36, iy + 58), sub_str, font=font_tag, fill=(148, 163, 184, 255))
|
|
151
|
+
|
|
152
|
+
# Badge right
|
|
153
|
+
badge_w_val = 112
|
|
154
|
+
draw.rounded_rectangle([right_x + right_w - 36 - badge_w_val, iy + 18, right_x + right_w - 36, iy + 50], radius=8, fill=(30, 58, 138, 200), outline=(56, 189, 248, 160), width=1)
|
|
155
|
+
draw.text((right_x + right_w - 30 - badge_w_val + 10, iy + 23), badge_str, font=get_font(font_path_en_bold, 15), fill=(186, 230, 253, 255))
|
|
156
|
+
|
|
157
|
+
# ================= BOTTOM BAR (INSTALL & REPO INFO) =================
|
|
158
|
+
bar_y = 760
|
|
159
|
+
bar_w = width - 220
|
|
160
|
+
draw.rounded_rectangle([left_x, bar_y, left_x + bar_w, bar_y + 200], radius=20, fill=(15, 23, 42, 240), outline=(56, 189, 248, 100), width=1)
|
|
161
|
+
|
|
162
|
+
# Left part of bottom bar: Install command box
|
|
163
|
+
cmd_box_x = left_x + 32
|
|
164
|
+
cmd_box_y = bar_y + 32
|
|
165
|
+
cmd_box_w = 880
|
|
166
|
+
cmd_box_h = 136
|
|
167
|
+
draw.rounded_rectangle([cmd_box_x, cmd_box_y, cmd_box_x + cmd_box_w, cmd_box_y + cmd_box_h], radius=12, fill=(3, 7, 18, 255), outline=(30, 41, 59, 255), width=1)
|
|
168
|
+
|
|
169
|
+
draw.text((cmd_box_x + 24, cmd_box_y + 20), "$ git clone https://github.com/37chengshan/eduevidence.git", font=font_code, fill=(248, 250, 252, 255))
|
|
170
|
+
draw.text((cmd_box_x + 24, cmd_box_y + 56), "$ cd eduevidence && bash install.sh --skill", font=font_code, fill=(56, 189, 248, 255))
|
|
171
|
+
draw.text((cmd_box_x + 24, cmd_box_y + 94), "✨ 支持自动检测当前环境(Claude Code, OMP, Codex, OpenCode 等)并一键激活", font=font_tag, fill=(100, 116, 139, 255))
|
|
172
|
+
|
|
173
|
+
# Right part of bottom bar: Stats / Open Source Badges
|
|
174
|
+
stats_x = cmd_box_x + cmd_box_w + 60
|
|
175
|
+
stats = [
|
|
176
|
+
("100%", "开源可溯源"),
|
|
177
|
+
("10+", "主流宿主支持"),
|
|
178
|
+
("4-State", "循证决策框架"),
|
|
179
|
+
]
|
|
180
|
+
for s_i, (num, lbl) in enumerate(stats):
|
|
181
|
+
sx = stats_x + s_i * 230
|
|
182
|
+
draw.text((sx, bar_y + 50), num, font=font_stat_val, fill=(56, 189, 248, 255))
|
|
183
|
+
draw.text((sx, bar_y + 115), lbl, font=font_stat_lbl, fill=(203, 213, 225, 255))
|
|
184
|
+
|
|
185
|
+
# Save Output
|
|
186
|
+
rgb_img = base.convert("RGB")
|
|
187
|
+
rgb_img.save(output_png, quality=95)
|
|
188
|
+
rgb_img.save(output_jpg, quality=95)
|
|
189
|
+
print(f"Generated promo images:\n - {output_png}\n - {output_jpg}")
|
|
190
|
+
|
|
191
|
+
if __name__ == "__main__":
|
|
192
|
+
create_promo_image()
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""scripts/lint_report_layout.py — 五主题报告排版约束门(静态 + 浏览器级)。
|
|
3
|
+
|
|
4
|
+
静态审计(无需浏览器):对基座 CSS(build_report.py 内联)与 5 份主题 CSS 检查
|
|
5
|
+
移动安全不变量——任何会撑破视口的写法(裸 1fr 轨道、固定 px 最小值的 auto-fit、
|
|
6
|
+
缺少移动端媒体覆写的双列 grid)都必须被拒绝;基座必须携带安全网。
|
|
7
|
+
|
|
8
|
+
浏览器级门(可选,需要 Chrome + Node ≥21):对烘焙出的 HTML 报告跑
|
|
9
|
+
check_mobile_layout.js,在 390/768/1280 视口 × brief/full 双视图实测:
|
|
10
|
+
页面无横向溢出、可见 shell 无内部裁切、画廊卡片滚入后全部 is-live(reveal 生效)。
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
python3 scripts/lint_report_layout.py # 静态审计主题 CSS 与基座
|
|
14
|
+
python3 scripts/lint_report_layout.py --html-dir <dir> # 追加浏览器级扫描
|
|
15
|
+
Exit: 0 = 全部通过;1 = 静态违规;2 = 浏览器级违规。
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import argparse
|
|
20
|
+
import re
|
|
21
|
+
import subprocess
|
|
22
|
+
import sys
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
|
|
25
|
+
ROOT = Path(__file__).resolve().parent.parent
|
|
26
|
+
SCRIPTS_DIR = ROOT / "visualization" / "eduevidence-report" / "scripts"
|
|
27
|
+
THEMES_DIR = SCRIPTS_DIR.parent / "themes"
|
|
28
|
+
THEMES = ("claude", "academic", "datalab", "datalab-dark", "presentation")
|
|
29
|
+
|
|
30
|
+
# 静态不变量 ----------------------------------------------------------------
|
|
31
|
+
# 1) 主题 css:双列 grid(含 px 最小值)必须在该主题文件内提供移动端覆写。
|
|
32
|
+
TWO_COL_GRID = re.compile(
|
|
33
|
+
r'grid-template-columns:\s*(?:minmax\(\s*\d+px[^,]*,\s*(?:\.?\d+(?:fr|px)?|[^)]*)\)|[^;]*fr)\s*[^;]*;')
|
|
34
|
+
# 2) auto-fit 轨道不得使用裸固定 px 最小值(必须 min(min(Npx,100%),1fr))。
|
|
35
|
+
BARE_AUTOFIT = re.compile(r'repeat\(\s*auto-fit\s*,\s*minmax\(\s*\d+px\s*,\s*1fr\s*\)\s*\)')
|
|
36
|
+
SAFE_AUTOFIT = re.compile(r'repeat\(\s*auto-fit\s*,\s*minmax\(\s*min\(\s*\d+px\s*,\s*100%\)\s*,\s*1fr\s*\)\s*\)')
|
|
37
|
+
# 3) 裸 `1fr 轨道`(无 minmax(0,1fr)):出现在媒体查询外即为移动端风险。
|
|
38
|
+
BARE_1FR = re.compile(r'grid-template-columns:\s*1fr\s*;')
|
|
39
|
+
MINMAX0_1FR = re.compile(r'minmax\(\s*0\s*(?:px)?\s*,\s*1fr\s*\)')
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _strip_comments(text: str) -> str:
|
|
43
|
+
return re.sub(r"/\*.*?\*/", "", text, flags=re.S)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _sections(text: str):
|
|
47
|
+
"""Yield (selector_block, css, inside_media) chunks to scope checks."""
|
|
48
|
+
# 简化:按 @media 拆分顶层块
|
|
49
|
+
chunks = []
|
|
50
|
+
pos = 0
|
|
51
|
+
pattern = re.compile(r"@media[^{]*\{")
|
|
52
|
+
for m in pattern.finditer(text):
|
|
53
|
+
if m.start() > pos:
|
|
54
|
+
chunks.append((text[pos:m.start()], False))
|
|
55
|
+
# 平衡取块
|
|
56
|
+
start = m.end() - 1
|
|
57
|
+
depth = 1
|
|
58
|
+
i = start + 1
|
|
59
|
+
while i < len(text) and depth:
|
|
60
|
+
if text[i] == "{":
|
|
61
|
+
depth += 1
|
|
62
|
+
elif text[i] == "}":
|
|
63
|
+
depth -= 1
|
|
64
|
+
i += 1
|
|
65
|
+
chunks.append((text[start:i], True))
|
|
66
|
+
pos = i
|
|
67
|
+
if pos < len(text):
|
|
68
|
+
chunks.append((text[pos:], False))
|
|
69
|
+
return chunks
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def lint_static() -> list[str]:
|
|
73
|
+
problems: list[str] = []
|
|
74
|
+
|
|
75
|
+
# 基座 CSS(E2 起外置于 assets/base.css;build_report.py 仅保留加载器)。
|
|
76
|
+
# 扫描源 = py 文本({{}} 归一为 {},兼容旧内联形态)+ base.css 全文。
|
|
77
|
+
report_py = (SCRIPTS_DIR / "build_report.py").read_text(encoding="utf-8")
|
|
78
|
+
base_text = report_py.replace("{{", "{").replace("}}", "}")
|
|
79
|
+
base_css_path = SCRIPTS_DIR.parent / "assets" / "base.css"
|
|
80
|
+
if base_css_path.exists():
|
|
81
|
+
base_text += "\n" + _strip_comments(base_css_path.read_text(encoding="utf-8"))
|
|
82
|
+
if "overflow-x:hidden" not in base_text and "overflow-x: hidden" not in base_text:
|
|
83
|
+
problems.append("base: html/body 缺少 overflow-x:hidden(横向溢出兜底)")
|
|
84
|
+
for needle in (
|
|
85
|
+
".report-shell { width:100%; max-width:1200px;",
|
|
86
|
+
"@media (max-width:980px)",
|
|
87
|
+
"@media (max-width:720px)",
|
|
88
|
+
):
|
|
89
|
+
if needle not in base_text:
|
|
90
|
+
problems.append(f"base: 缺少必需样式片段 {needle!r}")
|
|
91
|
+
if "minmax(0,1fr)" not in base_text:
|
|
92
|
+
problems.append("base: 缺少 minmax(0,1fr)(移动端 1fr 轨道收缩安全)")
|
|
93
|
+
if "@media (max-width:720px)" not in base_text:
|
|
94
|
+
problems.append("base: @media (max-width:720px) 块缺失")
|
|
95
|
+
elif ".outcome-groups{grid-template-columns:repeat(auto-fit,minmax(min(180px,100%),1fr))" not in base_text.replace(" ", ""):
|
|
96
|
+
problems.append("base@720: .outcome-groups 缺少移动端收缩覆写")
|
|
97
|
+
if ".evidence-detail-grid, .source-detail-grid { grid-template-columns:1fr;" not in base_text:
|
|
98
|
+
problems.append("base@720: detail 网格缺少 1fr 覆写")
|
|
99
|
+
|
|
100
|
+
# 各主题
|
|
101
|
+
for theme in THEMES:
|
|
102
|
+
css_path = THEMES_DIR / f"{theme}.css"
|
|
103
|
+
if not css_path.exists():
|
|
104
|
+
problems.append(f"{theme}: 主题 CSS 缺失")
|
|
105
|
+
continue
|
|
106
|
+
raw = css_path.read_text(encoding="utf-8")
|
|
107
|
+
text = _strip_comments(raw)
|
|
108
|
+
media_regions = _sections(text)
|
|
109
|
+
outside = "".join(c for c, in_media in media_regions if not in_media)
|
|
110
|
+
inside980 = "".join(c for c, in_media in media_regions
|
|
111
|
+
if in_media and re.search(r"max-width:\s*(980|1100)px", c))
|
|
112
|
+
|
|
113
|
+
m = BARE_1FR.search(outside)
|
|
114
|
+
if m:
|
|
115
|
+
problems.append(f"{theme}: 媒体查询外出现裸 grid-template-columns:1fr —— 必须 minmax(0,1fr) 或移入媒体查询")
|
|
116
|
+
for mm in BARE_AUTOFIT.finditer(outside + inside980):
|
|
117
|
+
problems.append(f"{theme}: auto-fit 使用裸固定 px 最小值 {mm.group(0)[:40]} —— 必须 min(min(Npx,100%),1fr)")
|
|
118
|
+
# 双列/多列 grid 且未缩放的:必须在 <=980 媒体块内有同一选择器的覆写
|
|
119
|
+
grid_decls = re.findall(
|
|
120
|
+
r'([^{}]+)\{\s*[^{}]*?grid-template-columns:\s*([^;}]+);', outside)
|
|
121
|
+
for selector, cols in grid_decls:
|
|
122
|
+
if re.search(r"minmax\(\s*0", cols):
|
|
123
|
+
continue
|
|
124
|
+
if "repeat(auto-fit" in cols:
|
|
125
|
+
continue # 上面已单独检查
|
|
126
|
+
fr_count = len(re.findall(r"(\d+(?:\.\d+)?fr|minmax\([^)]*\))", cols))
|
|
127
|
+
has_px = re.search(r"\d+px", cols) or "1fr" in cols
|
|
128
|
+
if fr_count >= 2 and has_px:
|
|
129
|
+
sel_key = selector.strip()
|
|
130
|
+
# 主题移动覆写应含同一选择器(允许 media 块内是通用 'grid-template-columns' 无选择器?不,需选择器)
|
|
131
|
+
if not re.search(re.escape(sel_key) + r"|" +
|
|
132
|
+
re.escape(sel_key.split(" ")[-1]) + r"\s*,\s*[^{]*", inside980) \
|
|
133
|
+
and sel_key not in inside980 \
|
|
134
|
+
and selector.strip().split(">")[-1].strip() not in inside980:
|
|
135
|
+
problems.append(
|
|
136
|
+
f"{theme}: {selector.strip()[:60]} 双列 grid 在移动端缺少同主题覆写"
|
|
137
|
+
f"(cols={cols.strip()[:40]};主题规则特异性高于基座断点,必须自带 @media ≤980)")
|
|
138
|
+
return problems
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def main(argv: list[str] | None = None) -> int:
|
|
142
|
+
parser = argparse.ArgumentParser(description="EduEvidence five-theme report layout gate")
|
|
143
|
+
parser.add_argument("--html-dir", nargs="+", default=None,
|
|
144
|
+
help="烘焙好的报告目录;提供后追加浏览器级实测(390/768/1280 × brief/full)")
|
|
145
|
+
parser.add_argument("--project", default="ai-coding-assistant-evidence",
|
|
146
|
+
help="--html-dir 缺省时使用的示例项目名")
|
|
147
|
+
args = parser.parse_args(argv)
|
|
148
|
+
|
|
149
|
+
problems = lint_static()
|
|
150
|
+
for p in problems:
|
|
151
|
+
print(f" [static] {p}")
|
|
152
|
+
code = 1 if problems else 0
|
|
153
|
+
|
|
154
|
+
html_dirs: list[Path] = []
|
|
155
|
+
if args.html_dir:
|
|
156
|
+
html_dirs = [Path(d) for d in args.html_dir]
|
|
157
|
+
if html_dirs and code == 0:
|
|
158
|
+
files = sorted({p for d in html_dirs for p in Path(d).glob("*.html")})
|
|
159
|
+
if not files:
|
|
160
|
+
print(" [browser] 没有可扫描的 HTML")
|
|
161
|
+
return 2
|
|
162
|
+
node = subprocess.run(
|
|
163
|
+
["node", str(SCRIPTS_DIR / "check_mobile_layout.js"),
|
|
164
|
+
"--port", "9290", "--widths", "390,768,1280"]
|
|
165
|
+
+ [f"file://{p.resolve()}" for p in files],
|
|
166
|
+
capture_output=True, text=True)
|
|
167
|
+
out = (node.stdout or "") + (node.stderr or "")
|
|
168
|
+
if node.returncode == 0:
|
|
169
|
+
print(" [browser] ALL CLEAN")
|
|
170
|
+
else:
|
|
171
|
+
print(out[-4000:])
|
|
172
|
+
code = 2
|
|
173
|
+
if code == 0:
|
|
174
|
+
print("[+] Layout gate PASSED: 5 themes static invariants clean"
|
|
175
|
+
+ (" + browser-level clean" if html_dirs else ""))
|
|
176
|
+
else:
|
|
177
|
+
print(f"[-] Layout gate FAILED ({code})", file=sys.stderr)
|
|
178
|
+
return code
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
if __name__ == "__main__":
|
|
182
|
+
sys.exit(main())
|