Skip to content

just_dna_enricher.mitomap_draft

just_dna_enricher.mitomap_draft

Draft variants.csv + studies.csv rows from the MITOMAP-miss increment (RM171).

Only the rated misses are written, and each of the other three buckets is refused for its own reason rather than filtered out silently:

  • a photocopy is an allele ClinVar already publishes, and on the measured vintage every matched bracketed row carried reviewed_by_expert_panel. Drafting it would attribute the ClinGen mtDNA VCEP's call to the wrong publisher, and it would give a ClinVar concordance check a copy of ClinVar to disagree with — a check that cannot fail (@tautology-zero).
  • an unrated miss is a real identity increment with no class this tier may map: no bracket at all, a bare confirmation token, or [VUS*]. Counted, and no class is invented for it.
  • an unmintable row publishes no VCF-spellable allele. Counted, with the reason.

genotype is stubbed, and the reason is not the one the contig would give. ClinVar's drafter fills the ALT on chrMT through sole_expressible_genotype, on the argument that a haploid contig leaves no zygosity open — a correct argument about ClinVar, whose record is a claim about an allele. It is not called here, because MITOMAP's row is a claim about a literature corpus: its homo and hetero columns say the variant has been reported homoplasmic, heteroplasmic, or not recorded, and a large share of these rows are reported only heteroplasmically. Writing genotype=<ALT> on one of those states the homoplasmic reading — which is precisely the claim reference_examples/mt_heteroplasmy keeps in variants.csv and separates from its heteroplasmy.csv bins. So the cell is left for the author, and the flags MITOMAP did publish are put in front of them, per row, as the worklist.

state and conclusion are required too. state is folded from clin_sig through the shared STATE_BY_CLIN_SIG wherever that fold exists and stubbed where it does not; conclusion is always stubbed, because it is the sentence a reader is shown when a measurement lands here and MITOMAP publishes a disease name, which is a different claim at a different grain.

Appends, never mutates (@draft-appends), matched on the coordinate identity — MITOMAP publishes no rsID column at all, so there is no second identity for a row to arrive under.

MitomapDraftResult dataclass

MitomapDraftResult(
    reports: list[DraftReport] = list(),
    warnings: list[str] = list(),
    skipped: bool = False,
    candidates: int = 0,
    withheld: dict[str, int] = dict(),
    withheld_brackets: dict[str, int] = dict(),
    indel_keys: int = 0,
    indefinite_alleles: list[str] = list(),
    stale: dict[str, tuple[dict, dict]] = dict(),
    dataset: str | None = None,
)

What was drafted, and an account of every row in the increment that was not.

MitomapDraftError

Bases: RuntimeError

A draft could not be attempted — no miss snapshot, or an unwritable spec directory.

draft_panel_from_mitomap_miss

draft_panel_from_mitomap_miss(
    spec_dir: Path,
    genes: Sequence[str] = (),
    *,
    snapshot: Path | None = None,
    declared_use: str = "unstated",
    dry_run: bool = False,
) -> MitomapDraftResult

Append the rated half of the MITOMAP increment into a module's variants.csv/studies.csv.

genes filters on the gene MITOMAP's locus names, and the filter runs first: "the increment has nothing for this gene" and "it has something and this provider would not write it" are different answers and are counted apart. A locus naming two genes, or the control region, carries no gene and is therefore never selected by a filter — which is the honest consequence of withholding the attribution rather than guessing it.

