Sample-bound filter nodes can apply to a patient #1855 - #1857
Conversation
A tag placed under a patient / specimen / extraction level node had no proband sample - two DNA callers on the one extraction resolve, so SampleNode gives up - and the tagging was then about nobody. It now records the patient the same way it records the sample: set at tag time, part of the tagging's identity, backfilled. - VariantTag.patient, with the unique constraint becoming varianttag_one_per_person_in_analysis (variant, tag, analysis, user, sample, patient), so two patient level nodes in one analysis get a tagging each - AnalysisNode.get_proband() resolves sample and patient in one ancestor walk (NodeProband); get_proband_sample / get_proband_patient are wrappers and get_proband_by_node_id answers for a whole graph in one load - one_off_backfill_variant_tag_patient, registered as a ManualOperation - the classify form offers only that patient's samples: a restricted queryset only validates the POST, so the patient is forwarded to SampleAutocompleteView - Classify & Report lists a patient tagging on the patient's tab and on each of their sample tabs, and another patient's classification no longer resolves it - the grid pill's person marker says who the tagging is for
Zygosity, Allele Frequency, Mode of Inheritance and Gene List (sample QC
panel) hung their filter off one sample. Under a group level SampleNode with
two callers on the one extraction the proband sample is ambiguous, so the node
stayed unset - and picking one caller keyed the filter on that caller's
genotype column, which is NULL on every other caller's row.
Each of the four now applies to either one sample or one patient. In patient
mode the filter applies to every ancestor sample of that patient, ORed as one
pk__in subquery per sample (the group SampleNode's shape - an OR across two
genotype aliases has nowhere to hang in annotate_and_filter_queryset). The
proband patient auto-sets it where the proband sample is ambiguous; single
sample analyses keep setting the sample and their query is unchanged.
- patient FK on the four nodes (migration 0144); one of sample/patient is set
- AncestorSampleMixin owns the choice: _set_sample/_set_patient,
get_filter_samples, get_filter_patient, _get_filter_samples_arg_q_dict;
SampleNode's per sample subquery moves to cohort_mixin.get_sample_pk_in_q
- a sample with no GT (Zygosity, MOI) or no AF column (Allele Frequency)
passes through unfiltered, as SampleNode already does, and the method
summary says so
- Gene List's sample QC panel unions the patient's samples' active lists,
resolved when asked rather than stored
- one "Applies to" picker in the four editors ("sample:<pk>" / "patient:<pk>"),
mapping a template AnalysisVariable to whichever FK is set; the MOI editor's
"From Patient" panel gets every choice's gene/disease data at render, in
place of the sample_patient_gene_disease AJAX URL whose view took an
argument the URL never passed
- sample deletion bumps patient mode nodes of that patient
|
🤖 Written by Claude Open question from #1857: a bare Zygosity, Allele Frequency, MOI and Gene List nodes now apply to either one Proposal, to decide later rather than block the PR: rename the two FKs to
The cheaper alternative is a docstring note above the fields plus |
The form mixin named the UI label rather than what it does: work out the samples a filter node reads, directly or via their patient. AppliesToMixin -> AncestorSampleSourceMixin, applies_to -> sample_source (label unchanged), so the form shares vocabulary with AncestorSampleMixin on the model. Its choices now come from get_ancestor_samples() - the set _get_configuration_errors validates a picked sample against - rather than get_samples(), which included the node's own. MOINodeView's per-choice gene/disease data follows the same set, so it can't offer a choice it has no entry for.
Issue #1855. Plan:
claude/plans/1855_filter_node_patient_scope_plan.md.Why
Zygosity, Allele Frequency, Mode of Inheritance and Gene List (sample QC panel) hold one
sampleFK, auto-set from the ancestors' proband. Under a group level SampleNode with two callers on the one extraction (TSO 500 small variants + CNV) the proband sample is ambiguous, so the node stayed unset. Picking one caller by hand was worse: the filter is keyed on that caller's genotype column, which is NULL on every other caller's row, so the node silently dropped them.What
Each of the four nodes applies to either one sample or one patient. In patient mode the filter applies to every ancestor sample of that patient, ORed as one
pk__insubquery per sample (the same shape the group SampleNode produces, and for the same reason: an OR across two genotype aliases has nowhere to hang inannotate_and_filter_queryset). The proband patient auto-sets it where the proband sample is ambiguous. Single-sample analyses keep setting the sample and produce the query they always did.patientFK on the four nodes (migration 0144). Exactly one ofsample/patientis set.AncestorSampleMixinowns the choice (_set_sample/_set_patient,get_filter_samples,get_filter_patient,_get_filter_samples_arg_q_dict). SampleNode's per-sample subquery builder moves tocohort_mixin.get_sample_pk_in_q.sample:<pk>/patient:<pk>), mapping a template AnalysisVariable to whichever FK is set.sample_patient_gene_diseaseAJAX URL, whose view required an argument the URL never passed.Tests
FilterNodePatientScopeTest(16 tests) inanalysis/tests/test_sample_node_levels.pyon the TSO 500 patient fixture. Full suite: 3230 tests OK.