just_dna_enricher.civic_api¶
just_dna_enricher.civic_api ¶
CIViC's GraphQL API — the one surface that publishes evidence the dated files cannot carry (RM160).
civic build reads a dated bulk release, and RM169 widened it as far as a dated file goes: the TSV
pair is accepted-only, and <date>-civic_accepted_and_submitted.vcf carries the submitted half
beside it, pinnable and byte-reproducible. A VCF record needs a POS. So an evidence item attached
to a variant CIViC publishes no GRCh37 coordinate for is published on exactly one surface — this one.
That is the whole of what this client is for, and it is a narrow claim: nothing else about the API is
richer than the files, and the three bulk summaries the builder does not read are not evidence tables.
Why it is not in the builder, and why that is not an omission. civic build is byte-reproducible
because its input is a pinned dated file pair, and civic reproduce proves it by building twice. An
API read has nothing to pin. RM160 was decided as shape 3 — read SUBMITTED at enrich time, where
network reads already live and reproducibility is never claimed — over the two shapes that would have
moved it into the build: hashing a capture as an input (reproducible against the capture rather than
against CIViC) and a second API-built parquet. The read is per variant by construction: the
evidenceItems query takes one variantId, so a corpus-wide read would be one request per variant of
the whole database. Batching it into civic build is the first repair anyone proposes and it is
exactly the bargain shape 3 refused.
CC0, so check_declared_use does not gate this (@acquisition-gate-is-not-a-read-gate): CIViC
permits sale and redistribution on every axis, so a use gate here would permit every call
unconditionally, and a flag feeding a gate that never gates is a flag that does nothing. --offline
does gate it, and it is a real refusal rather than an empty answer — a variant nobody asked about and
a variant the API says has nothing more are different facts (@unreachable-not-absent).
Three outcomes, never two. Items returned; the API answered and holds none (an empty list); and
the question could not be put at all (CivicApiUnavailable). The middle one is a return value and the
last one is an exception, the same split litvar draws, because a caller that flattens them turns an
outage into a permanent negative about a variant.
Statuses are CIViC's own words, lower-cased and not otherwise touched. The API serves ACCEPTED,
SUBMITTED and REJECTED; civic_vcf writes the first two lower-cased, and one vocabulary spelled
two ways by two surfaces of one source gets one normalizer (@one-normalizer-two-spellings), never a
translation into a house grade. A status outside the three raises rather than defaulting: the file
reader refuses the same way, because every count keyed on the vocabulary is wrong the moment a member
is silently absorbed (@lookup-with-a-default-hides-a-new-member).
The listing paginates and states its own total. evidenceItems served 37 items for variant 844 on
2026-09-03 in pages, and a reader taking the first page would have reported a variant with 34 hidden
citations as having four. totalCount sits in the same payload as the nodes, so the two are compared
and a short read warns rather than passing silently — the check is in the response, exactly as
LitVar's pmids_count is.
CivicApiError ¶
Bases: RuntimeError
CIViC could not be consulted in a way the caller must handle.
CivicApiUnavailable ¶
Bases: CivicApiError
The service was not reachable, so the question was never put.
A subclass rather than a second exception, so every existing except CivicApiError still fires
while a caller that must separate CIViC has nothing here from CIViC never answered can do it
by type rather than by reading __cause__ (@client-exception-contract). Only this one means
nobody was asked — a payload this client cannot read is a CivicApiError, because the service did
answer and the defect is in the reading.
CivicEvidenceItem
dataclass
¶
CivicEvidenceItem(
evidence_id: int,
variant_id: int,
status: str,
evidence_type: str | None = None,
evidence_direction: str | None = None,
evidence_level: str | None = None,
evidence_rating: int | None = None,
significance: str | None = None,
variant_origin: str | None = None,
source_id: int | None = None,
source_type: str | None = None,
citation_id: str | None = None,
title: str | None = None,
citation: str | None = None,
publication_year: int | None = None,
molecular_profile_id: int | None = None,
molecular_profile_name: str | None = None,
)
One evidence item as this lane needs to quote it.
Every field is what CIViC published, coerced and never converted. status is lower-cased and
nothing else; citation_id is echoed verbatim and pmid withholds unless the source really is a
PubMed record.
pmid
property
¶
The PubMed id, or None where this item's source is not a PubMed record.
Withheld rather than guessed: an ASCO or ASH abstract carries a citationId that is a real
identifier in a different namespace, and writing one into a pmid column would state a
PubMed record that does not exist. A citationId that is not digits is refused for the same
reason — CIViC's PubMed ids are bare numbers.
restate ¶
EID 9969 (submitted, PMID 12202531) — what a warning line puts in front of an author.
Source code in enricher/src/just_dna_enricher/civic_api.py
CivicApiClient ¶
CivicApiClient(
*,
url: str = CIVIC_API_URL,
client: Client | None = None,
gate: PacingGate | None = None,
offline: bool = False,
)
The one CIViC GraphQL call this lane needs, paced, retried, paginated and translated.
Hold one per run: the per-variant answers are cached on the instance, so a variant reached from
two authored tables is one request, and the pacing gate is shared across every call the run makes
rather than reset per question (@shared-pacing-gate).
offline=True is a refusal, not an empty answer: every call raises CivicApiUnavailable
before any transport is touched. A caller that wants "nobody asked" recorded gets it from the
exception; a caller that reads an empty list as "CIViC has nothing" would otherwise write a
permanent negative out of a run that had no egress.
Source code in enricher/src/just_dna_enricher/civic_api.py
offline
property
¶
Whether this client will refuse rather than fetch. Read by a caller writing its skip.
evidence_items ¶
Every evidence item CIViC holds for one variant, at any curation status.
An empty list is an answer — CIViC was asked and holds nothing for this variant — and a
CivicApiUnavailable is the third outcome, the question that was never put. The caller must
keep them apart; that is the whole reason the absence is a value and the failure is a type.
One variant per call by construction, which is why this lane lives at enrich time: the query
takes a single variantId, so there is no shape of it that a snapshot builder could pin.
Source code in enricher/src/just_dna_enricher/civic_api.py
normalize_status ¶
CIViC's ACCEPTED/SUBMITTED/REJECTED as this workspace spells it, or this tier's error.
Case is the only thing that moves. A member outside the three raises rather than being absorbed into a default, because the vocabulary is the instrument every count in this lane is keyed on — the file reader refuses an unknown status for the identical reason, and the two must not disagree about what a status is.