just_dna_enricher.civic_refutation¶
just_dna_enricher.civic_refutation ¶
RM170 — an authored direction over a variant CIViC has published a refutation of.
The defect this closes is not a missing value. It is that an author can write direction=risk over a
variant the same snapshot also rebuts, and every gate stays green. contested_variants cannot see the
pair: it counts a variant whose camps hold both risk and protective, and a Does Not Support
row enters no camp at all — CIVIC_DIRECTION_MAP sends it to None, because a refutation removes a
claim without establishing the opposite one (@refutation-withholds). So that counter is correctly 0
on every basis, and the muddy variants are invisible to it.
The withhold is a slot beside the camps, never a member of them. A snapshot variant carries three
independent things: the camps its Supports rows fill, a withhold any Does Not Support row raises,
and — once a module exists — the authored direction. Nothing here sums them into a point. Combining
signs would answer what should the cell become?, and RM170 asked for a finding: an enricher check
reports and never repairs (@enrichment-is-validation), and RM152 already refused filling direction
from a source's disagreement with itself. The house three-valued algebra is Kleene over answers, not
a metric over signs, so risk beside a withhold does not become unknown — it stays risk with a
warning next to it.
Two findings, deliberately two codes, because they are different sentences with different subjects and one count over both would silently mix them:
refutation_beside_claim— the source asserts and refutes. The RM170 case, e.g. VHL G104V: accepted supporting item 7134 against submitted refutation 10949.refutation_without_claim— the source has only ever refuted, and the module asserts a sign anyway. Author-versus-refutation rather than source-versus-itself. CHEK2 788 and TP53 4968 are the corpus.
A finding keys on the refuting evidence item, then fans out to the authored rows it touches. CIViC
evidence item 8721 is one statement about the two-variant genotype VHL S183L AND VHL D126N
(molecular profile 5278), and the snapshot writes it as two single-variant rows (RM174). Keying on the
evidence id means it is reported as one refutation over two subjects rather than as two
independent ones, which is true today and stays true whichever way RM174 is repaired.
The status basis is part of the finding, not a weight. Every assert-and-refute pair in CIViC rests
on a submitted refutation — measured in docs/probes/CONTRADICTION_CORPORA.md, where the two accepted
refutations in the whole database turn out to stand against nothing at all. A snapshot built on the
accepted basis therefore has an empty subject class by construction, so a run that finds nothing
must publish the basis it looked on; silence that does not name its basis reads as clear water.
EvidenceRef
dataclass
¶
EvidenceRef(
evidence_id: str,
status: str | None,
direction_raw: str | None,
pmid: str | None = None,
)
One CIViC evidence item as this finding needs to quote it.
restate ¶
EID 10949 (submitted) — what a warning line puts in front of an author.
RefutedSubject
dataclass
¶
RefutedSubject(
variant_key: str,
genotype: str,
authored_direction: str,
civic_variant_id: str | None,
civic_variant_name: str | None,
gene: str | None,
)
One authored row a refutation touches.
RefutationFinding
dataclass
¶
RefutationFinding(
code: str,
refuting: tuple[EvidenceRef, ...],
supporting: tuple[EvidenceRef, ...],
subjects: tuple[RefutedSubject, ...],
status_basis: str | None,
)
One published refutation, and every authored row it lands on.
Keyed by the refuting evidence item rather than by the variant, so a combination-genotype refutation is one finding with two subjects instead of two findings that look independent.
combination
property
¶
True when one evidence item refutes a genotype spanning more than one authored subject.
restate ¶
The paragraph an author reads. Names both sides, their statuses, and the basis.
Source code in enricher/src/just_dna_enricher/civic_refutation.py
RefutationComparison
dataclass
¶
RefutationComparison(
subjects: int = 0,
unmatched: int = 0,
findings: list[RefutationFinding] = list(),
status_basis: str | None = None,
)
What was compared, and what was found. Never a stand-in for a check that could not run.
civic_snapshot_rows ¶
Every row of the CIViC snapshot parquet as plain dicts, or [] when there is none.
Public and shared, because a second reading pass wanted exactly this and a private name is how a
second caller stops finding the first (@roster-is-as-wide-as-the-tables-it-reads) — RM160's
citation lane maps a module onto CIViC variant ids off the same rows. civic_draft keeps its own
copy on purpose: that one raises on a missing snapshot because a draft with no source is a
failed command, while a check with no snapshot is a skip, and one function cannot be both.
Neither reader is imported at module scope for the reason civic_draft._snapshot_rows gives: a
reader of a built snapshot must not drag the builder's dev extra in behind it.
Source code in enricher/src/just_dna_enricher/civic_refutation.py
compare_refutations ¶
compare_refutations(
variants: Sequence[VariantRow],
resolution_rows: Sequence[ResolutionRow],
*,
reference: Path | None,
) -> RefutationComparison | None
Authored directions against the refutations CIViC publishes. None when nothing was compared.
None — never a comparison of zeros — when no snapshot was provisioned or the one that was is
unreadable. A check that could not run is not a check that passed, and the caller writes a
skipped verification record from it rather than ran, findings=0.
Matching goes through comparison_plan, the same resolved-coordinate route the ClinVar
cross-check uses, so both authorities are asked about the same alleles. Rows with no authored
direction are not subjects: the module makes no claim on this axis, so there is nothing for a
refutation to sit beside.
Source code in enricher/src/just_dna_enricher/civic_refutation.py
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 | |