diff --git a/src/openamp_foundry/evidence/evidence_completeness_checker.py b/src/openamp_foundry/evidence/evidence_completeness_checker.py new file mode 100644 index 00000000..3370983b --- /dev/null +++ b/src/openamp_foundry/evidence/evidence_completeness_checker.py @@ -0,0 +1,303 @@ +from __future__ import annotations +from dataclasses import dataclass, field +from typing import List, Optional, Dict + + +EXPECTED_ARTIFACT_TYPES = frozenset({ + "BSP", # batch selection proposal + "WHR", # wet-lab hit record + "PCU", # post-experiment calibration update + "HCR", # hit confirmation report + "PQG", # phase Q completeness gate + "CFC", # candidate family clustering + "FNR", # family novelty report + "ATR", # audit trail report + "SRG", # scientific review readiness gate + "PEB", # preprint evidence bundle +}) + +DRY_LAB_ONLY_TYPES = frozenset({ + "BSP", "CFC", "FNR", "ATR", "SRG", "PEB", +}) + +WET_LAB_TYPES = frozenset({ + "WHR", "PCU", "HCR", "PQG", +}) + +VALID_COMPLETENESS_GRADES = frozenset({ + "A", # all 10 artifact types present + "B", # all dry-lab types + at least 2 wet-lab types + "C", # all dry-lab types present, wet-lab incomplete + "D", # some dry-lab types missing + "N/A", # completeness not assessed +}) + +VALID_REVIEW_READINESS_VERDICTS = frozenset({ + "ready_for_review", # grade A or B; can accompany external review + "dry_lab_review_only", # grade C; only dry-lab review possible + "not_ready", # grade D; major artifacts missing + "assessment_pending", # grade N/A; not yet checked +}) + + +@dataclass +class ArtifactPresenceRecord: + artifact_type: str + is_present: bool + artifact_id: Optional[str] = None + + +@dataclass +class EvidenceCompletenessChecker: + ecc_id: str + run_id: str + candidate_family_id: str + completeness_grade: str + review_readiness_verdict: str + artifact_presence_records: List[ArtifactPresenceRecord] + n_artifacts_present: int + n_artifacts_expected: int + n_dry_lab_present: int + n_wet_lab_present: int + missing_artifact_types: List[str] + dry_lab_only: bool + limitations: str + notes: str = "" + peb_id: Optional[str] = None + srg_id: Optional[str] = None + + +@dataclass +class ECCValidationResult: + valid: bool + violations: List[str] = field(default_factory=list) + + +def validate_evidence_completeness_checker(checker: EvidenceCompletenessChecker) -> ECCValidationResult: + violations = [] + + if not checker.ecc_id.startswith("ECC-"): + violations.append("ecc_id must start with 'ECC-'") + + if not checker.run_id: + violations.append("run_id is required") + + if not checker.candidate_family_id: + violations.append("candidate_family_id is required") + + if checker.candidate_family_id.startswith("TOY-"): + violations.append("TOY- candidate_family_id is not allowed in real ECC- records") + + if checker.completeness_grade not in VALID_COMPLETENESS_GRADES: + violations.append( + f"completeness_grade '{checker.completeness_grade}' must be one of {sorted(VALID_COMPLETENESS_GRADES)}" + ) + + if checker.review_readiness_verdict not in VALID_REVIEW_READINESS_VERDICTS: + violations.append( + f"review_readiness_verdict '{checker.review_readiness_verdict}' must be one of {sorted(VALID_REVIEW_READINESS_VERDICTS)}" + ) + + if checker.n_artifacts_expected != len(EXPECTED_ARTIFACT_TYPES): + violations.append( + f"n_artifacts_expected must be {len(EXPECTED_ARTIFACT_TYPES)} (all EXPECTED_ARTIFACT_TYPES), got {checker.n_artifacts_expected}" + ) + + if checker.n_artifacts_present < 0 or checker.n_artifacts_present > checker.n_artifacts_expected: + violations.append( + f"n_artifacts_present ({checker.n_artifacts_present}) must be in [0, {checker.n_artifacts_expected}]" + ) + + # Validate each presence record + present_types = set() + for i, rec in enumerate(checker.artifact_presence_records): + if rec.artifact_type not in EXPECTED_ARTIFACT_TYPES: + violations.append(f"artifact_presence_records[{i}].artifact_type '{rec.artifact_type}' not in EXPECTED_ARTIFACT_TYPES") + if rec.is_present: + present_types.add(rec.artifact_type) + + # Cross-check n_artifacts_present + computed_present = sum(1 for r in checker.artifact_presence_records if r.is_present) + if checker.n_artifacts_present != computed_present: + violations.append( + f"n_artifacts_present ({checker.n_artifacts_present}) inconsistent with records (computed {computed_present})" + ) + + # Cross-check missing types + computed_missing = sorted(EXPECTED_ARTIFACT_TYPES - present_types) + if sorted(checker.missing_artifact_types) != computed_missing: + violations.append( + f"missing_artifact_types {sorted(checker.missing_artifact_types)} does not match computed {computed_missing}" + ) + + # Cross-check dry_lab count + computed_dry = sum(1 for r in checker.artifact_presence_records if r.is_present and r.artifact_type in DRY_LAB_ONLY_TYPES) + if checker.n_dry_lab_present != computed_dry: + violations.append( + f"n_dry_lab_present ({checker.n_dry_lab_present}) inconsistent with records (computed {computed_dry})" + ) + + # Cross-check wet_lab count + computed_wet = sum(1 for r in checker.artifact_presence_records if r.is_present and r.artifact_type in WET_LAB_TYPES) + if checker.n_wet_lab_present != computed_wet: + violations.append( + f"n_wet_lab_present ({checker.n_wet_lab_present}) inconsistent with records (computed {computed_wet})" + ) + + # Grade A requires all artifacts + if checker.completeness_grade == "A" and checker.n_artifacts_present != len(EXPECTED_ARTIFACT_TYPES): + violations.append( + f"completeness_grade='A' requires all {len(EXPECTED_ARTIFACT_TYPES)} artifacts present" + ) + + # Grade B requires all dry-lab + at least 2 wet-lab + if checker.completeness_grade == "B": + if checker.n_dry_lab_present != len(DRY_LAB_ONLY_TYPES): + violations.append( + f"completeness_grade='B' requires all {len(DRY_LAB_ONLY_TYPES)} dry-lab artifacts present" + ) + if checker.n_wet_lab_present < 2: + violations.append( + "completeness_grade='B' requires at least 2 wet-lab artifacts present" + ) + + # Grade C requires all dry-lab types + if checker.completeness_grade == "C" and checker.n_dry_lab_present != len(DRY_LAB_ONLY_TYPES): + violations.append( + f"completeness_grade='C' requires all {len(DRY_LAB_ONLY_TYPES)} dry-lab artifacts present" + ) + + # Grade D must have some missing dry-lab types + if checker.completeness_grade == "D" and checker.n_dry_lab_present == len(DRY_LAB_ONLY_TYPES) and checker.n_wet_lab_present >= 2: + violations.append( + "completeness_grade='D' should not have all dry-lab and 2+ wet-lab artifacts present" + ) + + # Verdict consistency + if checker.completeness_grade == "A" and checker.review_readiness_verdict not in ("ready_for_review",): + violations.append( + "completeness_grade='A' requires review_readiness_verdict='ready_for_review'" + ) + + if checker.completeness_grade == "D" and checker.review_readiness_verdict not in ("not_ready",): + violations.append( + "completeness_grade='D' requires review_readiness_verdict='not_ready'" + ) + + if checker.completeness_grade == "N/A" and checker.review_readiness_verdict not in ("assessment_pending",): + violations.append( + "completeness_grade='N/A' requires review_readiness_verdict='assessment_pending'" + ) + + # ID prefix checks + if checker.peb_id is not None and not checker.peb_id.startswith("PEB-"): + violations.append("peb_id must start with 'PEB-' when provided") + + if checker.srg_id is not None and not checker.srg_id.startswith("SRG-"): + violations.append("srg_id must start with 'SRG-' when provided") + + if not checker.dry_lab_only: + violations.append("dry_lab_only must be True for ECC- records") + + if not checker.limitations or len(checker.limitations.strip()) < 10: + violations.append("limitations must be a non-empty string (at least 10 characters)") + + return ECCValidationResult(valid=len(violations) == 0, violations=violations) + + +def _compute_grade(n_dry: int, n_wet: int, n_total: int) -> str: + if n_total == len(EXPECTED_ARTIFACT_TYPES): + return "A" + elif n_dry == len(DRY_LAB_ONLY_TYPES) and n_wet >= 2: + return "B" + elif n_dry == len(DRY_LAB_ONLY_TYPES): + return "C" + else: + return "D" + + +def _compute_verdict(grade: str) -> str: + mapping = { + "A": "ready_for_review", + "B": "ready_for_review", + "C": "dry_lab_review_only", + "D": "not_ready", + "N/A": "assessment_pending", + } + return mapping[grade] + + +def build_evidence_completeness_checker( + ecc_id: str, + run_id: str, + candidate_family_id: str, + present_artifact_ids: Dict[str, str], + limitations: str, + notes: str = "", + peb_id: Optional[str] = None, + srg_id: Optional[str] = None, +) -> EvidenceCompletenessChecker: + records = [] + for atype in sorted(EXPECTED_ARTIFACT_TYPES): + artifact_id = present_artifact_ids.get(atype) + records.append(ArtifactPresenceRecord( + artifact_type=atype, + is_present=artifact_id is not None, + artifact_id=artifact_id, + )) + + n_present = sum(1 for r in records if r.is_present) + present_types = {r.artifact_type for r in records if r.is_present} + missing = sorted(EXPECTED_ARTIFACT_TYPES - present_types) + n_dry = sum(1 for r in records if r.is_present and r.artifact_type in DRY_LAB_ONLY_TYPES) + n_wet = sum(1 for r in records if r.is_present and r.artifact_type in WET_LAB_TYPES) + grade = _compute_grade(n_dry, n_wet, n_present) + verdict = _compute_verdict(grade) + + checker = EvidenceCompletenessChecker( + ecc_id=ecc_id, + run_id=run_id, + candidate_family_id=candidate_family_id, + completeness_grade=grade, + review_readiness_verdict=verdict, + artifact_presence_records=records, + n_artifacts_present=n_present, + n_artifacts_expected=len(EXPECTED_ARTIFACT_TYPES), + n_dry_lab_present=n_dry, + n_wet_lab_present=n_wet, + missing_artifact_types=missing, + dry_lab_only=True, + limitations=limitations, + notes=notes, + peb_id=peb_id, + srg_id=srg_id, + ) + result = validate_evidence_completeness_checker(checker) + if not result.valid: + raise ValueError(f"Invalid ECC: {result.violations}") + return checker + + +def format_evidence_completeness_checker(checker: EvidenceCompletenessChecker) -> str: + lines = [ + f"Evidence Completeness Checker — {checker.ecc_id}", + f"Run: {checker.run_id} | Family: {checker.candidate_family_id}", + f"Completeness Grade: {checker.completeness_grade} | Review Readiness: {checker.review_readiness_verdict}", + f"Artifacts Present: {checker.n_artifacts_present}/{checker.n_artifacts_expected}", + f" Dry-lab: {checker.n_dry_lab_present}/{len(DRY_LAB_ONLY_TYPES)} | Wet-lab: {checker.n_wet_lab_present}/{len(WET_LAB_TYPES)}", + ] + if checker.missing_artifact_types: + lines.append(f"Missing: {', '.join(checker.missing_artifact_types)}") + for rec in checker.artifact_presence_records: + status = "PRESENT" if rec.is_present else "MISSING" + id_str = f" ({rec.artifact_id})" if rec.artifact_id else "" + lines.append(f" [{rec.artifact_type}] {status}{id_str}") + if checker.peb_id: + lines.append(f"PEB Link: {checker.peb_id}") + if checker.srg_id: + lines.append(f"SRG Link: {checker.srg_id}") + lines.append(f"Limitations: {checker.limitations}") + if checker.notes: + lines.append(f"Notes: {checker.notes}") + lines.append("dry_lab_only: True") + return "\n".join(lines) diff --git a/tests/evidence/test_evidence_completeness_checker.py b/tests/evidence/test_evidence_completeness_checker.py new file mode 100644 index 00000000..cd30877e --- /dev/null +++ b/tests/evidence/test_evidence_completeness_checker.py @@ -0,0 +1,218 @@ +"""Tests for evidence_completeness_checker module — Phase S S3 (ECC-).""" +import pytest +from openamp_foundry.evidence.evidence_completeness_checker import ( + EvidenceCompletenessChecker, ArtifactPresenceRecord, + EXPECTED_ARTIFACT_TYPES, VALID_COMPLETENESS_GRADES, + VALID_REVIEW_READINESS_VERDICTS, DRY_LAB_ONLY_TYPES, WET_LAB_TYPES, + validate_evidence_completeness_checker, + build_evidence_completeness_checker, + format_evidence_completeness_checker, +) + +ALL_IDS = {"BSP":"BSP-001","WHR":"WHR-001","PCU":"PCU-001","HCR":"HCR-001","PQG":"PQG-001", + "CFC":"CFC-001","FNR":"FNR-001","ATR":"ATR-001","SRG":"SRG-001","PEB":"PEB-001"} +DRY_IDS = {k:v for k,v in ALL_IDS.items() if k in DRY_LAB_ONLY_TYPES} +WET_IDS = {k:v for k,v in ALL_IDS.items() if k in WET_LAB_TYPES} + + +def _all_records(present=None): + if present is None: present = set(EXPECTED_ARTIFACT_TYPES) + return [ArtifactPresenceRecord(t, t in present, ALL_IDS.get(t) if t in present else None) + for t in sorted(EXPECTED_ARTIFACT_TYPES)] + + +def _make(**kw): + records = _all_records() + defaults = dict( + ecc_id="ECC-001", run_id="RUN-001", candidate_family_id="FAM-001", + completeness_grade="A", review_readiness_verdict="ready_for_review", + artifact_presence_records=records, n_artifacts_present=10, + n_artifacts_expected=10, n_dry_lab_present=6, n_wet_lab_present=4, + missing_artifact_types=[], dry_lab_only=True, + limitations="Dry-lab assessment only. Wet-lab not validated.", + notes="", peb_id=None, srg_id=None, + ) + defaults.update(kw) + return EvidenceCompletenessChecker(**defaults) + + +class TestECCIDValidation: + def test_valid_ecc_id(self): assert validate_evidence_completeness_checker(_make(ecc_id="ECC-001")).valid + def test_invalid_ecc_id(self): + r = validate_evidence_completeness_checker(_make(ecc_id="001")) + assert not r.valid and any("ecc_id" in v for v in r.violations) + def test_wrong_prefix(self): assert not validate_evidence_completeness_checker(_make(ecc_id="SEG-001")).valid + def test_toy_family_blocked(self): + r = validate_evidence_completeness_checker(_make(candidate_family_id="TOY-001")) + assert not r.valid and any("TOY-" in v for v in r.violations) + def test_run_id_required(self): + r = validate_evidence_completeness_checker(_make(run_id="")) + assert not r.valid and any("run_id" in v for v in r.violations) + def test_family_id_required(self): assert not validate_evidence_completeness_checker(_make(candidate_family_id="")).valid + def test_peb_id_prefix_required(self): + r = validate_evidence_completeness_checker(_make(peb_id="001")) + assert not r.valid and any("peb_id" in v for v in r.violations) + def test_srg_id_prefix_required(self): + r = validate_evidence_completeness_checker(_make(srg_id="001")) + assert not r.valid and any("srg_id" in v for v in r.violations) + def test_valid_optional_ids(self): assert validate_evidence_completeness_checker(_make(peb_id="PEB-001", srg_id="SRG-001")).valid + + +class TestCompletenessGradeValidation: + def test_grade_A_all_present(self): assert validate_evidence_completeness_checker(_make(completeness_grade="A")).valid + def test_grade_A_requires_all_artifacts(self): + records = _all_records(set(EXPECTED_ARTIFACT_TYPES) - {"WHR"}) + r = validate_evidence_completeness_checker(_make( + completeness_grade="A", artifact_presence_records=records, + n_artifacts_present=9, n_wet_lab_present=3, missing_artifact_types=["WHR"] + )) + assert not r.valid and any("grade='A'" in v or "grade=\\'A\\'" in v or "'A'" in v for v in r.violations) + def test_grade_B_all_dry_plus_2_wet(self): + present = set(DRY_LAB_ONLY_TYPES) | {"WHR","PCU"} + records = _all_records(present) + r = validate_evidence_completeness_checker(_make( + completeness_grade="B", review_readiness_verdict="ready_for_review", + artifact_presence_records=records, n_artifacts_present=8, + n_dry_lab_present=6, n_wet_lab_present=2, missing_artifact_types=["HCR","PQG"] + )) + assert r.valid + def test_grade_B_requires_all_dry(self): + present = set(DRY_LAB_ONLY_TYPES) - {"CFC"} | {"WHR","PCU"} + records = _all_records(present) + r = validate_evidence_completeness_checker(_make( + completeness_grade="B", review_readiness_verdict="ready_for_review", + artifact_presence_records=records, n_artifacts_present=7, + n_dry_lab_present=5, n_wet_lab_present=2, missing_artifact_types=["CFC","HCR","PQG"] + )) + assert not r.valid and any("'B'" in v for v in r.violations) + def test_grade_C_all_dry_no_wet(self): + records = _all_records(set(DRY_LAB_ONLY_TYPES)) + r = validate_evidence_completeness_checker(_make( + completeness_grade="C", review_readiness_verdict="dry_lab_review_only", + artifact_presence_records=records, n_artifacts_present=6, + n_dry_lab_present=6, n_wet_lab_present=0, missing_artifact_types=["HCR","PCU","PQG","WHR"] + )) + assert r.valid + def test_grade_D_missing_dry(self): + present = set(DRY_LAB_ONLY_TYPES) - {"CFC","FNR"} + records = _all_records(present) + r = validate_evidence_completeness_checker(_make( + completeness_grade="D", review_readiness_verdict="not_ready", + artifact_presence_records=records, n_artifacts_present=4, + n_dry_lab_present=4, n_wet_lab_present=0, missing_artifact_types=["CFC","FNR","HCR","PCU","PQG","WHR"] + )) + assert r.valid + def test_invalid_grade(self): + r = validate_evidence_completeness_checker(_make(completeness_grade="E")) + assert not r.valid and any("completeness_grade" in v for v in r.violations) + def test_grades_count(self): assert len(VALID_COMPLETENESS_GRADES) == 5 + + +class TestVerdictConsistency: + def test_grade_A_requires_ready_verdict(self): + r = validate_evidence_completeness_checker(_make(completeness_grade="A", review_readiness_verdict="not_ready")) + assert not r.valid and any("'A'" in v and "ready_for_review" in v for v in r.violations) + def test_grade_D_requires_not_ready_verdict(self): + present = set(DRY_LAB_ONLY_TYPES) - {"CFC"} + records = _all_records(present) + r = validate_evidence_completeness_checker(_make( + completeness_grade="D", review_readiness_verdict="ready_for_review", + artifact_presence_records=records, n_artifacts_present=5, + n_dry_lab_present=5, n_wet_lab_present=0, missing_artifact_types=["CFC","HCR","PCU","PQG","WHR"] + )) + assert not r.valid and any("'D'" in v and "not_ready" in v for v in r.violations) + def test_grade_na_requires_assessment_pending(self): + r = validate_evidence_completeness_checker(_make( + completeness_grade="N/A", review_readiness_verdict="ready_for_review", + n_artifacts_present=10, n_dry_lab_present=6, n_wet_lab_present=4, missing_artifact_types=[] + )) + assert not r.valid + def test_verdicts_count(self): assert len(VALID_REVIEW_READINESS_VERDICTS) == 4 + def test_invalid_verdict(self): + r = validate_evidence_completeness_checker(_make(review_readiness_verdict="partial_ready")) + assert not r.valid and any("review_readiness_verdict" in v for v in r.violations) + + +class TestCountConsistency: + def test_n_expected_must_be_10(self): + r = validate_evidence_completeness_checker(_make(n_artifacts_expected=9)) + assert not r.valid and any("n_artifacts_expected" in v for v in r.violations) + def test_n_present_inconsistency_rejected(self): + r = validate_evidence_completeness_checker(_make(n_artifacts_present=5)) + assert not r.valid and any("n_artifacts_present" in v and "inconsistent" in v for v in r.violations) + def test_n_dry_inconsistency_rejected(self): + r = validate_evidence_completeness_checker(_make(n_dry_lab_present=3)) + assert not r.valid and any("n_dry_lab_present" in v for v in r.violations) + def test_n_wet_inconsistency_rejected(self): + r = validate_evidence_completeness_checker(_make(n_wet_lab_present=2)) + assert not r.valid and any("n_wet_lab_present" in v for v in r.violations) + def test_missing_types_inconsistency_rejected(self): + r = validate_evidence_completeness_checker(_make(missing_artifact_types=["WHR"])) + assert not r.valid and any("missing_artifact_types" in v for v in r.violations) + def test_expected_artifact_types_count(self): assert len(EXPECTED_ARTIFACT_TYPES) == 10 + def test_dry_lab_types_count(self): assert len(DRY_LAB_ONLY_TYPES) == 6 + def test_wet_lab_types_count(self): assert len(WET_LAB_TYPES) == 4 + + +class TestArtifactRecordValidation: + def test_invalid_artifact_type_in_records(self): + records = _all_records() + records[0] = ArtifactPresenceRecord("XYZ", True, "XYZ-001") + r = validate_evidence_completeness_checker(_make(artifact_presence_records=records)) + assert not r.valid and any("XYZ" in v for v in r.violations) + + +class TestDryLabAndLimitations: + def test_dry_lab_false_rejected(self): + r = validate_evidence_completeness_checker(_make(dry_lab_only=False)) + assert not r.valid and any("dry_lab_only" in v for v in r.violations) + def test_limitations_required(self): + r = validate_evidence_completeness_checker(_make(limitations="")) + assert not r.valid and any("limitations" in v for v in r.violations) + def test_limitations_short_rejected(self): assert not validate_evidence_completeness_checker(_make(limitations="Too short")).valid + def test_limitations_sufficient_valid(self): assert validate_evidence_completeness_checker(_make(limitations="Dry-lab assessment only. Wet-lab not validated.")).valid + + +class TestBuildAndFormat: + def test_build_grade_A_all_present(self): + c = build_evidence_completeness_checker("ECC-001","RUN-001","FAM-001", + ALL_IDS, "Dry-lab assessment only. Wet-lab not validated.") + assert c.completeness_grade == "A" and c.review_readiness_verdict == "ready_for_review" + assert c.n_artifacts_present == 10 and c.dry_lab_only is True + + def test_build_grade_C_dry_only(self): + c = build_evidence_completeness_checker("ECC-001","RUN-001","FAM-001", + DRY_IDS, "Dry-lab assessment only. Wet-lab not validated.") + assert c.completeness_grade == "C" and c.review_readiness_verdict == "dry_lab_review_only" + assert c.n_dry_lab_present == 6 and c.n_wet_lab_present == 0 + + def test_build_grade_D_partial_dry(self): + partial = {k:v for k,v in DRY_IDS.items() if k not in ("CFC","FNR")} + c = build_evidence_completeness_checker("ECC-001","RUN-001","FAM-001", + partial, "Dry-lab assessment only. Wet-lab not validated.") + assert c.completeness_grade == "D" and c.review_readiness_verdict == "not_ready" + + def test_build_raises_invalid(self): + with pytest.raises(ValueError): + build_evidence_completeness_checker("INVALID","RUN-001","FAM-001", + ALL_IDS, "Dry-lab assessment only. Wet-lab not validated.") + + def test_format_includes_ecc_id(self): assert "ECC-001" in format_evidence_completeness_checker(_make()) + def test_format_includes_grade(self): assert "A" in format_evidence_completeness_checker(_make()) + def test_format_includes_verdict(self): assert "ready_for_review" in format_evidence_completeness_checker(_make()) + def test_format_includes_artifact_counts(self): assert "10/10" in format_evidence_completeness_checker(_make()) + def test_format_includes_peb_link(self): assert "PEB-001" in format_evidence_completeness_checker(_make(peb_id="PEB-001")) + def test_format_is_multiline(self): assert "\n" in format_evidence_completeness_checker(_make()) + def test_valid_checker_passes(self): + r = validate_evidence_completeness_checker(_make()) + assert r.valid and r.violations == [] + def test_multiple_violations(self): + r = validate_evidence_completeness_checker(_make(ecc_id="INVALID", completeness_grade="Z")) + assert not r.valid and len(r.violations) >= 2 + def test_build_grade_B(self): + partial = dict(**DRY_IDS) + partial["WHR"] = "WHR-001" + partial["PCU"] = "PCU-001" + c = build_evidence_completeness_checker("ECC-001","RUN-001","FAM-001", + partial, "Dry-lab assessment only. Wet-lab not validated.") + assert c.completeness_grade == "B" and c.n_wet_lab_present == 2 diff --git a/tests/test_test_count_regression.py b/tests/test_test_count_regression.py index d96126da..6e210532 100644 --- a/tests/test_test_count_regression.py +++ b/tests/test_test_count_regression.py @@ -3,7 +3,7 @@ import subprocess import math -BASELINE = 9341 +BASELINE = 9404 def test_test_count_regression():