just_dna_enricher.pubmind_draft¶
just_dna_enricher.pubmind_draft ¶
Draft variants.csv rows from the PubMind snapshot (RM134 § C) — draft-panel --source pubmind.
A flag on the existing command, never a second command. draft-pubmind would write the same
tables from the same gene argument, so it would carry its own copy of the genotype worklist, the
placeholder guard, the dedup-against-the-file pass and the refusal summary — the four parts that are
hard to get right and the four that have each been fixed here at least once. draft-clinpgx is a
separate command because it writes different tables; this does not. So the machinery below is
imported from clinvar_draft rather than copied, including the worklist seam whose once-only
scoping was the bug RM71 removed: a second copy of that rule is the copy that goes stale.
PubMind publishes no gene column, and this module does not invent one. The snapshot is
(chrom, start, ref, alt) and nothing else locational — a verdict about a position, keyed to the text
an LLM extracted rather than to a locus. Turning --gene BRCA1 into a set of positions therefore
needs a gene→locus map, and this repo deliberately holds none: the compiler's own gene/locus check is
chromosome-granular precisely because gene boundaries are not ours to state. So the map is
ClinVar's own per-record gene attribution, matched at the exact position — every position ClinVar
records for the gene, with no clinical or review filter, since the map is a locus universe and not a
selection. A min/max span over those positions would be a boundary nobody defined, and would write a
gene cell that is a false claim wherever two genes overlap.
The consequence is a scope, stated rather than counted: a PubMind verdict at a position ClinVar has no record for cannot be reached by gene at all, and that class is not countable — attributing it to a gene is the very thing there is no map for. It includes the codon-decomposed offsets, which are PubMind's own back-mapping of a protein change and frequently land where nobody has filed a record.
Identity is the whole coordinate or nothing (@identity-whole-or-none). Most PubMind rows carry
no rsID — the snapshot has no rsID column at all — so chrom/start/ref/alts go in together,
and the row matches on the same five columns a ClinVar-drafted row does, so the two providers dedupe
against each other at one event rather than writing it twice.
What is withheld is named (@unreachable-not-absent). Four classes never become a row — a
contested key, a length-changing row, a call outside --clin-sig, and a confidence below the floor
or not stated at all — and each is counted under exactly one reason and reported with that reason.
PUBMIND_WITHHELD_REASONS is walked rather than restated, so candidates == drafted + Σ withheld is
an equality over the set (@registry-completeness).
A contested key is withheld, not resolved. Where the PVIDs at one coordinate disagree about the
call, choosing one means an ordering nobody defined — mode() over an unsorted group, which the
deterministic-ordering rule bans outright. The multiplicity is the finding: the coordinate, its PVIDs
and its competing calls are all reported, and no row is written. Contestation is decided over every
PVID at the key before --clin-sig and --min-confidence are applied, because a filter that
removes the dissenting record has picked the winner just as surely as mode() would.
Terms are unknown, and unknown warns rather than gates (@no-named-licence). check_declared_use
is a gate on fetching, and its unknown branch skips — correctly, for a pass that would go and get
data whose terms nobody can state. Nothing is fetched here: there is no ensure_pubmind_snapshot by
design, the operator built the snapshot themselves with pubmind build, and refusing to read it would
make that command's output a file nothing may consume. So the reason is reported in the source's own
words and the draft proceeds, exactly as the GWAS Catalog pass does with the same null terms. What the
unknown answer gates is publishing a module carrying those bytes, which is the redistribution axis
nothing in this repo designs yet.
What it will not fill, each a rule rather than an omission:
clinvar,pathogenic,benign— all three are ClinVar flags by their own field descriptions, and a position appearing in ClinVar's gene map says nothing about whether this allele is in ClinVar (@field-description-is-a-claim).phenotype— the ANNOVAR-redistributed channel carries no condition, and PubMind's per-record detail is withheld from it.- a study row — the same channel carries no PMID, so a PubMind draft grounds nothing and says so.
weight,direction,trait_efo_id,curator,method— as for ClinVar, and for the same reasons.
PubMindDraftError ¶
Bases: RuntimeError
A PubMind panel draft could not be completed.
PubMindDraftResult
dataclass
¶
PubMindDraftResult(
reports: list[DraftReport] = list(),
warnings: list[str] = list(),
candidates: int = 0,
drafted: int = 0,
withheld: dict[str, int] = dict(),
mapped_positions: int = 0,
spoken_positions: int = 0,
)
What a PubMind panel draft did — and, in equal detail, what it did not write.
accounts_for_every_candidate ¶
candidates == drafted + Σ withheld — the equality PUBMIND_WITHHELD_REASONS exists for.
Walked rather than listed, so a sixth reason cannot arrive without joining the sum.
Source code in enricher/src/just_dna_enricher/pubmind_draft.py
select_by_positions ¶
Every PubMind record at one of these exact positions — every PVID, never a winner.
A range query per contig, narrowed to the exact positions afterwards: the alternative is an IN
list of tens of thousands of positions, and the alternative to that is a span, which is the one
thing this module refuses to infer. Ordered deterministically so a re-draft offers the same rows
in the same order (Principle 7).
Source code in enricher/src/just_dna_enricher/pubmind_draft.py
gene_positions ¶
(chrom, start) → the requested gene(s) ClinVar attributes a record at that position to.
No clinical or review filter, deliberately: this is a locus universe rather than a selection, and the dials an author sets are about which PubMind verdicts to take. Filtering the map instead would silently narrow which positions PubMind is even asked about, and the narrower map is invisible in the output.
A position ClinVar attributes to two of the requested genes keeps both, so nothing here picks a gene either.
Source code in enricher/src/just_dna_enricher/pubmind_draft.py
draft_gene_panel_from_pubmind ¶
draft_gene_panel_from_pubmind(
spec_dir: Path,
genes: Sequence[str],
*,
snapshot: Path | None = None,
pubmind_snapshot: Path | None = None,
clin_sig: frozenset[str] = DEFAULT_CLIN_SIG,
min_confidence: int = DEFAULT_MIN_CONFIDENCE,
declared_use: str = "unstated",
offline: bool = False,
download: bool = True,
dry_run: bool = False,
) -> PubMindDraftResult
Draft variants.csv rows for one or more genes from PubMind, leaving genotype to the human.
Two snapshots, and each is doing a different job: snapshot is the ClinVar one, read for its
per-record gene attribution and nothing else, and pubmind_snapshot is the operator-built PubMind
one the verdicts come from. Neither is optional, and the messages say which is missing — an
author who has one and not the other should not have to guess.
Re-runnable and additive, exactly as the ClinVar path is: a key already in the file — stub or filled — is reported rather than re-added, and the open-stub worklist is read off the file rather than off this run, so asking twice answers twice.
Source code in enricher/src/just_dna_enricher/pubmind_draft.py
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 | |