Merge origin/master before analysis handoff push
This commit is contained in:
@@ -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,3 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from gfa_core.mas.agents.intent_v2 import IntentAgentV2
|
||||
from gfa_core.mas.utils.intent_parser import (
|
||||
parse_explicit_species_role_mentions,
|
||||
parse_gene_family_scope_selection,
|
||||
@@ -30,3 +33,58 @@ def test_explicit_role_parser_keeps_unassigned_mentions_separate() -> None:
|
||||
"comparativeSpecies": [],
|
||||
"unassigned": ["Ppy"],
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("species", ["Gm", "Br", "Pt", "Sb"])
|
||||
def test_scope_parser_preserves_species_supported_by_legacy_intent(species: str) -> None:
|
||||
scope = parse_gene_family_scope_selection(f"分析 {species} 的 SPL 基因家族")
|
||||
|
||||
assert scope["targetSpecies"] == [species]
|
||||
|
||||
|
||||
def test_scope_parser_accepts_explicit_role_bound_custom_species_code() -> None:
|
||||
scope = parse_gene_family_scope_selection("目标物种 Sv;参考物种 At")
|
||||
|
||||
assert scope["targetSpecies"] == ["Sv"]
|
||||
assert scope["referenceSpecies"] == ["At"]
|
||||
|
||||
|
||||
def test_scope_parser_does_not_treat_gene_family_as_species() -> None:
|
||||
scope = parse_gene_family_scope_selection("目标物种 Ft;分析 SPL 基因家族")
|
||||
|
||||
assert scope["targetSpecies"] == ["Ft"]
|
||||
|
||||
|
||||
def test_scope_parser_does_not_bind_custom_code_to_generic_goal_word() -> None:
|
||||
scope = parse_gene_family_scope_selection("分析目标 SPL 基因家族")
|
||||
|
||||
assert scope["targetSpecies"] == []
|
||||
|
||||
|
||||
def test_intent_validation_keeps_confirmed_target_and_reference_separate() -> None:
|
||||
agent = object.__new__(IntentAgentV2)
|
||||
|
||||
intent = agent._validate_intent(
|
||||
{"gene_family": "SPL", "stages": ["identify"], "params": {}},
|
||||
(
|
||||
"请按照你规划的步骤来帮我执行\n"
|
||||
"已确认上下文:基因家族 SPL;目标物种 Ft;参考物种 At。"
|
||||
),
|
||||
)
|
||||
|
||||
assert intent["target_species"] == ["Ft"]
|
||||
assert intent["reference_species"] == ["At"]
|
||||
assert intent["species"] == "Ft"
|
||||
|
||||
|
||||
def test_intent_validation_preserves_unlabelled_multi_target_request() -> None:
|
||||
agent = object.__new__(IntentAgentV2)
|
||||
|
||||
intent = agent._validate_intent(
|
||||
{"gene_family": "SPL", "stages": ["identify"], "params": {}},
|
||||
"分析 Ft 和 Os 的 SPL 基因家族",
|
||||
)
|
||||
|
||||
assert intent["target_species"] == ["Ft", "Os"]
|
||||
assert intent["reference_species"] == []
|
||||
assert intent["species"] == "Multi"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -426,6 +426,64 @@ def test_confirmed_multi_species_roles_remain_separate(monkeypatch):
|
||||
assert resolved["classification_species"] == ["Ft", "Ppy"]
|
||||
|
||||
|
||||
def test_confirmation_ack_normalizes_structured_role_drift(monkeypatch):
|
||||
main = _load_vue3_main()
|
||||
monkeypatch.setattr(
|
||||
main,
|
||||
"_confirmed_config_species_scope",
|
||||
lambda _job_id: {
|
||||
"targetSpecies": ["Ft"],
|
||||
"referenceSpecies": ["At"],
|
||||
"comparativeSpecies": [],
|
||||
},
|
||||
)
|
||||
|
||||
resolved = main._reconcile_confirmed_gene_family_species(
|
||||
"job-confirmed-ack",
|
||||
"是",
|
||||
{
|
||||
"gene_family": "SPL",
|
||||
"stages": ["identify"],
|
||||
"target_species": ["Ft", "At"],
|
||||
"params": {"targetSpecies": ["Ft", "At"]},
|
||||
},
|
||||
)
|
||||
|
||||
assert resolved["conflicts"] == []
|
||||
assert resolved["target_species"] == ["Ft"]
|
||||
assert resolved["reference_species"] == ["At"]
|
||||
|
||||
|
||||
def test_unassigned_reference_species_analysis_request_remains_blocked(monkeypatch):
|
||||
main = _load_vue3_main()
|
||||
monkeypatch.setattr(
|
||||
main,
|
||||
"_confirmed_config_species_scope",
|
||||
lambda _job_id: {
|
||||
"targetSpecies": ["Ft"],
|
||||
"referenceSpecies": ["At"],
|
||||
"comparativeSpecies": [],
|
||||
},
|
||||
)
|
||||
|
||||
resolved = main._reconcile_confirmed_gene_family_species(
|
||||
"job-unassigned-reference",
|
||||
"分析 At 的 SPL 基因家族",
|
||||
{
|
||||
"gene_family": "SPL",
|
||||
"stages": ["identify"],
|
||||
"target_species": ["At"],
|
||||
"params": {"targetSpecies": ["At"]},
|
||||
},
|
||||
)
|
||||
|
||||
assert any(
|
||||
conflict["code"] == "analysis_target_config_role_conflict"
|
||||
and conflict["species"] == "At"
|
||||
for conflict in resolved["conflicts"]
|
||||
)
|
||||
|
||||
|
||||
def test_confirmed_config_duplicate_roles_are_blocked(monkeypatch):
|
||||
main = _load_vue3_main()
|
||||
monkeypatch.setattr(
|
||||
|
||||
Reference in New Issue
Block a user