just_dna_enricher.gene_metrics¶
just_dna_enricher.gene_metrics ¶
enrich-gene-metrics — the third pass: the module's genes in, gene_metrics.csv out.
The gene-level sibling of the frequency pass, and the one gnomAD role that works fully offline.
The difference is size, not principle: a frequency slice of v4.1 would be tens to hundreds of
gigabytes, while gene-level constraint is one row per gene — a few megabytes as parquet. So this pass
gets the ClinVar treatment (a [dev] builder plus a cached snapshot, constraint_build) with the
live API as the fallback, rather than the other way round.
Snapshot first, live second — the same ordering rule the resolver chain uses, and for the same reason: a local hit costs nothing and leaves the rate limit alone for the passes that genuinely need it.
The two routes are not interchangeable, and the table says so. Checked against both: the bulk v4.1
file and the live gnomad_constraint field return different numbers for the same gene on the same
MANE transcript, because the live field serves v2.1.1 constraint while v4.1 constraint ships only
in the bulk file. So a row records which release it came from in dataset — gnomad_v4.1_constraint
from the snapshot, gnomad_v2.1.1_constraint from the API — and a module that gets the API fallback
because no snapshot was provisioned can see that it holds older numbers rather than silently
believing they are v4.1. This is exactly the confusion dataset is in the fact set to prevent.
The gene set comes from the gene column of variants.csv. That is the module saying which genes it
is about; querying anything else would be inventing scope the author did not ask for.
GeneMetricsEnrichmentError ¶
Bases: RuntimeError
Raised in strict mode when a module gene gets no constraint metrics.
GeneMetricsUnavailable ¶
Bases: GeneMetricsEnrichmentError
The gnomAD constraint API could not be reached, so the question was not put (RM101).
A subclass rather than a second exception, so every existing except GeneMetricsEnrichmentError still
catches it (P3 — additive within a major). It exists because this pass could fail two ways that
want different responses and had one type for both: the API was asked and never answered, and a module gene genuinely has no constraint metrics under strict. Only this one means the
source was asked and never answered.
Before RM101 this case did not reach GeneMetricsEnrichmentError at all — a GnomadError travelled straight out
through a try/finally with no except, so a caller's handler, written against the type this
module documents, was silent for exactly the failure it was written for.
module_genes ¶
The module's gene symbols, de-duplicated in first-occurrence order (P7: never set order).
Every authored table that carries gene, derived from the registry rather than named (RM157).
This read variants.csv alone while nine models declare the column, and it is the gene set three
passes take their scope from — constraint metrics, gene validity and the ClinGen dosage pass — so
a module whose genes live in its PGx tables had all three quietly do nothing. Measured on this
repo's own corpus: cyp2c19_star_alleles, apoe_epsilon, cyp2c9_warfarin_grch37 and
hfe_compound_het returned [] here while naming CYP2C19, APOE, CYP2C9, VKORC1, CYP4F2 and HFE
on rows an enrichment could have asked about. The workspace was already carrying two answers to
one question: pgx._module_genes reads two PGx tables, and this one read a table those modules do
not have.
pgx._GENE_TABLES stays as it is and is not the same roster: it is the pair whose presence
decides whether the star-allele cross-check applies at all, which is a question about that check's
inputs rather than about what the module is about.
Refuses on a table that will not parse, in the phrasing this pass already used. The roster
itself is a reporting surface and routes an unreadable table to not_read, but three passes take
their scope from this function and a half-read scope is a silently narrowed one — the same defect
one table wider. read_errors carries the loader's own message so the sentence is unchanged for
variants.csv, which is what gene_validity re-raises as its own error type.
Source code in enricher/src/just_dna_enricher/gene_metrics.py
lookup_snapshot ¶
gene symbol -> metrics dict from the offline constraint snapshot. Never fetches.
Mirrors clinvar.lookup_loci's shape: a batch lookup over an injected parquet directory via an
in-memory DuckDB view, so the core install stays polars-free (polars is the builder's, [dev]).
Source code in enricher/src/just_dna_enricher/gene_metrics.py
enrich_gene_metrics ¶
enrich_gene_metrics(
spec_dir: Path,
*,
mode: str = "best_effort",
offline: bool = False,
constraint_cache: Path | None = None,
dataset: str = CONSTRAINT_DATASET_LABEL,
download: bool = True,
write: bool = True,
client: GnomadClient | None = None,
) -> GeneMetricsResult
Fill gene_metrics.csv for the genes variants.csv mentions.
Existing rows are authoritative and merged, never clobbered — the same rule the other two passes
apply. offline restricts the chain to the snapshot, which (unlike the frequency pass) can still
produce a complete table when one is provisioned.
download provisions the published v4.1 snapshot when no local one is found, exactly as enrich()
does for the Ensembl and ClinVar snapshots — --offline is the switch that turns it off, and there
is deliberately no separate CLI flag for the same reason there is none there.
Source code in enricher/src/just_dna_enricher/gene_metrics.py
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 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 | |