from __future__ import annotations import asyncio import hashlib import json from dataclasses import dataclass from pathlib import Path from apps.web.pipeline_backend import _population_cli_command from apps.web.vue3.main import ExecuteRequest from gfa_core.mas.agents.base import AgentMessage, AgentState from gfa_core.mas.agents.intent_v2 import IntentAgentV2 from gfa_core.mas.agents.planning_v2 import PlanningAgentV2 from gfa_core.mas.core.fast_intent import fast_intent_recognition from gfa_core.mas.stage_catalog import EXECUTABLE_STAGES from gfa_core.mas.tools.stage_tools import StageTool, StageToolExecutor, StageToolRegistry from gfa_core.mas.utils.intent_parser import parse_intent_from_query, resolve_population_intent from gfa_core.project_paths import get_conda_env_bin, get_conda_env_rscript, get_configured_path POP_STAGES = ["pop_pca", "pop_phylogeny", "pop_structure", "pop_ld_decay", "pop_snp_density"] POPULATION_REQUEST = ( "使用 input/job_1784815396884_5db7e7/population_11samples.snp.vcf.gz " "和分组表 input/job_1784815396884_5db7e7/ population_11samples.groups.tsv," "执行完整的群体遗传分析。" ) POPULATION_VCF = "input/job_1784815396884_5db7e7/population_11samples.snp.vcf.gz" POPULATION_GROUPS = "input/job_1784815396884_5db7e7/population_11samples.groups.tsv" def _write_complete_pca_manifest(workspace: Path, vcf: Path) -> tuple[str, str]: final = workspace / "final" final.mkdir(parents=True, exist_ok=True) artifact = final / "cohort.pop_pca.test.tsv" artifact.write_text("sample\tPC1\nS1\t0.0\n", encoding="utf-8") digest = hashlib.sha256(vcf.read_bytes()).hexdigest() genotype = f"genotype:{digest}" analysis = "population-analysis:" + "b" * 64 proof = { "artifact": "pca_scores", "role": "pca_scores", "path": str(artifact.relative_to(workspace)), "full_path": str(artifact.resolve()), "type": "text/tab-separated-values", "size": artifact.stat().st_size, "sha256": hashlib.sha256(artifact.read_bytes()).hexdigest(), } roles = { "pca_scores", "pca_eigenvalues", "pca_variance_table", "pca_pruning_markers", "pca_pc1_pc2_plot", "pca_scree_plot", "pca_tassel_covariate", "report", "environment", } manifest = { "manifest_version": "1.0", "stage": "pop_pca", "prefix": "cohort", "status": "success", "start_time": "2026-07-24T00:00:00+00:00", "run_id": "pop_pca_test", "genotype_scope_id": genotype, "analysis_scope_id": analysis, "inputs": [{"role": "population_vcf", "sha256": digest}], "artifacts": {role: {**proof, "artifact": role, "role": role} for role in roles}, } (final / "cohort.pop_pca.test.manifest.json").write_text( json.dumps(manifest), encoding="utf-8", ) return genotype, analysis def test_population_stages_are_executable_and_registry_aligned() -> None: definitions = {item["id"]: item for item in StageToolRegistry.STAGE_DEFINITIONS} for stage in POP_STAGES: assert stage in EXECUTABLE_STAGES assert definitions[stage]["script"] == f"run_{stage}.py" assert definitions[stage]["category"] == "population_genomics" def test_population_intent_does_not_steal_gene_family_phylogeny() -> None: assert resolve_population_intent("进行群体遗传分析")["stages"] == POP_STAGES assert resolve_population_intent("构建群体系统树")["stages"] == ["pop_phylogeny"] assert resolve_population_intent("构建基因家族系统树") is None def test_population_request_binds_inputs_through_public_parser() -> None: parsed = parse_intent_from_query(POPULATION_REQUEST) assert parsed["stages"] == POP_STAGES assert parsed["primary_stage"] == "pop_snp_density" assert parsed["prefix"] == "cohort" assert parsed["params"]["populationVcf"] == POPULATION_VCF assert parsed["params"]["populationSampleGroup"] == POPULATION_GROUPS def test_population_request_binds_inputs_through_fast_parser() -> None: parsed = fast_intent_recognition(POPULATION_REQUEST) assert parsed is not None assert parsed["stages"] == POP_STAGES assert parsed["prefix"] == "cohort" assert parsed["params"]["populationVcf"] == POPULATION_VCF assert parsed["params"]["populationSampleGroup"] == POPULATION_GROUPS def test_population_request_binds_inputs_through_public_agent() -> None: state = AgentState(session_id="population-intent-test", job_id="job_1784815396884_5db7e7") message = AgentMessage( sender="router", receiver="intent", message_type="recognize", payload={"query": POPULATION_REQUEST, "workspace": "/tmp/population-intent-test"}, ) result = asyncio.run(IntentAgentV2(object()).execute(state, message)) parsed = result.results["results"]["intent"] assert parsed["stages"] == POP_STAGES assert parsed["prefix"] == "cohort" assert parsed["params"]["populationVcf"] == POPULATION_VCF assert parsed["params"]["populationSampleGroup"] == POPULATION_GROUPS def test_population_pca_request_cannot_route_to_rnaseq() -> None: parsed = parse_intent_from_query( "请使用 input/job_test/population.snp.vcf.gz 进行群体 PCA 主成分分析。" ) assert parsed["stages"] == ["pop_pca"] assert parsed["primary_stage"] == "pop_pca" assert parsed["params"]["populationVcf"] == "input/job_test/population.snp.vcf.gz" def test_population_group_table_is_optional() -> None: parsed = parse_intent_from_query( "使用 input/job_test/population.snp.vcf.gz 执行群体 SNP 密度分析。" ) assert parsed["params"]["populationVcf"] == "input/job_test/population.snp.vcf.gz" assert "populationSampleGroup" not in parsed["params"] def test_population_quoted_paths_keep_intentional_spaces() -> None: parsed = parse_intent_from_query( '使用 "input/job test/population cohort.vcf.gz" 和分组表 ' '"input/job test/population groups.tsv",执行群体 PCA 分析。' ) assert parsed["params"]["populationVcf"] == "input/job test/population cohort.vcf.gz" assert parsed["params"]["populationSampleGroup"] == "input/job test/population groups.tsv" def test_population_request_does_not_guess_between_unanchored_vcfs() -> None: parsed = parse_intent_from_query( "比较 input/job_test/a.vcf.gz 和 input/job_test/b.vcf.gz,执行群体 PCA 分析。" ) assert parsed["stages"] == ["pop_pca"] assert "populationVcf" not in parsed["params"] def test_population_plan_carries_inputs_to_every_stage() -> None: parsed = parse_intent_from_query(POPULATION_REQUEST) planner = PlanningAgentV2.__new__(PlanningAgentV2) stages = planner._generate_default_stages( parsed["stages"], parsed["prefix"], intent_params=parsed["params"], ) assert [stage["id"] for stage in stages] == POP_STAGES for stage in stages: assert stage["params"]["populationVcf"] == POPULATION_VCF assert stage["params"]["populationSampleGroup"] == POPULATION_GROUPS def test_population_backend_command_propagates_stage_parameters(tmp_path: Path) -> None: command = _population_cli_command( stage="pop_snp_density", workspace=tmp_path, job_id="job", prefix="cohort", vcf=tmp_path / "input.vcf", sample_group="", cpu=4, params={"chromosome_policy": "rice_1_12", "coarse_window_bp": 1_000_000, "fine_window_bp": 500_000, "rscript": "/opt/Rscript"}, ) assert command[:3] == [command[0], "-m", "apps.cli.run_pop_snp_density"] assert command[command.index("--chromosome-policy") + 1] == "rice_1_12" assert command[command.index("--rscript") + 1] == "/opt/Rscript" def test_population_ld_defaults_use_verified_project_wrappers() -> None: request = ExecuteRequest(stage="pop_ld_decay") assert request.popBcftools == "scripts/pysam_bcftools.py" assert request.popPlotOne == "scripts/plot_pop_ld_decay.py" assert request.popPlotMulti == "scripts/plot_pop_ld_decay.py" def test_execute_request_exposes_population_confirmation_fields() -> None: request = ExecuteRequest(stage="pop_structure", populationVcf="/data/cohort.vcf.gz", popKMin=2, popKMax=10, popGwasK=2) assert request.populationVcf == "/data/cohort.vcf.gz" assert (request.popKMin, request.popKMax, request.popGwasK) == (2, 10, 2) def test_execute_api_submits_all_population_fields(monkeypatch, tmp_path: Path) -> None: from apps.web.vue3 import main as api class DummyExecutor: def __init__(self) -> None: self.calls = [] def submit(self, *args): self.calls.append(args) vcf = tmp_path / "cohort.vcf" vcf.write_text( "##fileformat=VCFv4.2\n#CHROM\tPOS\tID\tREF\tALT\tQUAL\tFILTER\tINFO\tFORMAT\tS1\n" "1\t1\trs1\tA\tG\t.\tPASS\t.\tGT\t0/1\n", encoding="utf-8", ) groups = tmp_path / "groups.tsv" groups.write_text("S1\tGroup A\n", encoding="utf-8") dummy = DummyExecutor() monkeypatch.setattr(api, "PROJECT_ROOT", tmp_path) monkeypatch.setattr(api, "WORKSPACE_ROOT", tmp_path / "workspace") monkeypatch.setattr(api, "INPUT_ROOT", tmp_path / "input") monkeypatch.setattr(api, "CELERY_AVAILABLE", False) monkeypatch.setattr(api, "executor", dummy) result = asyncio.run(api.execute_stage(api.ExecuteRequest( stage="pop_ld_decay", job_id="job", prefix="cohort", populationVcf=str(vcf), populationSampleGroup=str(groups), popBcftools="/tools/bcftools", popLddecay="/tools/PopLDdecay", popPlotOne="/tools/plot-one", popPlotMulti="/tools/plot-multi", ))) assert result["async"] is True, result assert len(dummy.calls) == 1 submitted = dummy.calls[0][4] assert submitted["populationVcf"] == str(vcf) assert submitted["populationSampleGroup"] == str(groups) assert submitted["popBcftools"] == "/tools/bcftools" assert submitted["popLddecay"] == "/tools/PopLDdecay" assert submitted["popPlotOne"] == "/tools/plot-one" assert submitted["popPlotMulti"] == "/tools/plot-multi" def test_stage_tool_executor_forwards_population_fields_to_backend(monkeypatch, tmp_path: Path) -> None: from apps.web import pipeline_backend captured = {} @dataclass class FakeJobInfo: job_id: str input_dir: str workspace_dir: str logs_dir: str created_at: str def fake_run_stage(**kwargs): captured.update(kwargs) manifest_path = Path(kwargs["job"].workspace_dir) / "final" / "cohort.pop_structure.manifest.json" manifest_path.parent.mkdir(parents=True, exist_ok=True) manifest_path.write_text( json.dumps({"stage": "pop_structure", "status": "success"}), encoding="utf-8", ) return "✅ pop_structure completed", manifest_path monkeypatch.setattr(pipeline_backend, "JobInfo", FakeJobInfo) monkeypatch.setattr(pipeline_backend, "run_stage", fake_run_stage) params = { "populationVcf": "/data/cohort.vcf.gz", "populationSampleGroup": "/data/groups.tsv", "popPlink": "/tools/plink", "popAdmixture": "/tools/admixture", "popKMin": 3, "popKMax": 7, "popCvFolds": 4, "popRandomSeed": 23, "popGwasK": 5, } tool = StageTool( "pop_structure", "Population structure", "run_pop_structure.py", "population_genomics", "test", timeout=5, ) result = asyncio.run( StageToolExecutor(project_root=tmp_path).execute(tool, "job", "cohort", params) ) assert result["status"] == "success" assert captured["population_vcf"] == "/data/cohort.vcf.gz" assert captured["population_sample_group"] == "/data/groups.tsv" assert captured["population_params"] == { "plink": "/tools/plink", "tassel": get_configured_path("TASSEL_PIPELINE", "run_pipeline.pl"), "fasttree": str(get_conda_env_bin("ASAW") / "FastTree"), "admixture": "/tools/admixture", "bcftools": "scripts/pysam_bcftools.py", "poplddecay": str(get_conda_env_bin("ASAW") / "PopLDdecay"), "plot_one": "scripts/plot_pop_ld_decay.py", "plot_multi": "scripts/plot_pop_ld_decay.py", "rscript": str(get_conda_env_rscript("ASAW")), "k_min": 3, "k_max": 7, "cv_folds": 4, "random_seed": 23, "gwas_k": 5, "chromosome_policy": "all_vcf_contigs", "chromosomes": "", } def test_population_snp_density_uses_portable_rscript_default() -> None: project_root = Path(__file__).resolve().parents[2] main_source = (project_root / "apps/web/vue3/main.py").read_text(encoding="utf-8") frontend_source = (project_root / "apps/web/vue3/index.html").read_text(encoding="utf-8") assert "/data/miniconda3/envs/ASAW/bin/Rscript" not in main_source assert "/data/miniconda3/envs/ASAW/bin/Rscript" not in frontend_source assert "/home/xzq/miniconda3/envs/ASAW/bin/Rscript" not in main_source assert "/home/xzq/miniconda3/envs/ASAW/bin/Rscript" not in frontend_source def test_celery_artifact_validation_uses_population_manifest_identity(monkeypatch) -> None: import tasks from gfa_core.mas.tools import stage_identity captured = {} def fake_get_stage_status_for_identity(*, identity, **_kwargs): captured["identity"] = identity return type("Status", (), {"completed": True})() monkeypatch.setattr( stage_identity, "get_stage_status_for_identity", fake_get_stage_status_for_identity, ) result = tasks.validate_single_stage_artifacts( "job", "cohort", "pop_pca", { "final_output": { "manifest": { "stage": "pop_pca", "prefix": "cohort", "genotype_scope_id": "genotype:abc", "analysis_scope_id": "population-analysis:def", "run_id": "pop_pca_run", } } }, ) assert result is None assert captured["identity"].genotype_scope_id == "genotype:abc" assert captured["identity"].analysis_scope_id == "population-analysis:def" assert captured["identity"].run_id == "pop_pca_run" def test_population_preflight_resolves_relative_paths_from_project_root(monkeypatch, tmp_path: Path) -> None: from apps.web.vue3 import main as api input_dir = tmp_path / "input" / "job_test" input_dir.mkdir(parents=True) vcf = input_dir / "cohort.vcf.gz" vcf.write_bytes(b"vcf") groups = input_dir / "groups.tsv" groups.write_text("S1\tA\n", encoding="utf-8") elsewhere = tmp_path / "elsewhere" elsewhere.mkdir() monkeypatch.setattr(api, "PROJECT_ROOT", tmp_path) monkeypatch.chdir(elsewhere) result = api._api_semantic_preflight( "job_test", "pop_pca", "cohort", { "populationVcf": "input/job_test/cohort.vcf.gz", "populationSampleGroup": "input/job_test/groups.tsv", }, ) assert result["valid"] is True assert result["materialized_params"] == { "populationVcf": str(vcf.resolve()), "populationSampleGroup": str(groups.resolve()), } def test_population_preflight_reports_missing_vcf_path(monkeypatch, tmp_path: Path) -> None: from apps.web.vue3 import main as api monkeypatch.setattr(api, "PROJECT_ROOT", tmp_path) result = api._api_semantic_preflight( "job_test", "pop_pca", "cohort", {"populationVcf": "input/job_test/missing.vcf.gz"}, ) assert result["valid"] is False assert result["issues"] == [{ "severity": "error", "code": "missing_population_vcf", "message": "Population VCF does not exist: input/job_test/missing.vcf.gz", }] def test_population_preflight_reports_missing_group_path(monkeypatch, tmp_path: Path) -> None: from apps.web.vue3 import main as api vcf = tmp_path / "cohort.vcf.gz" vcf.write_bytes(b"vcf") monkeypatch.setattr(api, "PROJECT_ROOT", tmp_path) result = api._api_semantic_preflight( "job_test", "pop_pca", "cohort", { "populationVcf": str(vcf), "populationSampleGroup": "input/job_test/missing.groups.tsv", }, ) assert result["valid"] is False assert result["issues"] == [{ "severity": "error", "code": "missing_population_sample_group", "message": "Population sample-group table does not exist: input/job_test/missing.groups.tsv", }] def test_population_preflight_materializes_empty_scope_from_manifest(monkeypatch, tmp_path: Path) -> None: from apps.web.vue3 import main as api vcf = tmp_path / "cohort.vcf.gz" vcf.write_bytes(b"vcf") workspace_root = tmp_path / "workspace" genotype, analysis = _write_complete_pca_manifest(workspace_root / "job", vcf) monkeypatch.setattr(api, "PROJECT_ROOT", tmp_path) monkeypatch.setattr(api, "WORKSPACE_ROOT", workspace_root) result = api._api_semantic_preflight( "job", "pop_pca", "cohort", {"populationVcf": str(vcf)}, ) assert result["valid"] is True assert result["materialized_params"]["genotypeScopeId"] == genotype assert result["materialized_params"]["analysisScopeId"] == analysis assert result["materialized_params"]["runId"] == "pop_pca_test" def test_population_preflight_rejects_partial_scope(monkeypatch, tmp_path: Path) -> None: from apps.web.vue3 import main as api vcf = tmp_path / "cohort.vcf.gz" vcf.write_bytes(b"vcf") monkeypatch.setattr(api, "PROJECT_ROOT", tmp_path) monkeypatch.setattr(api, "WORKSPACE_ROOT", tmp_path / "workspace") result = api._api_semantic_preflight( "job", "pop_pca", "cohort", {"populationVcf": str(vcf), "genotypeScopeId": "genotype:" + "a" * 64}, ) assert result["valid"] is False assert result["issues"][0]["code"] == "invalid_population_manifest_identity" def test_population_plan_recognizes_completed_manifest_without_request_scope(monkeypatch, tmp_path: Path) -> None: from apps.web.vue3 import main as api vcf = tmp_path / "cohort.vcf.gz" vcf.write_bytes(b"vcf") workspace_root = tmp_path / "workspace" _write_complete_pca_manifest(workspace_root / "job", vcf) monkeypatch.setattr(api, "PROJECT_ROOT", tmp_path) monkeypatch.setattr(api, "WORKSPACE_ROOT", workspace_root) details = [{ "id": "pop_pca", "params": {"prefix": "cohort", "populationVcf": str(vcf)}, }] completed = api._identity_completed_stages_for_plan( "job", ["pop_pca"], {"populationVcf": str(vcf)}, details, effective_prefix="cohort", ) assert completed == ["pop_pca"] assert details[0]["params"]["genotypeScopeId"].startswith("genotype:") assert details[0]["params"]["analysisScopeId"].startswith("population-analysis:") def test_population_results_materialize_unique_identity_when_scopes_are_empty(monkeypatch, tmp_path: Path) -> None: from apps.web.vue3 import main as api vcf = tmp_path / "cohort.vcf.gz" vcf.write_bytes(b"vcf") workspace_root = tmp_path / "workspace" _write_complete_pca_manifest(workspace_root / "job", vcf) monkeypatch.setattr(api, "WORKSPACE_ROOT", workspace_root) result = asyncio.run(api.get_results( job_id="job", stage="pop_pca", result_prefix="cohort", rnaseq_scope_id="", scope_id="", genotype_scope_id="", phenotype_scope_id="", covariate_scope_id="", model_scope_id="", source_model_scope_id="", analysis_scope_id="", )) assert result["pop_pca"]["files"] assert result["pop_pca"]["status"] == "success" def test_execute_api_returns_concrete_population_preflight_error(monkeypatch, tmp_path: Path) -> None: from apps.web.vue3 import main as api monkeypatch.setattr(api, "PROJECT_ROOT", tmp_path) monkeypatch.setattr(api, "WORKSPACE_ROOT", tmp_path / "workspace") monkeypatch.setattr(api, "INPUT_ROOT", tmp_path / "input") result = asyncio.run(api.execute_stage(api.ExecuteRequest( stage="pop_pca", job_id="job_test", prefix="cohort", populationVcf="input/job_test/missing.vcf.gz", ))) assert result["status"] == "preflight_failed" assert result["message"] == "Population VCF does not exist: input/job_test/missing.vcf.gz" def test_frontend_contains_all_population_stage_ids() -> None: html = (Path(__file__).resolve().parents[2] / "apps" / "web" / "vue3" / "index.html").read_text(encoding="utf-8") for stage in POP_STAGES: assert stage in html assert "applyPopulationExecutionParams" in html assert "appendPopulationIdentityQuery" in html assert "populationVcf" in html def test_frontend_population_binding_uses_stage_before_confirmation_params() -> None: html = (Path(__file__).resolve().parents[2] / "apps" / "web" / "vue3" / "index.html").read_text(encoding="utf-8") function_start = html.index("const applyPopulationExecutionParams") function_end = html.index("const executeSingleStageWithParams", function_start) function_source = html[function_start:function_end] assert function_source.index("stageParams[camelKey]") < function_source.index("batchParams[camelKey]") assert function_source.index("batchParams[camelKey]") < function_source.index("planParams[camelKey]") assert function_source.index("planParams[camelKey]") < function_source.index("confirmParams[camelKey]") def test_population_plink_commands_use_plink19_missing_id_template() -> None: project = Path(__file__).resolve().parents[2] for script in ("run_pop_pca.py", "run_pop_structure.py"): source = (project / "apps" / "cli" / script).read_text(encoding="utf-8") assert "@:#:$1:$2" in source assert "@:#:$r:$a" not in source def test_population_results_require_exact_identity_and_valid_hashes(monkeypatch, tmp_path: Path) -> None: from apps.web.vue3 import main as web workspace_root = tmp_path / "workspace" final = workspace_root / "job" / "final" final.mkdir(parents=True) artifact = final / "cohort.pop_pca.123456789abc.pca.tsv" artifact.write_text("sample\tPC1\nS1\t0.0\n", encoding="utf-8") genotype = "genotype:" + "a" * 64 analysis = "population-analysis:" + "b" * 64 pca_roles = { "pca_scores", "pca_eigenvalues", "pca_variance_table", "pca_pruning_markers", "pca_pc1_pc2_plot", "pca_scree_plot", "pca_tassel_covariate", "report", "environment", } proof = { "path": f"final/{artifact.name}", "size": artifact.stat().st_size, "sha256": hashlib.sha256(artifact.read_bytes()).hexdigest(), } manifest = { "stage": "pop_pca", "prefix": "cohort", "status": "success", "genotype_scope_id": genotype, "analysis_scope_id": analysis, "artifacts": {role: {"role": role, **proof} for role in pca_roles}, } (final / "cohort.pop_pca.123456789abc.manifest.json").write_text(json.dumps(manifest), encoding="utf-8") sibling_artifact = final / "sibling.pop_pca.123456789abc.pca.tsv" sibling_artifact.write_text("sample\tPC1\nS1\t9.9\n", encoding="utf-8") sibling = { **manifest, "prefix": "sibling", "artifacts": {"pca_scores": { "role": "pca_scores", "path": f"final/{sibling_artifact.name}", "size": sibling_artifact.stat().st_size, "sha256": hashlib.sha256(sibling_artifact.read_bytes()).hexdigest(), }}, } (final / "sibling.pop_pca.123456789abc.manifest.json").write_text(json.dumps(sibling), encoding="utf-8") monkeypatch.setattr(web, "WORKSPACE_ROOT", workspace_root) from gfa_core.mas.tools.stage_identity import StageIdentity, get_stage_status_for_identity partial = {**manifest, "prefix": "partial", "artifacts": {"pca_scores": {"role": "pca_scores", **proof}}} (final / "partial.pop_pca.123456789abc.manifest.json").write_text(json.dumps(partial), encoding="utf-8") partial_status = get_stage_status_for_identity( "job", StageIdentity(stage="pop_pca", prefix="partial", genotype_scope_id=genotype, analysis_scope_id=analysis), workspace_root=workspace_root, ) assert partial_status.completed is False assert any("missing required population artifact role" in error for error in partial_status.errors) partial_results = asyncio.run(web.get_results( job_id="job", stage="pop_pca", result_prefix="partial", rnaseq_scope_id="", scope_id="", genotype_scope_id=genotype, analysis_scope_id=analysis, phenotype_scope_id="", covariate_scope_id="", model_scope_id="", source_model_scope_id="", ))["pop_pca"] assert partial_results["files"] == [] ld_qc = final / "partialld.group_qc.json" ld_qc.write_text(json.dumps({"valid_groups": [{"group": "North", "slug": "north", "sample_count": 3}]}), encoding="utf-8") ld_qc_proof = { "path": f"final/{ld_qc.name}", "size": ld_qc.stat().st_size, "sha256": hashlib.sha256(ld_qc.read_bytes()).hexdigest(), } ld_roles = {"ld_all_stat", "ld_all_plot_png", "ld_all_plot_pdf", "group_sample_qc", "report", "environment"} ld_artifacts = {role: {"role": role, **proof} for role in ld_roles} ld_artifacts["group_sample_qc"] = {"role": "group_sample_qc", **ld_qc_proof} partial_ld = { "stage": "pop_ld_decay", "prefix": "partialld", "status": "success", "genotype_scope_id": genotype, "analysis_scope_id": analysis, "metrics": {"valid_group_count": 1}, "artifacts": ld_artifacts, } (final / "partialld.pop_ld_decay.123456789abc.manifest.json").write_text(json.dumps(partial_ld), encoding="utf-8") partial_ld_status = get_stage_status_for_identity( "job", StageIdentity(stage="pop_ld_decay", prefix="partialld", genotype_scope_id=genotype, analysis_scope_id=analysis), workspace_root=workspace_root, ) assert partial_ld_status.completed is False assert any("ld_group_stat_north" in error for error in partial_ld_status.errors) def results(**identity): return asyncio.run(web.get_results( job_id="job", stage="pop_pca", result_prefix="cohort", rnaseq_scope_id="", scope_id="", genotype_scope_id=identity.get("genotype", ""), analysis_scope_id=identity.get("analysis", ""), phenotype_scope_id="", covariate_scope_id="", model_scope_id="", source_model_scope_id="", )) exact_files = results(genotype=genotype, analysis=analysis)["pop_pca"]["files"] assert exact_files assert all("sibling" not in item["name"] for item in exact_files) assert results(genotype=genotype)["pop_pca"]["files"] == [] artifact.write_text("tampered\n", encoding="utf-8") assert results(genotype=genotype, analysis=analysis)["pop_pca"]["files"] == [] artifact.write_text("sample\tPC1\nS1\t0.0\n", encoding="utf-8") failed = { "stage": "pop_pca", "prefix": "cohort", "status": "failed", "start_time": "2099-01-01T00:00:00+00:00", "genotype_scope_id": genotype, "analysis_scope_id": analysis, "artifacts": {}, "error": "controlled retry failure", } (final / "cohort.pop_pca.failed.manifest.json").write_text(json.dumps(failed), encoding="utf-8") latest = results(genotype=genotype, analysis=analysis)["pop_pca"] assert latest["files"] == [] assert latest["status"] == "failed"