From 247640b497aa646ab8f2c19341a3a3b11fe9431b Mon Sep 17 00:00:00 2001 From: Hana Snow Date: Mon, 27 Apr 2026 12:14:27 -0400 Subject: [PATCH 1/2] share detailed phenotypes when nopt restricted --- clickhouse_search/search_tests.py | 8 ++++---- seqr/fixtures/1kg_project.json | 1 + seqr/views/apis/variant_search_api.py | 18 +++++++++++------- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/clickhouse_search/search_tests.py b/clickhouse_search/search_tests.py index 6307fce27e..2cf127c3f4 100644 --- a/clickhouse_search/search_tests.py +++ b/clickhouse_search/search_tests.py @@ -1054,8 +1054,8 @@ def test_variant_lookup(self, mock_liftover): }, 'I2_F0_1-10439-AC-A': { 'affected': 'A', 'familyGuid': 'F0_1-10439-AC-A', 'individualGuid': 'I2_F0_1-10439-AC-A', 'sex': 'X0', - 'features': [{'category': 'HP:0000707', 'label': '1 terms'}, - {'category': 'HP:0001626', 'label': '1 terms'}], + 'features': [{'category': 'HP:0000707', 'label': 'Morphological abnormality of the central nervous system', 'id': 'HP:0002011'}, + {'category': 'HP:0001626', 'label': 'Arrhythmia', 'id': 'HP:0011675'}], 'vlmContactEmail': 'test@broadinstitute.org,vlm@broadinstitute.org', }, } @@ -1128,8 +1128,8 @@ def test_variant_lookup(self, mock_liftover): }, 'I2_F0_suffix_140608_DUP': { 'affected': 'A', 'familyGuid': 'F0_suffix_140608_DUP', 'individualGuid': 'I2_F0_suffix_140608_DUP', - 'features': [{'category': 'HP:0000707', 'label': '1 terms'}, - {'category': 'HP:0001626', 'label': '1 terms'}], + 'features': [{'category': 'HP:0000707', 'label': 'Morphological abnormality of the central nervous system', 'id': 'HP:0002011'}, + {'category': 'HP:0001626', 'label': 'Arrhythmia', 'id': 'HP:0011675'}], 'sex': 'X0', 'vlmContactEmail': 'test@broadinstitute.org,vlm@broadinstitute.org', }, } diff --git a/seqr/fixtures/1kg_project.json b/seqr/fixtures/1kg_project.json index 9957c85223..005f532f82 100644 --- a/seqr/fixtures/1kg_project.json +++ b/seqr/fixtures/1kg_project.json @@ -63,6 +63,7 @@ "workspace_namespace": "my-seqr-billing", "is_mme_enabled": true, "is_demo": true, + "restrict_sharing": true, "mme_primary_data_owner": "", "mme_contact_url": "mailto:seqr-test@gmail.com,test@broadinstitute.org", "vlm_contact_email": "seqr-test@gmail.com,test@broadinstitute.org", diff --git a/seqr/views/apis/variant_search_api.py b/seqr/views/apis/variant_search_api.py index 6f7013eb8f..3c7c9e2894 100644 --- a/seqr/views/apis/variant_search_api.py +++ b/seqr/views/apis/variant_search_api.py @@ -563,6 +563,7 @@ def _update_lookup_variant(variant, response, individual_guid_map, user): for i in Individual.objects.filter(family__guid__in=no_access_families).values( 'family__guid', 'individual_id', 'affected', 'sex', 'features', 'guid', vlmContactEmail=F('family__project__vlm_contact_email'), + restrict_sharing=F('family__project__restrict_sharing'), ) } add_individual_hpo_details([i for _, i in individual_summary_map.values()]) @@ -598,17 +599,20 @@ def _update_lookup_variant(variant, response, individual_guid_map, user): continue individual_guid = f'I{j}_{family_guid}' individual_guid_map[unmapped_individual_guid] = individual_guid - feature_category_count = defaultdict(int) - for feature in individual['features'] or []: - feature_category_count[feature.get('category', 'Other')] += 1 + features = individual['features'] or [] + if individual.pop('restrict_sharing'): + feature_category_count = defaultdict(int) + for feature in features: + feature_category_count[feature.get('category', 'Other')] += 1 + features = [ + {'category': category, 'label': f'{count} terms'} + for category, count in feature_category_count.items() + ] response['individualsByGuid'][individual_guid] = { **individual, 'familyGuid': family_guid, 'individualGuid': individual_guid, - 'features': [ - {'category': category, 'label': f'{count} terms'} - for category, count in feature_category_count.items() - ], + 'features': features, } variant['genotypes'][individual_guid] = genotype From c93996b83191279af8455ae110528a1ef6a803aa Mon Sep 17 00:00:00 2001 From: Hana Snow Date: Mon, 27 Apr 2026 15:38:57 -0400 Subject: [PATCH 2/2] actually include gene search --- clickhouse_search/search.py | 6 ++++-- .../commands/tag_seqr_prioritized_variants.py | 18 +++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/clickhouse_search/search.py b/clickhouse_search/search.py index d3368c7457..fcd8714ee6 100644 --- a/clickhouse_search/search.py +++ b/clickhouse_search/search.py @@ -129,9 +129,11 @@ def _parse_locus_search(locus, genome_version, search): exclude_locations = bool(exclude.get('rawItems')) if locus and exclude_locations: raise InvalidSearchException('Cannot specify both Location and Excluded Genes/Intervals') + if not (locus or exclude_locations): + return genes, intervals, invalid_items = parse_locus_list_items( - locus or exclude, genome_version=genome_version, get_genes_func=_get_genes, + locus or exclude, genome_version=genome_version, get_genes_func=get_search_genes, ) if invalid_items: raise InvalidSearchException('Invalid genes/intervals: {}'.format(', '.join(invalid_items))) @@ -151,7 +153,7 @@ def _parse_locus_search(locus, genome_version, search): exclude.pop('rawItems', None) -def _get_genes(gene_ids, genome_version): +def get_search_genes(gene_ids, genome_version): genes = GeneInfo.objects.filter(**{'gene_id__in': gene_ids, f'start_grch{genome_version}__isnull': False}).values( 'id', 'gene_id', **{field: F(f'{field}_grch{genome_version}') for field in ['chrom', 'start', 'end']}, ) diff --git a/seqr/management/commands/tag_seqr_prioritized_variants.py b/seqr/management/commands/tag_seqr_prioritized_variants.py index a3150444c7..c59138e3f2 100644 --- a/seqr/management/commands/tag_seqr_prioritized_variants.py +++ b/seqr/management/commands/tag_seqr_prioritized_variants.py @@ -6,12 +6,11 @@ from django.db.models.functions import JSONObject import json -from clickhouse_search.search import get_clickhouse_variants, ENTRY_CLASS_MAP +from clickhouse_search.search import get_clickhouse_variants, get_search_genes, ENTRY_CLASS_MAP from panelapp.models import PaLocusListGene from reference_data.models import GENOME_VERSION_GRCh38 from seqr.models import Project, Family, Individual, Sample, LocusList from seqr.utils.communication_utils import send_project_notification -from seqr.utils.gene_utils import get_genes from clickhouse_search.constants import ANY_AFFECTED, HOMOZYGOUS_RECESSIVE, X_LINKED_RECESSIVE_MALE_AFFECTED, DE_NOVO, COMPOUND_HET from seqr.views.utils.json_utils import DjangoJSONEncoderWithSets from seqr.views.utils.orm_to_json_utils import SEQR_TAG_TYPE @@ -475,7 +474,7 @@ def handle(self, *args, **options): family_guid_map[guid] = db_id family_name_map[db_id] = family_id - exclude_genes = get_genes(EXCLUDE_GENE_IDS, genome_version=GENOME_VERSION_GRCh38) + exclude_genes = get_search_genes(EXCLUDE_GENE_IDS, genome_version=GENOME_VERSION_GRCh38) gene_by_moi = defaultdict(dict) for gene_list in GENE_LISTS: self._get_gene_list_genes(gene_list['name'], gene_list['confidences'], gene_by_moi, exclude_genes.keys()) @@ -487,7 +486,7 @@ def handle(self, *args, **options): for dataset_type, searches in SEARCHES.items(): self._run_dataset_type_searches( dataset_type, searches, sample_qs, updates, search_counts, samples_by_dataset_type, family_guid_map, - project, exclude_genes, gene_by_moi, + project, list(exclude_genes.values()), gene_by_moi, ) self._run_multi_data_type_comp_het_search( @@ -517,7 +516,7 @@ def handle(self, *args, **options): ) @classmethod - def _run_dataset_type_searches(cls, dataset_type, searches, sample_qs, updates, search_counts, samples_by_dataset_type, family_guid_map, project, exclude_genes, gene_by_moi): + def _run_dataset_type_searches(cls, dataset_type, searches, sample_qs, updates, search_counts, samples_by_dataset_type, family_guid_map, project, exclude_intervals, gene_by_moi): is_sv = dataset_type == Sample.DATASET_TYPE_SV_CALLS sample_qs = sample_qs.filter(dataset_type=dataset_type) if is_sv: @@ -545,14 +544,15 @@ def _run_dataset_type_searches(cls, dataset_type, searches, sample_qs, updates, logger.info(f'Searching for prioritized {dataset_type} variants in {len(samples_by_family)} families in project {project.name}') for search_name, config_search in searches.items(): logger.info(f'Searching for criteria: {search_name}') - exclude_locations = not config_search.get('gene_list_moi') - search_genes = exclude_genes if exclude_locations else gene_by_moi[config_search['gene_list_moi']] + search_genes = gene_by_moi[config_search['gene_list_moi']] if config_search.get('gene_list_moi') else None + if search_genes: + exclude_intervals = None sample_data = cls._get_valid_family_sample_data( project, sample_type, samples_by_family, config_search.get('family_filter'), ) num_results = cls._execute_search( {dataset_type: sample_data}, search_name, family_variant_data, family_guid_map, - exclude_locations=exclude_locations, genes=search_genes, **config_search, **ALL_SEARCHES_CRITERIA, + exclude_intervals=exclude_intervals, genes=search_genes, **config_search, **ALL_SEARCHES_CRITERIA, ) if sample_data['num_families'] else 0 search_counts[search_name] = num_results @@ -699,7 +699,7 @@ def _get_gene_list_genes(name, confidences, gene_by_moi, exclude_gene_ids): ]).values('gene_id', 'is_dominant', 'is_recessive', 'is_mito') gene_id_mois = {g['gene_id']: g for g in moi_gene_ids} - genes_by_id = get_genes(gene_id_mois.keys(), genome_version=GENOME_VERSION_GRCh38, additional_model_fields=['id']) + genes_by_id = get_search_genes(gene_id_mois.keys(), genome_version=GENOME_VERSION_GRCh38) gene_by_moi[DOMINANT_MOI].update({gene_id: gene for gene_id, gene in genes_by_id.items() if not gene_id_mois[gene_id]['is_recessive']}) gene_by_moi[RECESSIVE_MOI].update({gene_id: gene for gene_id, gene in genes_by_id.items() if not gene_id_mois[gene_id]['is_dominant']}) gene_by_moi[MITO_MOI].update({gene_id: gene for gene_id, gene in genes_by_id.items() if gene_id_mois[gene_id]['is_mito']})