just_dna_enricher.clingen¶
just_dna_enricher.clingen ¶
ClinGen dosage sensitivity — the gene-level authority beside gnomAD's constraint (0.5).
gnomAD tells you how intolerant of variation a gene looks in a population sample; ClinGen tells you
whether a curated expert panel found evidence that losing (haploinsufficiency) or gaining
(triplosensitivity) a copy actually causes disease. Different questions, different evidence, so they
are separate rows in gene_metrics.csv sharing a gene and naming their own dataset — never merged
into one row, which would put a statistical estimate and a curated verdict under one provenance.
Free, and that matters here. Every pharmacogenomics upstream this workspace touches is
CC BY-SA plus a bar on sale; ClinGen is not, so a module built on dosage sensitivity stays sellable.
The SourceRow this pass emits records that rather than leaving it to be assumed.
Three shapes in the source file that would break a naive reader, all found by reading the real file rather than its documentation:
- The ratings are numeric codes that are not ordinal.
{0,1,2,3}grade increasing evidence, but30means "gene associated with autosomal recessive phenotype" and40means "dosage sensitivity unlikely". Sorting on the raw number ranks40above3— the reverse of the meaning — so the codes are decoded tovocab.VALID_DOSAGE_SENSITIVITYterms at this boundary and never stored raw. - The triplosensitivity column carries a literal
"Not yet evaluated"(210 of 1,520 genes at the 2026-08-01 release). It is an absence, not a rating, so it becomesNone— and it is what makes anint(cell)reader crash on one file in seven. - The file starts with six
#comment lines, the last of which is the real header, so it is neither a plain TSV nor a comment-free one.
Reports, never repairs, like every other check in this tier: a gene ClinGen has not curated gets no row rather than a fabricated "no evidence" one, because "not curated" and "curated as no evidence" are different facts and the file distinguishes them.
ClinGenError ¶
Bases: RuntimeError
A ClinGen fetch or parse failed in a way the caller must see.
ClinGenUnavailable ¶
Bases: ClinGenError
The curation list could not be fetched, so ClinGen was never actually asked (RM101).
A subclass rather than a second exception, so every existing except ClinGenError still catches
it (P3 — additive within a major). It exists because ClinGenError covered two opposite
histories: the curation list could not be fetched (fetch_curation_list), or a local
gene_metrics.csv this module was handed will not parse. Only the first means the source was
asked. A caller could separate them until now only by reading exc.__cause__ — chained from
httpx.HTTPError for the fetch and raised bare for the table — which is a private detail to
depend on, and the alternative of matching the message is worse: neither string is pinned as an
API, so a reword would silently flip a consumer's verdict from "unchecked" to "your table is
broken".
DosageRating
dataclass
¶
DosageRating(
gene: str,
haploinsufficiency: str | None = None,
triplosensitivity: str | None = None,
)
One gene's curated dosage sensitivity, decoded.
decode_rating ¶
One ClinGen score cell → a VALID_DOSAGE_SENSITIVITY term, or None.
None for blank, for "Not yet evaluated", and for any code the mapping does not know — an
unrecognized code is a signal that ClinGen added a rating this release does not model, and
guessing at it would be worse than recording nothing.
Source code in enricher/src/just_dna_enricher/clingen.py
parse_curation_list ¶
Parse the gene-curation TSV → {gene: DosageRating} plus the release date it declares.
The release line (#01 Aug,2026) is the only version this file carries — ClinGen publishes no
numbered release — so it becomes the dataset label the same way the ClinPGx snapshot uses its
CREATED_<date> marker.
Source code in enricher/src/just_dna_enricher/clingen.py
fetch_curation_list ¶
Download the gene-curation list (a few hundred KB — small enough to fetch whole).
Source code in enricher/src/just_dna_enricher/clingen.py
enrich_dosage_sensitivity ¶
enrich_dosage_sensitivity(
spec_dir: Path,
*,
mode: str = "best_effort",
declared_use: str = "unstated",
offline: bool = False,
write: bool = True,
curation_text: str | None = None,
url: str = DEFAULT_CLINGEN_URL,
) -> ClinGenResult
Add ClinGen dosage rows to gene_metrics.csv for the genes variants.csv names.
Existing rows are authoritative and merged, never clobbered — the standing rule for every pass. A gene ClinGen has not curated is reported as missing and gets no row: unlike gnomAD's "looked up, genuinely absent", ClinGen's silence means nobody has assessed this yet, which is not a fact about the gene.
A pass that covers nothing writes no licence row either (S77). licensing.csv travels to the
registry and is read as this module uses this source; recording ClinGen for a module ClinGen
curates no gene of is a false statement in a published artifact, and it fires the
licence-disagreement warning against a conflict that does not exist. ClinGenResult.source_row is
still populated, so a caller can see the terms of what was consulted — that is a different fact
and it has a different home.
offline (RM39). This was the one pass in the family without the flag, so it downloaded the
curation TSV unconditionally and the only way to stop it was to inject curation_text= — which
requires the caller to have fetched the thing already, i.e. to have solved the problem the
parameter would solve. A caller running the family under one switch had to know, out of band, that
one member ignored it, and the cost of forgetting was silent egress from a path
ENRICHER.md documents as making none. enrich_frequencies is the shape
copied: a no-op with a warning, reported as skipped_offline, never a failure. An injected
curation_text still wins, because that is not egress; ClinGen has no snapshot and this
deliberately does not add one (that is RM38's family, and a much bigger question).
Source code in enricher/src/just_dna_enricher/clingen.py
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 | |