Source code in enricher/src/just_dna_enricher/mitomap_draft.py
def draft_panel_from_mitomap_miss(
    spec_dir: Path,
    genes: Sequence[str] = (),
    *,
    snapshot: Path | None = None,
    declared_use: str = "unstated",
    dry_run: bool = False,
) -> MitomapDraftResult:
    """Append the rated half of the MITOMAP increment into a module's `variants.csv`/`studies.csv`.

    `genes` filters on the gene MITOMAP's `locus` names, and the filter runs **first**: "the increment
    has nothing for this gene" and "it has something and this provider would not write it" are
    different answers and are counted apart. A locus naming two genes, or the control region, carries
    no gene and is therefore never selected by a filter — which is the honest consequence of
    withholding the attribution rather than guessing it.
    """
    spec_dir = Path(spec_dir)
    result = MitomapDraftResult(
        withheld=dict.fromkeys(
            ("photocopy", "unrated_miss", "unmintable", "gene_not_requested", "incomplete_row"), 0
        )
    )

    # CC BY 3.0 states commercial and clinical use free, so this always answers `None`. Kept because
    # the gate is per source and a reader of this file should see which answer it gives.
    declared_use, declared_from = effective_declared_use(spec_dir, MITOMAP_TERMS, declared_use)  # S105
    refusal = check_declared_use(MITOMAP_TERMS, declared_use)
    if refusal is not None:  # pragma: no cover - unreachable while the terms stay permissive
        raise MitomapDraftError(refusal)

    reference = _resolve_snapshot(snapshot)
    rows = _snapshot_rows(reference, MISS_PARQUET)
    if not rows:
        raise MitomapDraftError(
            f"the MITOMAP-miss snapshot at {reference} holds no rows. Rebuild it with "
            f"`just-dna-enricher mitomap miss` — an empty increment and an unbuilt one are "
            f"different answers and this one cannot tell you which it is."
        )
    release = read_miss_release(reference)
    result.dataset = miss_dataset_label(release)
    result.stale = stale_parents(reference)

    wanted = {gene.strip().upper() for gene in genes if gene.strip()}
    admitted: list[dict] = []
    for row in rows:
        gene = (row.get("gene") or "").strip()
        if wanted and gene.upper() not in wanted:
            # Counted outside `candidates`, like every other provider's gene filter: a gene the
            # caller did not ask for is not something this provider withheld.
            result.withheld["gene_not_requested"] += 1
            continue
        admitted.append(row)
    result.candidates = len(admitted)

    drafted: list[dict] = []
    partials: list[PartialRow] = []
    incomplete: list[str] = []
    for row in admitted:
        bucket = str(row.get("bucket") or "")
        if bucket != "rated_miss":
            result.withheld[bucket if bucket in result.withheld else "unmintable"] += 1
            if bucket == "unrated_miss" and row.get("withheld_bracket"):
                result.withheld_brackets[str(row["withheld_bracket"])] = (
                    result.withheld_brackets.get(str(row["withheld_bracket"]), 0) + 1
                )
            continue
        cells = _cells(row)
        missing = _PROVIDER.precondition.missing(cells) if _PROVIDER.precondition else []
        if missing:
            # Not reachable from a well-formed miss snapshot — a rated miss has all five by
            # construction — and guarded anyway, because the alternative is a raw ValidationError
            # naming a column the author never wrote (`@specific-rejection`).
            result.withheld["incomplete_row"] += 1
            incomplete.append(f"{row.get('table')}/{row.get('record_id')} ({', '.join(missing)})")
            continue
        stubbed = tuple(name for name in (*_STUBBED, "state") if name not in cells)
        partials.append(PartialRow(model=VariantRow, cells=cells, stubbed=stubbed, match_on=_MATCH_ON))
        drafted.append(row)
        if str(row.get("key_shape")) == "indel":
            result.indel_keys += 1
        if indefinite_length(row.get("allele")):
            result.indefinite_alleles.append(
                f"{CONTIG}:{row.get('start')} {row.get('ref')}>{row.get('alt')} "
                f"(MITOMAP calls it {row.get('allele')!r})"
            )

    # The licence row lands inside each table's commit (RM232).
    commit_licence = licence_commit(
        sources=[MITOMAP_TERMS.source],
        spec_dir=spec_dir,
        dataset=result.dataset,
        declared_use=declared_use,
        error=MitomapDraftError,
    )
    if partials:
        result.reports.append(
            append_partial_rows(
                spec_dir, VARIANTS_CSV, partials, dry_run=dry_run, before_commit=commit_licence
            )
        )
        studies, unusable = _study_rows(drafted, _snapshot_rows(reference, CITATIONS_PARQUET))
        if studies:
            result.reports.append(
                append_rows(spec_dir, STUDIES_CSV, studies, dry_run=dry_run, before_commit=commit_licence)
            )
        if unusable:
            result.warnings.append(
                f"{unusable} MITOMAP citation(s) were refused by studies.csv's own model and were "
                f"not written."
            )

    result.warnings.extend(_notes(result, drafted, incomplete))

    covered = bool(result.reports) and any(
        outcome.status in {"added", "already_present"} for outcome in result.reports[0].outcomes
    )
    if not dry_run:
        # **The licensed source is `mitomap`, not the derived lane.** The increment is a computation
        # this repository performs; the *content* in the drafted rows is MITOMAP's, and it is MITOMAP's
        # attribution duty a published module has to carry (`@source-vs-authority`). The ClinVar half
        # of the pin rides in `dataset`, because the increment's identity really is both parents.
        result.warnings.extend(
            record_draft_provenance(
                provider=_PROVIDER,
                sources=[MITOMAP_TERMS.source],
                spec_dir=spec_dir,
                dataset=result.dataset,
                covered=covered,
                drafted=bool(result.added),
                declared_use=declared_use,
                error=MitomapDraftError,
            )
        )
    return result