diff --git a/src/mavedb/lib/csv/namespaces.py b/src/mavedb/lib/csv/namespaces.py index 0565c6a8..285fd684 100644 --- a/src/mavedb/lib/csv/namespaces.py +++ b/src/mavedb/lib/csv/namespaces.py @@ -64,7 +64,7 @@ class CsvNamespace(StrEnum): CsvNamespace.CLINGEN: ("ClinGen allele ID", CsvNamespaceGroup.ANNOTATION), CsvNamespace.REFERENCE_HGVS: ("Reference-frame HGVS", CsvNamespaceGroup.ANNOTATION), CsvNamespace.VEP: ("VEP consequence", CsvNamespaceGroup.ANNOTATION), - CsvNamespace.GNOMAD: ("gnomAD allele frequency", CsvNamespaceGroup.ANNOTATION), + CsvNamespace.GNOMAD: ("gnomAD population frequency", CsvNamespaceGroup.ANNOTATION), CsvNamespace.SCORE_SET: ("Score set and target gene", CsvNamespaceGroup.PROVENANCE), CsvNamespace.RELATIONSHIP: ("Relationship to the requested variant", CsvNamespaceGroup.PROVENANCE), } diff --git a/src/mavedb/lib/csv/specs.py b/src/mavedb/lib/csv/specs.py index 157cbdf4..88e6d5f1 100644 --- a/src/mavedb/lib/csv/specs.py +++ b/src/mavedb/lib/csv/specs.py @@ -194,7 +194,15 @@ def _optional(getter: Callable) -> Callable: ), CsvNamespace.GNOMAD: CsvNamespaceSpec( source=RowSource.GNOMAD, - resolvers={"gnomad_af": _optional(lambda gnomad: gnomad.allele_frequency)}, + resolvers={ + "gnomad_af": _optional(attrgetter("allele_frequency")), + "gnomad_ac": _optional(attrgetter("allele_count")), + "gnomad_an": _optional(attrgetter("allele_number")), + "gnomad_faf95_max": _optional(attrgetter("faf95_max")), + "gnomad_faf95_max_ancestry": _optional(attrgetter("faf95_max_ancestry")), + "gnomad_id": _optional(attrgetter("db_identifier")), + "gnomad_version": _optional(attrgetter("db_version")), + }, needs_mappings=True, needs_gnomad=True, ), diff --git a/src/mavedb/scripts/resources/README.md b/src/mavedb/scripts/resources/README.md index f59b4c84..5aa37c8c 100644 --- a/src/mavedb/scripts/resources/README.md +++ b/src/mavedb/scripts/resources/README.md @@ -130,7 +130,13 @@ Columns are grouped by a namespace prefix. The groups below always appear: | `mavedb.post_mapped_hgvs_at_assay_level` | Post-mapped HGVS at the assay reference level (transcript or protein) | | `mavedb.post_mapped_vrs_id` | GA4GH VRS identifier for the post-mapped allele (e.g. `ga4gh:VA.n9ax-9x6gOC0OEt73VMYqCBfqfxG1XUH`) | | `vep.vep_functional_consequence` | VEP functional consequence term (e.g. `missense_variant`) | -| `gnomad.gnomad_af` | gnomAD v4.1 allele frequency | +| `gnomad.gnomad_af` | gnomAD allele frequency (allele count ÷ allele number) | +| `gnomad.gnomad_ac` | gnomAD allele count — chromosomes observed carrying the allele | +| `gnomad.gnomad_an` | gnomAD allele number — total chromosomes sampled | +| `gnomad.gnomad_faf95_max` | Maximum filtering allele frequency at 95% confidence across genetic ancestry groups | +| `gnomad.gnomad_faf95_max_ancestry` | Genetic ancestry group attaining `gnomad_faf95_max` | +| `gnomad.gnomad_id` | gnomAD variant identifier (`chrom-pos-ref-alt`), e.g. `17-43092919-G-A` | +| `gnomad.gnomad_version` | gnomAD release the frequencies were drawn from (e.g. `v4.1`) | | `clingen.clingen_allele_id` | ClinGen Allele Registry CA identifier (e.g. `CA12345`) | Two further groups vary by score set, because they exist only where MaveDB holds the underlying data. diff --git a/tests/lib/csv/test_columns.py b/tests/lib/csv/test_columns.py index 74dd588a..429dfe71 100644 --- a/tests/lib/csv/test_columns.py +++ b/tests/lib/csv/test_columns.py @@ -322,7 +322,22 @@ def test_quotes_values_containing_commas(self): {}, ), (["vep"], {"core", "vep"}, {"vep": ["vep_functional_consequence"]}, {}), - (["gnomad"], {"core", "gnomad"}, {"gnomad": ["gnomad_af"]}, {}), + ( + ["gnomad"], + {"core", "gnomad"}, + { + "gnomad": [ + "gnomad_af", + "gnomad_ac", + "gnomad_an", + "gnomad_faf95_max", + "gnomad_faf95_max_ancestry", + "gnomad_id", + "gnomad_version", + ] + }, + {}, + ), (["clingen"], {"core", "clingen"}, {"clingen": ["clingen_allele_id"]}, {}), (["scores", "mavedb"], {"core", "scores", "mavedb"}, {"scores": ["score"]}, {}), (["clinvar.2024_01"], {"core", "clinvar.2024_01"}, {}, {"clinvar.2024_01": "01_2024"}), diff --git a/tests/lib/csv/test_variant.py b/tests/lib/csv/test_variant.py index 9105e504..b7dc77e8 100644 --- a/tests/lib/csv/test_variant.py +++ b/tests/lib/csv/test_variant.py @@ -490,6 +490,45 @@ def test_post_mapped_vrs_id_is_never_synthesized_from_digest(self, session, setu assert rows[0]["mavedb.post_mapped_vrs_id"] == "NA" + def test_gnomad_namespace_reports_the_whole_frequency_record(self, session, setup_lib_db_with_mapped_variant): + """AF alone cannot be linked out from or judged for sampling depth; the namespace carries the record.""" + mapped_variant = setup_lib_db_with_mapped_variant + mapped_variant.gnomad_variants.append(GnomADVariant(**TEST_GNOMAD_VARIANT)) + session.add(mapped_variant) + session.commit() + + with patch("mavedb.lib.csv.fetch.GNOMAD_DATA_VERSION", TEST_GNOMAD_DATA_VERSION): + csv_text = get_variant_csv(session, mapped_variant.variant.urn) + rows = _parse_csv(csv_text) + + assert [column for column in rows[0].keys() if column.startswith("gnomad.")] == [ + "gnomad.gnomad_af", + "gnomad.gnomad_ac", + "gnomad.gnomad_an", + "gnomad.gnomad_faf95_max", + "gnomad.gnomad_faf95_max_ancestry", + "gnomad.gnomad_id", + "gnomad.gnomad_version", + ] + assert rows[0]["gnomad.gnomad_af"] == str(TEST_GNOMAD_VARIANT["allele_frequency"]) + assert rows[0]["gnomad.gnomad_ac"] == str(TEST_GNOMAD_VARIANT["allele_count"]) + assert rows[0]["gnomad.gnomad_an"] == str(TEST_GNOMAD_VARIANT["allele_number"]) + assert rows[0]["gnomad.gnomad_faf95_max"] == str(TEST_GNOMAD_VARIANT["faf95_max"]) + assert rows[0]["gnomad.gnomad_faf95_max_ancestry"] == str(TEST_GNOMAD_VARIANT["faf95_max_ancestry"]) + assert rows[0]["gnomad.gnomad_id"] == str(TEST_GNOMAD_VARIANT["db_identifier"]) + assert rows[0]["gnomad.gnomad_version"] == TEST_GNOMAD_DATA_VERSION + + def test_gnomad_record_absent_leaves_every_column_na(self, session, setup_lib_db_with_mapped_variant): + """A variant with no gnomAD record reports NA across the namespace, never a zero frequency.""" + mapped_variant = setup_lib_db_with_mapped_variant + + with patch("mavedb.lib.csv.fetch.GNOMAD_DATA_VERSION", TEST_GNOMAD_DATA_VERSION): + rows = _parse_csv(get_variant_csv(session, mapped_variant.variant.urn)) + + gnomad_values = {key: value for key, value in rows[0].items() if key.startswith("gnomad.")} + assert len(gnomad_values) == 7 + assert set(gnomad_values.values()) == {"NA"} + def test_gnomad_variant_from_another_version_is_not_reported(self, session, setup_lib_db_with_mapped_variant): mapped_variant = setup_lib_db_with_mapped_variant mapped_variant.gnomad_variants.append(GnomADVariant(**TEST_GNOMAD_VARIANT)) @@ -673,9 +712,9 @@ def record(conn, cursor, statement, parameters, context, executemany): for statement in statements if "score_calibrations" in statement and " variants" in statement.replace("\n", " ") ] - assert ( - calibration_scans == [] - ), "calibration discovery joined the variants table; it should filter on score_set_id" + assert calibration_scans == [], ( + "calibration discovery joined the variants table; it should filter on score_set_id" + ) def test_base_namespaces_are_all_present_by_default(self, session, setup_lib_db_with_mapped_variant): variant = setup_lib_db_with_mapped_variant.variant @@ -877,7 +916,7 @@ def test_entries_are_labeled_for_a_picker(self, session, setup_lib_db_with_mappe # A ClinVar release is named by its date. assert by_namespace["clinvar.2024_11"].label == "ClinVar significance (November 2024)" assert by_namespace["clinvar.2024_11"].group == "annotation" - assert by_namespace["gnomad"].label == "gnomAD allele frequency" + assert by_namespace["gnomad"].label == "gnomAD population frequency" assert by_namespace["score_set"].group == "provenance" diff --git a/tests/routers/test_variant.py b/tests/routers/test_variant.py index 14de3a9f..48f3fbd9 100644 --- a/tests/routers/test_variant.py +++ b/tests/routers/test_variant.py @@ -240,7 +240,7 @@ def test_score_set_namespaces_are_labeled_and_grouped( by_namespace = {entry["namespace"]: entry for entry in entries} assert {"scores", "score_set", "vep", "gnomad", "clingen"} <= set(by_namespace) assert "relationship" not in by_namespace - assert by_namespace["gnomad"]["label"] == "gnomAD allele frequency" + assert by_namespace["gnomad"]["label"] == "gnomAD population frequency" assert by_namespace["gnomad"]["group"] == "annotation" # Every entry is renderable without the client inventing labels. assert all(entry["label"] and entry["group"] for entry in entries)