diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1723510..8473da7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,7 +70,7 @@ jobs: id: drafts uses: ./ with: - files: examples/drafts/*.yaml + files: examples/drafts/*claim.yaml profile: literature-claim format: draft fail-on: rejected diff --git a/CHANGELOG.md b/CHANGELOG.md index c0f7186..4780097 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +- Add admitted and rejected `dataset-label` draft examples, covered by draft and + installed-wheel checks. + ## 0.7.0 — Expert review, quality checks, community and documentation site - Add `bioevidence review` (`check`, `agreement`, `adjudication-sheet`, `score`, `freeze`) diff --git a/examples/drafts/dataset_label_reference.yaml b/examples/drafts/dataset_label_reference.yaml new file mode 100644 index 0000000..1925d2b --- /dev/null +++ b/examples/drafts/dataset_label_reference.yaml @@ -0,0 +1,35 @@ +# Synthetic dataset-label draft admitted for reference annotation. +# Build: bioevidence build examples/drafts/dataset_label_reference.yaml --output record.json +# Validate: bioevidence validate record.json --profile dataset-label (exit 0: admitted) +profile: dataset-label +uses: [reference_annotation] + +statement: + subject: {id: "SYN:SAMPLE_A", label: Synthetic sample A, type: sample} + predicate: has_label + object: {id: "SYN:LABEL_A", label: Synthetic class A, type: class_label} + scope: ["cohort:synthetic"] + +sources: + - id: labels + title: Synthetic label table; not real biological evidence + type: dataset_snapshot + uri: https://example.org/synthetic-labels + version: synthetic-v1 + retrieved_at: "2026-09-25T00:00:00Z" + sha256: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +evidence: + - source: labels + locator: row-1-label + text: Synthetic sample A has synthetic class A. + type: curated_label + method: manual_curation + scope: ["cohort:synthetic"] + + - source: labels + locator: row-1-link + text: Synthetic sample A is linked to row 1 of the label table. + type: sample_link + method: deterministic_parser + scope: ["cohort:synthetic"] diff --git a/examples/drafts/dataset_label_training_unreviewed.yaml b/examples/drafts/dataset_label_training_unreviewed.yaml new file mode 100644 index 0000000..f5a81d9 --- /dev/null +++ b/examples/drafts/dataset_label_training_unreviewed.yaml @@ -0,0 +1,35 @@ +# Synthetic dataset-label draft rejected for training use without human acceptance. +# Build: bioevidence build examples/drafts/dataset_label_training_unreviewed.yaml --output record.json +# Validate: bioevidence validate record.json --profile dataset-label (exit 1: rejected) +profile: dataset-label +uses: [training_data] + +statement: + subject: {id: "SYN:SAMPLE_A", label: Synthetic sample A, type: sample} + predicate: has_label + object: {id: "SYN:LABEL_A", label: Synthetic class A, type: class_label} + scope: ["cohort:synthetic"] + +sources: + - id: labels + title: Synthetic label table; not real biological evidence + type: dataset_snapshot + uri: https://example.org/synthetic-labels + version: synthetic-v1 + retrieved_at: "2026-09-25T00:00:00Z" + sha256: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +evidence: + - source: labels + locator: row-1-label + text: Synthetic sample A has synthetic class A. + type: curated_label + method: manual_curation + scope: ["cohort:synthetic"] + + - source: labels + locator: row-1-link + text: Synthetic sample A is linked to row 1 of the label table. + type: sample_link + method: deterministic_parser + scope: ["cohort:synthetic"] diff --git a/tests/test_draft.py b/tests/test_draft.py index 1a5f4af..fa6346b 100644 --- a/tests/test_draft.py +++ b/tests/test_draft.py @@ -39,6 +39,19 @@ def test_reviewed_draft_is_admitted_for_both_uses(): assert overall == "admitted" and uses == {"research_summary": "admitted", "knowledge_base": "admitted"} +def test_dataset_label_reference_draft_is_admitted(): + overall, uses, _ = statuses(draft("dataset_label_reference.yaml")) + assert overall == "admitted" + assert uses == {"reference_annotation": "admitted"} + + +def test_dataset_label_training_draft_requires_human_acceptance(): + overall, uses, report = statuses(draft("dataset_label_training_unreviewed.yaml")) + assert overall == "rejected" + assert uses == {"training_data": "rejected"} + assert {f["rule_id"] for f in report["findings"]} == {"BEV010"} + + def test_build_expands_structure_and_hashes_local_file(): record = build(draft()) source, = record["source_artifacts"] @@ -138,9 +151,17 @@ def test_draft_schema_is_valid_json_schema(profile): assert schema["properties"]["profile"] == {"const": profile} -@pytest.mark.parametrize("name", ["llm_claim.yaml", "reviewed_claim.yaml"]) -def test_example_drafts_match_their_profile_schema(name): - Draft202012Validator(draft_json_schema("literature-claim")).validate(draft(name)) +@pytest.mark.parametrize( + "name,profile", + [ + ("llm_claim.yaml", "literature-claim"), + ("reviewed_claim.yaml", "literature-claim"), + ("dataset_label_reference.yaml", "dataset-label"), + ("dataset_label_training_unreviewed.yaml", "dataset-label"), + ], +) +def test_example_drafts_match_their_profile_schema(name, profile): + Draft202012Validator(draft_json_schema(profile)).validate(draft(name)) def test_draft_schema_turns_profile_allowlists_into_enums(): diff --git a/tests/test_github_action.py b/tests/test_github_action.py index c0bdfd2..54374af 100644 --- a/tests/test_github_action.py +++ b/tests/test_github_action.py @@ -22,7 +22,7 @@ def run(monkeypatch, tmp_path, *args): @pytest.mark.parametrize("fail_on,expected", [("review", 1), ("rejected", 0)]) def test_drafts_fail_on_review_only_when_asked(monkeypatch, tmp_path, fail_on, expected): - code, summary, counts = run(monkeypatch, tmp_path, "--files", "examples/drafts/*.yaml", + code, summary, counts = run(monkeypatch, tmp_path, "--files", "examples/drafts/*claim.yaml", "--profile", "literature-claim", "--format", "draft", "--fail-on", fail_on) assert code == expected assert counts == {"admitted": 1, "review_required": 1, "rejected": 0, "error": 0} diff --git a/tools/check_distribution.py b/tools/check_distribution.py index c938124..46af654 100644 --- a/tools/check_distribution.py +++ b/tools/check_distribution.py @@ -44,10 +44,15 @@ def run(*args, expected=0): run("generate-schema", "--output", output) assert "BioEvidenceRecord" in json.loads(output.read_text(encoding="utf-8"))["$defs"] with tempfile.TemporaryDirectory() as directory: - for draft, expected in [("llm_claim.yaml", 2), ("reviewed_claim.yaml", 0)]: + for draft, profile, expected in [ + ("llm_claim.yaml", "literature-claim", 2), + ("reviewed_claim.yaml", "literature-claim", 0), + ("dataset_label_reference.yaml", "dataset-label", 0), + ("dataset_label_training_unreviewed.yaml", "dataset-label", 1), + ]: output = Path(directory) / (draft + ".json") run("build", root / "examples/drafts" / draft, "--output", output) - run("validate", output, "--profile", "literature-claim", expected=expected) + run("validate", output, "--profile", profile, expected=expected) schema = json.loads(run("draft-schema", "--profile", "literature-claim").stdout) assert schema["properties"]["profile"] == {"const": "literature-claim"} record = json.loads((root / "examples/general/curated_assertion.json").read_text(encoding="utf-8"))