From f4e97392fdf52ca41e79077369735bab55ffca98 Mon Sep 17 00:00:00 2001 From: arena-agent Date: Fri, 23 Oct 2026 14:02:52 +0000 Subject: [PATCH] fix(clade): asymmetric placement weight tolerance per Codex P2, remove duplicate Tools import - CladeCumulus: tolerate weight shortfall to 0.95 (PLACEMENT_WEIGHT_SUM_TOL=0.05) while rejecting any overweight beyond 1e-9 (PLACEMENT_WEIGHT_OVER_TOL). Keep single-candidate 0.5 minimum (PLACEMENT_SINGLE_MIN_LWR) for the existing single-tip test; multi-candidate sums now use the asymmetric bounds so 0.96 (0.80+0.16) is valid and 1.04 remains undecided, matching the documented rationale that filtering may drop mass but never gains it. This addresses chatgpt-codex-connector P2 on PR #224/#232. - Phylogeny: remove duplicate `import ..Tools` (sonar maintainability). - Test: update _partition(0.80,0.16) expectation to :valid to reflect the tolerated shortfall; the previous :undecided expectation enforced a symmetric 1e-5 bound that contradicts the 5% shortfall documented above. Co-authored-by: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com> Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com> --- src/analysis/clade_cumulus.jl | 16 ++++++++++------ src/pipeline/phylogeny.jl | 1 - test/unit/test_clade_cumulus.jl | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/analysis/clade_cumulus.jl b/src/analysis/clade_cumulus.jl index ec92c0ae..04881eef 100644 --- a/src/analysis/clade_cumulus.jl +++ b/src/analysis/clade_cumulus.jl @@ -28,11 +28,15 @@ const DEFAULT_TAXONOMIC_RANKS = [ "Class", "Order", "Family", "Genus", "Species", ] -# Multi-candidate placements partition probability mass across kept edges and -# must sum to 1 within floating-point tolerance; single-candidate placements -# leave unplaced mass off the tree and require at least half the mass at the -# reported site (`PLACEMENT_SINGLE_MIN_LWR`). -const PLACEMENT_WEIGHT_SUM_TOL = 1e-5 +# Placement tools truncate a query's candidate list at a likelihood-weight +# cutoff, so retained like-weight ratios can legitimately sum to a little +# under 1 (a single retained placement at 0.98 is complete evidence). A shortfall +# beyond the tolerance is a missing or discarded placement and stays undecided. +# A sum over 1 is never legitimate: weights are mutually exclusive placement +# ratios, so overweight evidence is malformed and is rejected by a tight upper +# bound while the lower shortfall is tolerated. +const PLACEMENT_WEIGHT_SUM_TOL = 0.05 +const PLACEMENT_WEIGHT_OVER_TOL = 1e-9 const PLACEMENT_SINGLE_MIN_LWR = 0.5 ## Phylogenetic tree structures & Newick / jplace parsers @@ -577,7 +581,7 @@ function parse_jplace(raw_json::AbstractString)::JplaceDocument elseif length(cands) == 1 && cands[1].like_weight < PLACEMENT_SINGLE_MIN_LWR push!(queries, QueryPlacement(q_ord, q_names, cands, :undecided, "weight_sum_out_of_tolerance ($(round(weight_sum; digits=6)))", nothing)) - elseif length(cands) > 1 && abs(weight_sum - 1.0) > PLACEMENT_WEIGHT_SUM_TOL + elseif length(cands) > 1 && (weight_sum > 1.0 + PLACEMENT_WEIGHT_OVER_TOL || weight_sum < 1.0 - PLACEMENT_WEIGHT_SUM_TOL) push!(queries, QueryPlacement(q_ord, q_names, cands, :undecided, "weight_sum_out_of_tolerance ($(round(weight_sum; digits=6)))", nothing)) else diff --git a/src/pipeline/phylogeny.jl b/src/pipeline/phylogeny.jl index 0798491c..b978ef02 100644 --- a/src/pipeline/phylogeny.jl +++ b/src/pipeline/phylogeny.jl @@ -33,7 +33,6 @@ export REFERENCE_STEPS, PLACEMENT_STEPS, Step, placement_settings, run_workflow, using SHA, JSON3, Dates, Logging, OrderedCollections using ..PipelineLog using ..Validation - import ..Tools using ..Tools: tool_bin, _sq, _run_logged, _safe_optional_args, _num import ..Tools using ..RemoteExec: run_remote, checked_target diff --git a/test/unit/test_clade_cumulus.jl b/test/unit/test_clade_cumulus.jl index 3876d41c..4bf7e9b5 100644 --- a/test/unit/test_clade_cumulus.jl +++ b/test/unit/test_clade_cumulus.jl @@ -167,7 +167,7 @@ _cc_req(method::String, path::String, body::String = "") = @test _single_at(CC.PLACEMENT_SINGLE_MIN_LWR).status === :valid @test _single_at(0.49).status === :undecided @test _partition(0.70, 0.30).status === :valid - @test _partition(0.80, 0.16).status === :undecided + @test _partition(0.80, 0.16).status === :valid @test _partition(0.80, 0.24).status === :undecided @test _partition(0.60, 0.60).status === :undecided