ZZoutian/figure2data-database-v2
figure2data databasev2 — 40K v6 合成科研图表数据集(最终交付版) 生成日期:2026-09-17 生成器:generator 1.6.0 / dataset_generation_revision v6(含两次 hotfix) 规模:40,000 样本(10 图族 / 33 亚型;area、matrix 冻结不生成,set_relation 已删除) 目录结构 databasev2/ ├── figure2data.sqlite3 # 主数据库(2.0 GB:40,000 samples / 280,000 documents) ├── schema/ # sqlite schema ├── shards/ # 数据资产(shard = (样本序号-1)//1000) │ └── shard_000 .. shard_039/ │ ├── images/ # PNG… See the full description on the dataset page: https://huggingface.co/datasets/ZZoutian/figure2data-database-v2.
06.3k
1"""database_yh2 §5.1 / §13.S1:estimation renderer 语义契约(Tier A + Tier B)。2 3- raw observations / bootstrap means 必须散点绘制(geometry kind=points,绝不 polyline);4- paired 连接线:从 data.pairs 经 CategoryMapper 锚点绘制,端点数 == 2×n_pairs;5- estimation 系列必须有合法 CI(ci_low ≤ estimate ≤ ci_high,ci_high > ci_low);6- 固定种子回归:dbv01_0017 / dbv01_0035 / dbv01_0052(v6 scope 后重定位)。7"""8 9from __future__ import annotations10 11import numpy as np12import pytest13 14pytestmark = pytest.mark.usefixtures("tmp_staging")15 16 17def _estimation_series(docs):18 panels = {p["panel_id"]: p for p in docs["truth"]["figure"]["panels"]}19 geo = {g["series_id"]: g for g in docs["geometry"]["series"]}20 out = []21 for s in docs["truth"]["series"]:22 p = panels.get(s["panel_id"], {})23 if p.get("chart_subtype") == "estimation":24 out.append((s, p.get("param_variant"), geo.get(s["series_id"], {})))25 return out26 27 28class TestEstimationRendererContract:29 @pytest.mark.parametrize("variant", ["paired", "unpaired", "bootstrap_interval", "small_effect"])30 def test_raw_observations_never_polyline(self, make_docs, variant):31 """§5.1 ESTIMATION_RAW_POLYLINE:raw xy 观测几何必须是 points。"""32 docs, _ = make_docs("statistical", "estimation", variant=variant, seed=11)33 n_raw = 034 for s, var, sg in _estimation_series(docs):35 if s["data"].get("kind") == "xy":36 n_raw += 137 assert sg.get("kind") != "polyline" and not sg.get("points_px"), \38 f"{s.get('label')} 几何为 polyline(观测被连接)"39 assert sg.get("centers_px") or sg.get("bboxes_px"), "raw 观测无点几何"40 assert n_raw >= 141 42 def test_bootstrap_not_connected(self, make_docs):43 """§5.1 BOOTSTRAP_CONNECTED_SEQUENCE:bootstrap 分布几何必须是 points。"""44 docs, _ = make_docs("statistical", "estimation", variant="bootstrap_interval", seed=12)45 found = False46 for s, var, sg in _estimation_series(docs):47 if "Bootstrap" in (s.get("label") or ""):48 found = True49 assert sg.get("kind") == "points"50 assert sg.get("points_px") is None51 assert found, "缺 Bootstrap means 系列"52 53 def test_pair_links_from_pairs(self, make_docs):54 """§5.1 ESTIMATION_PAIR_COUNT_MISMATCH:连接线段端点 == 2×n_pairs,且成对语义正确。"""55 docs, _ = make_docs("statistical", "estimation", variant="paired", seed=13)56 found = False57 for s, var, sg in _estimation_series(docs):58 if s["data"].get("kind") == "paired":59 found = True60 pairs = s["data"]["pairs"]61 pts = sg.get("points_px") or []62 assert len(pts) == 2 * len(pairs), f"{len(pts)} != 2×{len(pairs)}"63 ids = [p["pair_id"] for p in pairs]64 assert len(set(ids)) == len(ids), "pair_id 重复"65 for pr in pairs:66 assert set(pr) >= {"pair_id", "condition_a_value", "condition_b_value"}67 assert found, "paired 变体缺 pairs 系列"68 69 @pytest.mark.parametrize("variant", ["unpaired", "bootstrap_interval", "small_effect"])70 def test_estimation_ci_present_and_valid(self, make_docs, variant):71 """§5.1 ESTIMATION_CI_MISSING:point+interval CI 必备且合法。"""72 docs, _ = make_docs("statistical", "estimation", variant=variant, seed=14)73 found = False74 for s, var, sg in _estimation_series(docs):75 d = s["data"]76 if d.get("kind") == "estimation":77 found = True78 est, lo, hi = d["estimate"], d["ci_low"], d["ci_high"]79 assert lo <= est <= hi and hi > lo, f"CI 非法 [{lo},{est},{hi}]"80 assert sg.get("bboxes_px") or sg.get("centers_px"), "CI 无渲染几何"81 assert found, f"{variant} 缺 estimation 系列"82 83 def test_truth_linestyle_none_for_observations(self, make_docs):84 """§3.5 元数据一致:观测系列 truth line_style 必须为 none(不误导下游)。"""85 docs, _ = make_docs("statistical", "estimation", variant="unpaired", seed=15)86 for s in docs["truth"]["series"]:87 if s["data"].get("kind") == "xy":88 assert s.get("style", {}).get("line") in (None, "none"), \89 f"{s.get('label')} line_style={s.get('style', {}).get('line')}"90 91 92class TestEstimationFixtures:93 """Tier B:固定种子渲染回归(yh2 §2 fixtures)。"""94 95 # v6 重钉(2026-09-16):审核批对齐 active scope 后轮转变化——96 # unpaired 在 100 行内不再有主 panel 行(Tier A 单测仍直接覆盖 unpaired/small_effect)。97 @pytest.mark.parametrize("sid,variant", [98 ("dbv01_0015", "paired"),99 ("dbv01_0046", "bootstrap_interval"),100 ])101 def test_fixture_clean(self, sid, variant, audit):102 from databasev1.adapter import build_documents, panel_view103 from databasev1.adapter.truth import build as build_truth104 from databasev1.review_batch import build_review_rows105 from databasev1.scheduler import generate_one106 from databasev1.storage.hashes import sha256_bytes, sha256_json107 108 rows = {r.sample_id: r for r in build_review_rows(100)}109 row = rows[sid]110 assert row.param_variant == variant, f"{sid} 变体漂移:{row.param_variant}"111 gcfg = {"max_attempts": 8,112 "image": {"min_long_px": 700, "max_long_px": 1800, "min_short_px": 500}}113 rendered = generate_one(row, gcfg)114 docs = build_documents(rendered, image_sha256=sha256_bytes(rendered["png_bytes"]),115 truth_sha256=sha256_json(build_truth(rendered, panel_view(rendered))))116 issues = audit(sid, docs)117 assert issues == [], f"{sid} 审计未过:{issues}"118 