fix: allow reordered subfamily member records
This commit is contained in:
@@ -118,7 +118,7 @@ def _atomic_promote_pair(curated_id: Path, curated_pep: Path, final_id: Path, fi
|
||||
tmp_pep.write_text(curated_pep.read_text(encoding="utf-8", errors="ignore"), encoding="utf-8")
|
||||
tmp_ids = _read_id_list(tmp_id)
|
||||
tmp_fasta_ids = _read_fasta_ids(tmp_pep)
|
||||
if tmp_ids != tmp_fasta_ids:
|
||||
if sorted(tmp_ids) != sorted(tmp_fasta_ids):
|
||||
tmp_id.unlink(missing_ok=True)
|
||||
tmp_pep.unlink(missing_ok=True)
|
||||
raise RuntimeError(f"Temporary final member validation failed for {final_id.stem}")
|
||||
@@ -160,7 +160,7 @@ def _promote_curated_members_to_final(final_dir: Path, prefixes: Iterable[str],
|
||||
if not curated_ids:
|
||||
raise ValueError(f"Curated member ID file is empty: {curated_id}")
|
||||
fasta_ids = _read_fasta_ids(curated_pep)
|
||||
if curated_ids != fasta_ids:
|
||||
if sorted(curated_ids) != sorted(fasta_ids):
|
||||
raise ValueError(
|
||||
f"Curated member ID/FASTA mismatch for {member_prefix}: "
|
||||
f"{len(curated_ids)} IDs vs {len(fasta_ids)} FASTA records"
|
||||
@@ -179,7 +179,7 @@ def _promote_curated_members_to_final(final_dir: Path, prefixes: Iterable[str],
|
||||
|
||||
promoted_ids = _read_id_list(final_id)
|
||||
promoted_fasta_ids = _read_fasta_ids(final_pep)
|
||||
if promoted_ids != curated_ids or promoted_fasta_ids != curated_ids:
|
||||
if sorted(promoted_ids) != sorted(curated_ids) or sorted(promoted_fasta_ids) != sorted(curated_ids):
|
||||
raise RuntimeError(f"Post-promotion final member validation failed for {member_prefix}")
|
||||
promotions.append({
|
||||
"prefix": member_prefix,
|
||||
|
||||
@@ -259,7 +259,7 @@ def _build_result(
|
||||
|
||||
id_list = _read_ids(path_id) if path_id else []
|
||||
pep_ids = _read_fasta_ids(path_pep) if path_pep else []
|
||||
if path_id and path_pep and id_list != pep_ids:
|
||||
if path_id and path_pep and sorted(id_list) != sorted(pep_ids):
|
||||
raise MemberResolutionError(f"Member ID/FASTA mismatch: {path_id} vs {path_pep}")
|
||||
member_count = len(id_list or pep_ids)
|
||||
warnings = []
|
||||
|
||||
@@ -116,6 +116,49 @@ def test_pre_confirm_prefers_curated_before_identified(tmp_path: Path):
|
||||
assert result.member_count == 2
|
||||
|
||||
|
||||
def test_pre_confirm_accepts_same_curated_members_in_different_order(tmp_path: Path):
|
||||
ws = tmp_path / "workspace" / "job1"
|
||||
final_dir = ws / "final"
|
||||
input_dir = tmp_path / "input" / "job1"
|
||||
prefix = "Target.Sl.GRAS"
|
||||
|
||||
_write(final_dir / f"{prefix}.curated_members.id", "gene1\ngene2\n")
|
||||
_write(final_dir / f"{prefix}.curated_members.pep.fa", _pep(["gene2", "gene1"]))
|
||||
|
||||
result = resolve_effective_members(
|
||||
workspace=ws,
|
||||
final_dir=final_dir,
|
||||
input_dir=input_dir,
|
||||
prefix=prefix,
|
||||
phase="pre_confirm",
|
||||
require="pair",
|
||||
)
|
||||
|
||||
assert result.member_count == 2
|
||||
assert result.path_id == final_dir / f"{prefix}.curated_members.id"
|
||||
assert result.path_pep == final_dir / f"{prefix}.curated_members.pep.fa"
|
||||
|
||||
|
||||
def test_pre_confirm_rejects_different_curated_members_with_equal_counts(tmp_path: Path):
|
||||
ws = tmp_path / "workspace" / "job1"
|
||||
final_dir = ws / "final"
|
||||
input_dir = tmp_path / "input" / "job1"
|
||||
prefix = "Target.Sl.GRAS"
|
||||
|
||||
_write(final_dir / f"{prefix}.curated_members.id", "gene1\ngene2\n")
|
||||
_write(final_dir / f"{prefix}.curated_members.pep.fa", _pep(["gene1", "gene3"]))
|
||||
|
||||
with pytest.raises(MemberResolutionError, match="ID/FASTA mismatch"):
|
||||
resolve_effective_members(
|
||||
workspace=ws,
|
||||
final_dir=final_dir,
|
||||
input_dir=input_dir,
|
||||
prefix=prefix,
|
||||
phase="pre_confirm",
|
||||
require="pair",
|
||||
)
|
||||
|
||||
|
||||
def test_subfamily_evidence_required_rejects_unreviewed_identified_members(tmp_path: Path):
|
||||
ws = tmp_path / "workspace" / "job1"
|
||||
final_dir = ws / "final"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from apps.cli.run_subfamily_confirm import _promote_curated_members_to_final
|
||||
|
||||
|
||||
@@ -25,3 +27,37 @@ def test_promote_curated_members_to_final_records_checksums(tmp_path: Path):
|
||||
assert promotion["source_curated_id_checksum"]
|
||||
assert (final_dir / f"{prefix}.final_members.id").read_text(encoding="utf-8").splitlines() == curated_ids
|
||||
assert (final_dir / f"{prefix}.final_members.pep.fa").exists()
|
||||
|
||||
|
||||
def test_promote_curated_members_accepts_same_members_in_different_order(tmp_path: Path):
|
||||
final_dir = tmp_path / "final"
|
||||
final_dir.mkdir()
|
||||
prefix = "Target.Sl.GRAS"
|
||||
(final_dir / f"{prefix}.curated_members.id").write_text("gene1\ngene2\n", encoding="utf-8")
|
||||
curated_pep = ">gene2\nMT\n>gene1\nMA\n"
|
||||
(final_dir / f"{prefix}.curated_members.pep.fa").write_text(curated_pep, encoding="utf-8")
|
||||
labels = {
|
||||
"gene1": {"confirmed_subfamily": "GRAS-a"},
|
||||
"gene2": {"confirmed_subfamily": "GRAS-b"},
|
||||
}
|
||||
|
||||
promotions = _promote_curated_members_to_final(final_dir, [prefix], labels)
|
||||
|
||||
assert promotions[0]["count"] == 2
|
||||
assert (final_dir / f"{prefix}.final_members.id").read_text(encoding="utf-8") == "gene1\ngene2\n"
|
||||
assert (final_dir / f"{prefix}.final_members.pep.fa").read_text(encoding="utf-8") == curated_pep
|
||||
|
||||
|
||||
def test_promote_curated_members_rejects_different_members_with_equal_counts(tmp_path: Path):
|
||||
final_dir = tmp_path / "final"
|
||||
final_dir.mkdir()
|
||||
prefix = "Target.Sl.GRAS"
|
||||
(final_dir / f"{prefix}.curated_members.id").write_text("gene1\ngene2\n", encoding="utf-8")
|
||||
(final_dir / f"{prefix}.curated_members.pep.fa").write_text(">gene1\nMA\n>gene3\nMT\n", encoding="utf-8")
|
||||
labels = {
|
||||
"gene1": {"confirmed_subfamily": "GRAS-a"},
|
||||
"gene2": {"confirmed_subfamily": "GRAS-b"},
|
||||
}
|
||||
|
||||
with pytest.raises(ValueError, match="ID/FASTA mismatch"):
|
||||
_promote_curated_members_to_final(final_dir, [prefix], labels)
|
||||
|
||||
Reference in New Issue
Block a user