CIViC survey — what the source actually contains, measured¶
Probed 2026-08-31 against https://civicdb.org/api/graphql, the public GraphQL API. No key, no
authentication, no rate-limit headers observed. Licence CC0 1.0 Universal for the content (the MIT
licence covers their application source, not the data) — so redistribution and commercial_use are
both permitted and licensing.py's existing cc0 entry already spells it. It asks nothing in return — no share-alike, no bar on sale, attribution requested rather than required — which is what distinguishes it from the several sources here that also permit redistribution.
This is evidence, never contract — the standing rule for everything under docs/probes/. Nothing
here is a decision. The decisions live in ROADMAP.md § RM152 and in the
0.7 proposal addendum; where this document and those disagree, they win.
Why it exists. RM152 was filed from S84's report, then its ROADMAP section was destroyed by a concurrent edit and rebuilt from two surviving records — its prose is a reconstruction and its own block-quote warns a reader to suspect it. The numbers below are the primary record, each one paired with the query that produced it, so the next reader neither re-runs these queries blind nor trusts a reconstruction. Every figure obtained by subtraction rather than by querying is flagged as such; there is one, and S84's original report is where it came from.
How to re-derive any number here¶
Every count is a totalCount on a filtered evidenceItems or assertions connection. The whole
survey is reproducible with:
import json, urllib.request
URL = "https://civicdb.org/api/graphql"
def gq(query, variables=None):
body = {"query": query} | ({"variables": variables} if variables else {})
req = urllib.request.Request(URL, data=json.dumps(body).encode(),
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=60) as r:
d = json.load(r)
if "errors" in d:
raise RuntimeError(d["errors"])
return d["data"]
def count(**kw): # e.g. count(variantOrigin="RARE_GERMLINE")
args = ", ".join(f"{k}: {v}" for k, v in kw.items())
return gq("{ evidenceItems(%s) { totalCount } }" % args)["evidenceItems"]["totalCount"]
Paging uses first: 50 with pageInfo { hasNextPage endCursor } and an after: cursor. Enum members
come from introspection, { __type(name: "VariantOrigin") { enumValues { name } } }, rather than from
the documentation — which is the @probe-the-real-file rule applied to an API.
The vocabularies, from introspection¶
| Enum | Members |
|---|---|
VariantOrigin |
SOMATIC, RARE_GERMLINE, COMMON_GERMLINE, UNKNOWN, COMBINED, MIXED, NA |
EvidenceType |
DIAGNOSTIC, PROGNOSTIC, PREDICTIVE, PREDISPOSING, FUNCTIONAL, ONCOGENIC |
EvidenceDirection |
SUPPORTS, DOES_NOT_SUPPORT, NA |
EvidenceLevel |
A, B, C, D, E |
EvidenceSignificance |
24 members, spanning several axes at once — SENSITIVITYRESPONSE, RESISTANCE, BETTER_OUTCOME, POOR_OUTCOME, POSITIVE, NEGATIVE, NA, ADVERSE_RESPONSE, PATHOGENIC, LIKELY_PATHOGENIC, BENIGN, LIKELY_BENIGN, UNCERTAIN_SIGNIFICANCE, REDUCED_SENSITIVITY, GAIN_OF_FUNCTION, LOSS_OF_FUNCTION, UNALTERED_FUNCTION, NEOMORPHIC, UNKNOWN, DOMINANT_NEGATIVE, PREDISPOSITION, PROTECTIVENESS, ONCOGENICITY, LIKELY_ONCOGENIC |
EvidenceSignificance is not one axis. It carries therapy response, prognosis, ACMG clinical
significance, protein-function effect and predisposition in a single field — which is why no single
map from it into this format's vocabularies exists, and why the useful question is always
significance × evidenceType × evidenceDirection rather than significance alone.
The denominator nobody declared — read this before quoting any number¶
evidenceItems and assertions both default to status: NON_REJECTED. Not ACCEPTED, not
ALL. Every figure in S84's report, in RM152, and in this document rides on that default, and none of
them said so:
| default | NON_REJECTED |
ACCEPTED |
SUBMITTED |
REJECTED |
ALL |
|
|---|---|---|---|---|---|---|
| Evidence items | 11,518 | 11,518 | 4,904 | 6,614 | 421 | 11,939 |
| Assertions | 296 | 296 | 147 | 149 | 18 | 314 |
So SUBMITTED is the majority of CIViC — 6,614 of 11,518 — and SUBMITTED means an item a
curator entered that no editor has signed off. A survey that reads the default is reading mostly
unreviewed content. This is the @probe-the-real-file rule biting on an API: the documented totals and
the served totals differ by a filter the schema applies silently.
Nothing here is wrong because of it — the default is a reasonable basis and the comparison to
ClinVar is roughly like-for-like — but any adoption must state the basis, and the numbers move a lot
when it changes. See the curation-status section below, where restricting to ACCEPTED takes the
contested count to zero.
Scale, and the germline fraction¶
| Evidence items | |
|---|---|
Total (NON_REJECTED, the default) |
11,518 |
SOMATIC |
7,376 |
RARE_GERMLINE |
3,018 |
COMMON_GERMLINE |
85 |
UNKNOWN |
374 |
NA |
627 |
COMBINED |
20 |
MIXED |
18 |
The seven buckets sum to 11,518 exactly, so the enum partitions cleanly and no item is missing an
origin. S84 reported the last three as 412 by subtraction; queried individually they are 374 + 20 +
18 = 412, so the subtraction was sound. Germline is RARE_GERMLINE + COMMON_GERMLINE = 3,103,
26.9%.
CIViC is a somatic cancer-interpretation resource. That is not a criticism of the source — it is what the source is for. The consequence for this format is only that 73% of it describes tumour tissue no consumer's germline genotype can satisfy.
The clinical-significance axis: why the concordance route died¶
Of the 3,103 germline items, the ACMG five-tier — the only members VALID_CLIN_SIG can receive — is:
| Germline items | |
|---|---|
UNCERTAIN_SIGNIFICANCE |
594 |
PATHOGENIC |
4 |
LIKELY_PATHOGENIC |
1 |
BENIGN |
0 |
LIKELY_BENIGN |
0 |
| five-tier total | 599 |
The largest single germline significance is NA at 812. The full germline histogram sums to 3,103:
PREDISPOSITION 1,456 · NA 812 · UNCERTAIN_SIGNIFICANCE 594 · SENSITIVITYRESPONSE 121 ·
LOSS_OF_FUNCTION 24 · POOR_OUTCOME 23 · GAIN_OF_FUNCTION 14 · RESISTANCE 13 ·
BETTER_OUTCOME 12 · ADVERSE_RESPONSE 11 · POSITIVE 6 · DOMINANT_NEGATIVE 5 · PATHOGENIC 4 ·
NEOMORPHIC 2 · ONCOGENICITY 2 · PROTECTIVENESS 2 · LIKELY_PATHOGENIC 1 · NEGATIVE 1.
The operative number is 5, not 3,103. The clin_sig concordance check's entire product is
opposition — a pathogenic-class call set against a benign-class one — and concordance.py states in
terms that an uncertain call opposes nothing and sits in the undecided camp. An authority holding 5
calls in one camp and 0 in the other cannot make discordant sayable about anything; joined, it
would read single or concordant by construction.
The direction axis: where the germline mass actually sits¶
By evidence type, the germline subset is 2,867 of 3,103 PREDISPOSING. By significance, the
direction-bearing pair is PREDISPOSITION 1,456 + PROTECTIVENESS 2 = 1,458. Crossed with
evidenceDirection:
SUPPORTS |
DOES_NOT_SUPPORT |
NA |
|
|---|---|---|---|
PREDISPOSITION |
1,452 | 4 | 0 |
PROTECTIVENESS |
2 | 0 | 0 |
Direction NA is 0 of 1,458. Every germline predisposition/protectiveness item carries a stated
direction — which is the one respect in which this axis is better populated than clin_sig, where
812 of 3,103 are NA.
Mapped onto this format's VALID_DIRECTIONS (protective / risk / neutral / unknown /
contested), the camps are 1,452 risk-class against 2 protective-class. That is the same shape
that killed the clin_sig route, with a smaller minority camp.
Contests, at variant granularity — and the grouping that gets this wrong¶
Group by variant, never by molecular profile. A molecular profile may name several variants, and
one variant may appear in several profiles, so profile-grouping understates contests. Measured both
ways over the same 1,458 rows: profile-grouping finds 1 contested subject, variant-grouping finds
3. The two extra are real and the mechanism is worth stating — MP5278 is a two-variant profile
(VHL S183L AND VHL D126N) carrying the single DOES_NOT_SUPPORT item eid 8721, which propagates to
both member variants, and each of those carries SUPPORTS evidence on its own separate
single-variant profile. Nine variants in this set appear in more than one profile.
Exploded over molecularProfile.variants[]: 620 distinct variants, 1,464 (variant, evidence) pairs,
213 variants with more than one item. Camp sets: {risk} 613 · {risk, not_risk} 3 ·
{not_risk} 2 · {protective} 2. No variant reaches three camps.
The three contested variants, in full:
| Variant | Camps | Items |
|---|---|---|
2161 VHL S183L (c.548C>T) |
risk, not_risk | ev 10733 SUPPORTS C PMID 34439168 · ev 5797 SUPPORTS C PMID 24466223 · ev 8721 DOES_NOT_SUPPORT C PMID 21454469 |
2428 VHL G104V (c.311G>T) |
risk, not_risk | ev 7134 SUPPORTS C PMID 29789510 · ev 10949 DOES_NOT_SUPPORT C PMID 33618821 |
2533 VHL D126N (c.376G>A) |
risk, not_risk | ev 10770 SUPPORTS C PMID 28043156 · ev 8721 DOES_NOT_SUPPORT C PMID 21454469 |
All three are risk against not_risk — a claim and its refutation. Genuine opposition
(risk against protective) is 0. Two further variants carry only {not_risk} (variant 788
CHEK2 IVS2+1G>A ev 1854; variant 4968 TP53 R72P ev 1302) — a lone refutation with nothing
asserting the claim, which is not contested. Two carry only {protective} (variant 4980
AXIN2 rs143348853 ev 12065; variant 258 MTHFR A222V ev 1756).
So RM152's "PREDISPOSITION × DOES_NOT_SUPPORT is 4 items, precisely the reading contested was
added for" is wrong in both directions: the contested count at the right granularity is 3, not 4,
and two of the four items it counts are lone refutations that contested does not describe.
Widening the scope changes nothing, and that is the useful result¶
All 620 variants were re-swept with no origin, significance, type or status filter —
evidenceItems(variantId: N) paged whole, 2,811 distinct evidence items. (Paging at first: 200, not
50: variant 1747 carries 103 items and 1739 carries 99.)
- Origins now in scope:
RARE_GERMLINE2,590 ·SOMATIC172 ·UNKNOWN20 ·COMMON_GERMLINE17 ·COMBINED10 ·NA7 ·MIXED4. - 1,342 of those items are campless —
NA660,UNCERTAIN_SIGNIFICANCE499, and the rest on therapy/prognosis/function axes. They cannot create a contest. - 11 new camp-bearing items appear, every one
PREDISPOSITION/SUPPORTS, all on VHL variants that already carriedrisk. - Additional contested variants found by widening: 0. Not one variant's camp set changed.
Scope of that zero (@probe-names-the-table): it is over the 620 variants that appear in the 1,458
germline direction rows. A CIViC variant whose only camp-bearing evidence has a non-germline origin was
never a candidate and was not probed. This is not a statement about CIViC as a whole.
The curation-status axis, which nothing in the report or the roadmap mentions¶
Every evidence item carries a status, and it is not evenly distributed: of the 1,458 germline
direction rows, 925 are SUBMITTED and 533 ACCEPTED. Over the widened 2,811, it is 2,091
SUBMITTED to 729 ACCEPTED. SUBMITTED means an item a curator has entered and no editor has
signed off.
Restricted to ACCEPTED, the contested count is 0. All three contests dissolve: eids 10949 and
8721 are SUBMITTED, and for variants 2161 and 2533 the SUPPORTS side is SUBMITTED too. 330 of
620 variants have no ACCEPTED camp-bearing item at all.
This is the axis a confidence/confidence_unit pair is for — ClinSigAuthorityCallRow already
requires a magnitude to name its instrument, and status is CIViC's own, unconverted. Any adoption
must decide whether SUBMITTED items are read at all; the answer changes every number above.
Provenance is per record, and every one is a PMID¶
evidenceType is PREDISPOSING for all 1,458 and source.sourceType is PUBMED for all 1,458.
So every row carries a real PMID — per-record provenance already in the shape studies.csv wants,
which is the strongest thing this source has going for it and is unaffected by every negative finding
above. Level and rating are tabulated below.
Combination genotypes arrive in two encodings, and one of them does not decompose¶
Of the 625 profiles, 6 name more than one variant — all VHL, all exactly two variants, all joined
by an uppercase " AND ", carrying one row each.
But that is not CIViC's only encoding. 24 single-variant records carry a conjunction inside the
variant's own name — one variant id, one profile, a name describing two to four alterations:
variant 4181 C162Y(c.486C>G) and L188V(c.562C>G) and P81S(c.241C>T) and F119L(c.357C>G), variant
4096 R161* (c.481C>T) AND R200fs (c.598del), variant 3314 rs1801270 and rs1059234, variant 4192
Deletion AND I151S(c.452T>G).
Exploding over molecularProfile.variants[] does not decompose these — they stay one variant id
with a compound name. The name-level encoding is four times more common here than the profile-level
one (24 vs 6). Any drafter must handle both, or it will mint one identity for what the source is
describing as several alterations.
The corpus is VHL-dominated¶
Per-variant germline direction-item counts run from 1 (407 variants) to 48 — variant 1739,
VHL R167Q (c.500G>A). The next eight are also VHL: R167W 41, R161* 33, F76del 32, R161Q 27, N78S 19,
Y98H 18, Exon 3 Deletion 17, S65W 16. A survey of this set is substantially a survey of VHL.
Scope: both tables, and why the assertions zero is structural¶
The finding must be as wide as evidenceItems and assertions, and no wider
(@probe-names-the-table). assertions takes no variantOrigin argument — confirmed by
introspection — so all 296 were paged and split per record.
Origin split of the 296: SOMATIC 275 · RARE_GERMLINE 6 · MIXED 6 · UNKNOWN 6 ·
COMBINED 3 · COMMON_GERMLINE 0.
All 296 assertions are SUPPORTS. Verified server-side rather than by tallying:
assertionDirection: DOES_NOT_SUPPORT returns 0 on the default basis and 1 under status: ALL — the
single one that exists is among the 18 rejected.
The six germline assertions are all PREDISPOSING, all SUPPORTS, all with ampLevel: null:
SUPPORTS |
|
|---|---|
PATHOGENIC |
4 |
LIKELY_PATHOGENIC |
1 |
UNCERTAIN_SIGNIFICANCE |
1 |
AID4 (var 1739 VHL R167Q) · AID14 (var 1956 VHL E70K) · AID17 (var 2088 VHL F76del) ·
AID18 (var 1810 VHL Q195*) · AID41 (var 1776 VHL L184P) · AID42 (var 1747 VHL R167W).
Germline assertions carrying PREDISPOSITION or PROTECTIVENESS: 0 — and the zero is structural,
not empirical. AssertionSignificance is a different and smaller enum than EvidenceSignificance
— 16 members against 24. PREDISPOSITION, PROTECTIVENESS, ONCOGENICITY, GAIN_OF_FUNCTION,
LOSS_OF_FUNCTION, DOMINANT_NEGATIVE, NEOMORPHIC, UNALTERED_FUNCTION and UNKNOWN are not
members of it, and ONCOGENIC is assertion-only. Filtering assertions by
significance: PREDISPOSITION is a GraphQL type error, not an empty result.
That is a stronger statement than a count: no CIViC assertion can ever carry the direction axis. The assertions table is not a thin source of direction evidence — it is structurally incapable of holding any, so it cannot become one as the database grows.
The origins outside the germline pair contribute almost nothing¶
The reporter's germline filter is RARE_GERMLINE + COMMON_GERMLINE, and a filter whose scope is
narrower than its name is the defect RM152 itself names. So the other 1,039 items were paged in full
and every cell independently re-derived as a server-side filtered totalCount:
| Origin | Items | PREDISPOSITION/SUPPORTS |
PREDISPOSITION/DOES_NOT_SUPPORT |
PROTECTIVENESS |
|---|---|---|---|---|
UNKNOWN |
374 | 3 | 0 | 0 |
COMBINED |
20 | 2 | 0 | 0 |
MIXED |
18 | 0 | 0 | 0 |
NA |
627 | 3 | 0 | 0 |
| total | 1,039 | 8 | 0 | 0 |
Eight rows. All PREDISPOSING, all SUPPORTS, seven of the eight on VHL/FBXW7/KLLN. Widening the
origin filter to everything a VCF could plausibly reach adds 8 to 1,458 and adds zero to either
minority camp.
The whole germline set by type and direction¶
All 3,103 germline items, not just the PREDISPOSING 2,867:
evidenceType |
SUPPORTS |
DOES_NOT_SUPPORT |
NA |
Total |
|---|---|---|---|---|
PREDISPOSING |
2,054 | 4 | 809 | 2,867 |
PREDICTIVE |
130 | 15 | 0 | 145 |
FUNCTIONAL |
41 | 4 | 0 | 45 |
PROGNOSTIC |
31 | 6 | 0 | 37 |
DIAGNOSTIC |
5 | 2 | 0 | 7 |
ONCOGENIC |
2 | 0 | 0 | 2 |
| total | 2,263 | 31 | 809 | 3,103 |
31 germline items carry DOES_NOT_SUPPORT, but only 4 of them are on the direction axis. The
other 27 sit on therapy, prognosis and protein-function significances — real disagreement, on axes
this format does not model as direction. So no direction-bearing predisposition signal lives outside
PREDISPOSING.
809 PREDISPOSING germline rows carry evidenceDirection: NA and significance: NA — neither
supporting nor refuting. A third state, and the reason the earlier "direction NA is 0 of 1,458"
holds: those 809 fall outside the 1,458 because their significance is NA, not because they have a
direction.
Evidence level and rating¶
| Level | SUPPORTS |
DOES_NOT_SUPPORT |
|---|---|---|
| A | 2 | 0 |
| B | 38 | 2 |
| C | 1,413 | 2 |
| D | 1 | 0 |
| E | 0 | 0 |
Ratings 1–5: 18 · 331 · 982 · 122 · 5. No nulls in either column. The corpus is 97.1% level C, rating 3.
Two things this kills, both of which would be tempting shortcuts:
- "The refutations are weak, so discount them." Two of the four
DOES_NOT_SUPPORTrows are level B andACCEPTED— EID1854 (CHEK2 IVS2+1G>A, B/2) and EID1302 (TP53 R72P, B/4). They are not uniformly weak, and a rule that dropped them would be discarding the better-reviewed half. - "The protective pair is noise." EID12065 (
AXIN2 rs143348853) is level A, rating 5 — the highest-quality item in the entire 1,458 — thoughSUBMITTED. The minority camp is tiny and is not low-grade.
Genome build — and the identifiers that make it survivable¶
The coordinates really are GRCh37¶
ReferenceBuild introspects as {NCBI36, GRCH37, GRCH38}, so GRCh38 is permitted. It is almost
never used. Over the whole corpus (all 5,065 variants: 4,624 GeneVariant, 415 FusionVariant, 23
FactorVariant, 3 RegionVariant):
referenceBuild, gene variants |
Count |
|---|---|
null |
2,433 |
GRCH37 |
2,189 |
GRCH38 |
2 |
The two exceptions are id 2885 ABL1 T315V and id 5371 MEN1 T344M. ensemblVersion is 75 on
1,753 records — the terminal GRCh37 Ensembl release. coordinates takes no arguments: there is no
query path that asks for a build, so a client gets whatever the curator stored.
Over the 620 variants behind the 1,458 germline direction rows: GRCH37 376 · null 244 · GRCH38
0. Coordinate completeness: full chrom+start+ref+alt 289 · chrom+start only 86 · none 245.
This format is GRCh38-only — refget_accession raises outside GRCh38, genome_build lives in the
manifest and is injected at load, and multi-build identity is RM15, deferred to 1.0 as an
identity-change. So no CIViC coordinate is directly usable, and the 987 evidence rows on a
fully-coordinated single-variant profile are 987 rows of GRCh37: a blocker, not a reach figure.
But CIViC publishes build-independent identifiers, and that changes the answer¶
The coordinate is not the only identity CIViC carries. Measured over the same 620:
| Identifier | Non-null | On the 376 with coordinates | On the 244 without |
|---|---|---|---|
alleleRegistryId (ClinGen CAID) |
363 | 363 | 0 |
myVariantInfo.dbsnpRsid |
275 | 275 | 0 |
rsID in variantAliases |
174 | 174 | 0 |
| union rsID, either source | 276 | 276 | 0 |
myVariantInfo.clinvarHgvsGenomic carrying a GRCh38 NC_ accession |
252 | 252 | 0 |
| union GRCh38-explicit HGVS | 288 | 287 | 1 |
clinvarIds, excluding the NONE FOUND/N/A sentinels |
209 | 208 | 1 |
maneSelectTranscript |
362 | 362 | 0 |
The HGVS route works because the NC_ accession version encodes the build, and ClinVar publishes
both — e.g. R1275Q carries NC_000002.11:g.29432664C>T (GRCh37) and
NC_000002.12:g.29209798C>T (GRCh38). Every record with any NC_ accession in
clinvarHgvsGenomic has a GRCh38 one: 252 of 252.
The number that decides the design:
Of the 376 variants with a GRCh37 coordinate, 318 carry at least one build-independent or GRCh38-explicit identifier — 245 have both a GRCh38 HGVS and an rsID, 42 GRCh38-HGVS only, 31 rsID only. 58 have a GRCh37 coordinate and nothing else.
All 244 null-build variants carry none of these, but they have no coordinate to lift either — they are protein- and transcript-level descriptions (72 frameshift/truncation, 71 protein+cDNA, 33 splice/intronic, 24 point substitutions, 20 structural/exon-level, 3 categorical). 243 of the 244 have a coordinates object with every field null.
So liftover is required for at most 58 variants, and is unnecessary for the rest. RM48's rule —
"if the paper gives an rs-number, liftover is unnecessary and strictly worse, because authoring the
rs-number produces the independent second value resolution._verify cross-examines" — applies here
directly, and better than it does for a human author: CIViC publishes the rs-number, so not even
the recovery call is needed. Reading the identifier the source already carries is not liftover and not
recovery; it is the ordinary resolution chain.
That is also why an rsID route is clean where a recovered one would not be. A drafter writing an rsID
CIViC published is writing an independent value resolution can cross-examine. A drafter filling an
rsID recovered from Ensembl at a GRCh37 position would have resolution verify a value against the
service that produced it — the hints.REDUNDANCY_BEARING refusal, and the reason RM48 reports and
never fills.
Internal consistency, and one malformed record¶
Two checks run without any external source:
coordinates.start/referenceBases/variantBasesagainst the GRCh37NC_HGVS: 276 of 276 comparable records agree on all three, zero mismatches.startis the 1-based HGVSg.position, which is what this format'sstartmeans (@start-1based).myVariantInfoIdagainstcoordinates: 346 of 346 match chromosome and position exactly, and it is GRCh37-positioned throughout.
Three raw tuples, so a reader can check them:
id=9 R1275Q GRCH37 chr2 29432664 C>T CA341482 rs113994087 → NC_000002.12:g.29209798C>T
id=113 M918T GRCH37 chr10 43617416 T>C CA009082 rs74799832 → NC_000010.11:g.43121968T>C
id=117 R248Q GRCH37 chr17 7577538 C>T CA000387 rs11540652 → NC_000017.11:g.7674220C>T
One record is malformed and it is the shape a guard must catch. CIViC id 1770 N150fs (c.449del)
has referenceBuild=GRCH37 and start=10188305 and referenceBases=A, but null chromosome and
null variantBases. It passes a build filter and has no usable position — a partially-populated
coordinate, which is exactly what @identity-whole-or-none exists for: a provider fills identity whole
or not at all. It is also why the comparable-record denominator above is 276 + 99 = 375 and not 376.
Other fields worth knowing about¶
Full GeneVariant field list: alleleRegistryId, clinicalSignificanceCounts, clinvarIds, comments,
coordinates, creationActivity, deprecated, deprecationActivity, deprecationReason,
detailedClinicalSignificanceCounts, events, feature, flagged, flags, hgvsDescriptions, id,
lastAcceptedRevisionEvent, lastCommentEvent, lastSubmittedRevisionEvent, link, maneSelectTranscript,
molecularProfiles, myVariantInfo, name, openCravatAnnotations, openCravatUrl, openRevisionCount,
revisions, singleVariantMolecularProfile, singleVariantMolecularProfileId, variantAliases,
variantTypes.
clinvarIds is a raw curator string list with sentinels in it — NONE FOUND ×147 and N/A ×6 —
so its non-null count of 362 overstates real coverage by 153. A consumer reading it without filtering
the sentinels would treat "the curator checked and found none" as an id. openCravatAnnotations is
untyped JSON.
The bulk releases, which are a different source from the API¶
The whole download surface, enumerated 2026-09-01 — because the earlier survey named the three files the builder reads and stopped there, and one of the unnamed ones turned out to answer a question this document had recorded as unanswerable:
| file | rows / size | note |
|---|---|---|
ClinicalEvidenceSummaries.tsv |
4,878 | every row accepted |
VariantSummaries.tsv |
1,992 | also accepted-only — a fact nothing had stated |
MolecularProfileSummaries.tsv |
— | tells a combination genotype from a dangling reference |
AssertionSummaries.tsv |
145 | not read; the assertions table is structurally incapable of the direction axis |
FeatureSummaries.tsv |
973 | gene/feature metadata |
GeneSummaries.tsv |
973 | byte-identical to FeatureSummaries.tsv — one file, two names |
VariantGroupSummaries.tsv |
30 | curator groupings |
civic_accepted.vcf |
2,347 CSQ | the accepted slice, GRCh37 |
civic_accepted_and_submitted.vcf |
5,864 CSQ | the wider basis, dated and pinnable |
No source, therapy, disease or phenotype TSV exists (404). Both VCFs exist for every dated release as well as nightly.
The VCF is a strict subset of the TSV and the subset is not arbitrary. A VCF record needs a POS,
so a variant with no GRCh37 coordinate cannot appear: the accepted VCF holds 473 direction rows on 236
variants against the TSV's 533 on 290, and 52 of the 54 it drops are exactly the
unresolvable_identity class. It widens the corpus; it cannot replace the TSVs.
CIViC publishes nightly and dated TSV releases — civicdb.org/downloads/nightly/nightly-*.tsv and
civicdb.org/downloads/01-Aug-2026/01-Aug-2026-*.tsv (probed: 01-Aug-2026 and 01-Jul-2026 both
200). A dated release is what a snapshot can pin, so it is the right build input; the API has no
dated release at all.
The bulk file is accepted-only. ClinicalEvidenceSummaries is 4,903 rows and every one has
evidence_status = accepted — against the API default's 11,518 NON_REJECTED. Two published surfaces
of one source, differing 2.35×, and neither declares its basis. That is the single most important
methodological fact in this survey: a number from the TSV and a number from the API are not
comparable.
On the bulk (accepted) basis, the direction corpus is:
| Rows | |
|---|---|
| Germline evidence rows | 813 |
…of which Predisposing |
675 |
Direction-axis (Predisposition ∪ Protectiveness) |
533 |
Predisposition / Supports |
530 |
Predisposition / Does Not Support |
2 |
Protectiveness / Supports |
1 |
533 matches the API's ACCEPTED count for the same slice exactly, which cross-validates both probes.
The join is clean. ClinicalEvidenceSummaries.molecular_profile_id →
VariantSummaries.single_variant_molecular_profile_id joins 533 of 533 direction rows to 290
distinct variants. Multi-variant profiles have no single_variant_molecular_profile_id, so they
drop out by construction — which is correct, since a combination profile is not a single-variant
identity.
Identity in the bulk file is thinner than in the API, and this is what a builder gets¶
VariantSummaries carries chromosome, start, stop, reference_bases, variant_bases,
representative_transcript, ensembl_version, reference_build, hgvs_descriptions, allele_registry_id,
clinvar_ids, variant_aliases. It does not carry the API's myVariantInfo block, which is where
dbsnpRsid and clinvarHgvsGenomic live. So over the 290 joined variants:
| Route | Variants |
|---|---|
GRCh38 NC_ accession present in hgvs_descriptions |
40 |
…of those, one parse_grch38_substitution can read |
12 |
| GRCh37-only accession | 181 |
rsID in variant_aliases |
126 |
ClinGen allele_registry_id (excluding the unregistered sentinel) |
235 |
| Reachable — a readable GRCh38 substitution or an rsID | 133 |
| neither | 157, of which 102 carry a CAID |
Measured with the shipped parse_rsids and parse_grch38_substitution rather than an ad-hoc regex.
On this release, 329 of the 533 evidence rows are reachable and 204 are not.
This table used to have two columns, and the reconciliation is worth more than the numbers were.
It gave 159 reachable / 131 not for the nightly download against 133 / 157 for the dated
01-Aug-2026 release, and read the gap as the nightly being later and better curated. Re-measured on
2026-09-01, that reading is wrong: the two files are identical on this slice — same 533 rows, same
290 variants, same 53 unreached, and not one identity cell different on any shared variant — and each
of them gives 133 under one definition of "reachable" and 159 under another. The 159 counts every
variant carrying a GRCh38 accession (40 of them); the 133 counts only those carrying one the
substitution parser can read (12). One file, two definitions, 26 variants of daylight.
That is @existence-not-identity in the shape a denominator can take — an accession that exists is
not an accession that resolves — and the 28 unreadable ones are not a parser defect: they are
deletions and insertions, and NC_000003.12:g.10146622del states no bases for a substitution parser
to hold. has_unparsable_grch38 exists precisely to keep that class countable (49 rows) instead of
folding it into "the source said nothing".
Scoring the accession requires the per-chromosome RefSeq map, not a version number. NC_000001.11
is GRCh38 but NC_000002.11 is GRCh37 — the version that means "GRCh38" differs per chromosome. A
first pass here tested ".11:g." or ".12:g." and reported 208 reachable where the real figure is 40.
The map is a domain constant and belongs in the builder as one.
reference_build over the whole variant file: GRCh37 1,283 · empty 715 · GRCh38 1.
Consequence for a snapshot. Build from the dated TSV pair — that is what makes a rebuild
byte-reproducible, and mixing in live API enrichment would forfeit exactly that. Carry
allele_registry_id as a snapshot column so the 157 unreachable variants stay addressable, and leave
CAID→GRCh38 resolution to a later pass, where RM48's report-never-fill rule governs it.
The nightly, re-surveyed — what a month of curation actually added¶
Probed 2026-09-01, both files downloaded fresh and put through the shipped builder's own filter
chain. The question was how much a snapshot pinned to 01-Aug-2026 is missing by not reading the
nightly. The answer is nothing at all on the axis this source was adopted for:
01-Aug-2026 |
nightly (2026-09-01) | |
|---|---|---|
| Evidence rows in the file | 4,878 | 4,903 |
| Variant records | 1,992 | 1,999 |
| Germline rows | 811 | 813 |
| Direction-axis rows | 533 | 533 |
| Joined to a single-variant profile | 533 / 290 variants | 533 / 290 variants |
| Kept by the builder | 474 / 237 variants | 474 / 237 variants |
Dropped unresolvable_identity |
59 / 53 variants | 59 / 53 variants |
Identity rsid / both / grch38_hgvs / caid |
310 / 17 / 7 / 140 | 310 / 17 / 7 / 140 |
+25 evidence rows and +7 variants, and not one of them reaches this format. The 25 new rows are 21
Somatic, 1 Unknown, 1 N/A and 2 germline — and the two germline ones are Gain of Function
and Sensitivity/Response, neither on the direction axis. The 7 new variants are FGFR3, PTPRD, FGFR2,
GNAS and three with no gene named: somatic oncology, which is what CIViC mostly is.
Two further checks, because "the totals match" is a weaker claim than it looks:
- The sets are identical, not merely the counts. The 290 direction variants are the same 290 ids, and the 53 unreached are the same 53 ids — no variant swapped places with another.
- No shared variant changed an identity cell. All nine of
variant,variant_aliases,hgvs_descriptions,allele_registry_id,chromosome,start,reference_bases,variant_bases,reference_buildwere compared across every variant present in both files: zero differences. So the curation that happened in this window added records; it did not back-fill an identifier onto an existing one.
01-Sep-2026 does not exist yet — all three files 404 on 2026-09-01 — so the dated release a snapshot
can pin is still 01-Aug-2026, and pinning it costs nothing measurable.
What this does not say. It is one month, on one axis, and the germline direction slice is 11% of the file; a nightly read on the somatic majority would be a different measurement with a different answer. It is also not a currency check — that asks the download index for a later dated release (survey item 3), and this compares two files that both exist today.
What was built from this, and what it looks like in practice¶
CIViC was adopted on 2026-08-31 on the direction axis and nowhere else (RM152), and the identity
residue was closed the same day (RM153) — civic build, draft-panel --source civic, civic publish,
civic reproduce, a ClinGen Allele Registry client, and two licence rows, with no schema change of
any kind. The operative surface is documented in the enricher reference and the authoring skill;
what belongs here is the numbers a reader should expect, so a run that disagrees is recognisable as
the source having moved rather than the tool having broken.
Building the 01-Aug-2026 release¶
| Evidence rows read | 4,878 |
Dropped non_germline_origin |
4,067 |
Dropped not_direction_axis |
278 |
Dropped unresolvable_identity |
59 |
Dropped combination_profile / no_variant_record |
0 / 0 |
| Kept | 474 rows on 237 variants |
Identity: rsid / both / grch38_hgvs / caid |
310 / 17 / 7 / 140 |
The zeros are measured, not structural: 209 multi-variant profiles carrying 547 accepted rows
exist in this release and none is germline, so combination_profile would fire on a source that grew
one.
Drafting it into an empty spec¶
| Offline | With the registry | |
|---|---|---|
| Variant rows | 115 | 201 |
| CAID → rs-number | 0 | 88 |
| CAID → GRCh38 coordinate | 0 | 13 |
| One-sided indels anchored | 0 | 39 |
Withheld caid_unresolved |
140 | 0 |
Every drafted row carries a DOID: in trait_efo_id, and 474 kept rows collapse to 201 authored
variants because one variant carries several evidence items — the drafter reports the rest as
already_present, which is what makes a re-run a no-op.
Reproducing and publishing it¶
# build + validate in one command; exits non-zero on any failure, so it is usable in CI
just-dna-enricher civic reproduce --release 01-Aug-2026
# or the two halves separately
just-dna-enricher civic build --release 01-Aug-2026 --out ./civic
just-dna-enricher civic publish ./civic --dry-run # then without --dry-run
civic reproduce runs five checks, and the third is the only one that needs a network for a reason
other than fetching:
- The release downloads and every file is hashed — a sha256 per input, so a later disagreement is a finding about the source rather than a mystery.
- Two independent builds are byte-identical (Principle 7). A parquet has no inherent row order, so this is what proves the sort is load-bearing.
- Every placed coordinate is cross-examined against the GRCh38 reference sequence through refget/seqrepo. This is the external validation the rest of the pipeline cannot give itself: the positions come from RefSeq accessions inside ClinVar HGVS, and an unrelated service is asked whether the reference base at each is what we wrote. A wrong-build or off-by-one placement fails here and nowhere else. Measured 2026-08-31: 24 of 24 read, 0 mismatches.
- The drop registry closes as an equality over a walked set —
4878 = 474 + 4404. - The publish plan is exactly
data/civic.parquet+release.json+LICENSE.txt.
--offline skips only the third and says so: a check that could not run is not a check that passed.
What the published snapshot does and does not contain. It carries the CIViC derivation, its
release.json (naming the dated release and the accepted status basis) and CIViC's own CC0 text. It
does not carry the ClinGen registry's answers — the CAID travels, the rs-number ClinGen returns
for it does not. Resolving at draft time is a read; baking those responses into a redistributed file
would be passing on bytes whose terms nobody has established.
The 53 variants nothing reaches — opened, and 34 of them resolved¶
Probed 2026-09-01. After every published identifier and the registry, 53 variants (59 evidence rows) carried no identifier of any kind. All 53 were then put through a four-tier identity procedure — the ClinGen Allele Registry by an HGVS expression built from the variant's own name, NCBI E-utilities, Ensembl's Variant Recoder, and the papers each evidence row cites. Thirty-four resolve.
| Class | Variants | Rows | Outcome |
|---|---|---|---|
Resolved from the name — a c. or protein fragment the source published and never registered |
33 | 33 | rsID and/or CAID plus a GRCh38 coordinate, cross-checked on a second service |
| Resolved already — variant 1770, whose answer the record carried | 1 | 1 | CA020360 / rs794727253 |
| Liftover-only | 9 | 13 | closed 2026-08-31; ceiling 13 rows, honest recovery at most one |
| No allele identity exists — the name states a class of event | 6 | 8 | correctly dropped |
| Two readings, nothing to choose between them | 2 | 2 | withheld; needs a curator and a paywalled fulltext |
| Conjunction-named — two alterations in one record | 2 | 2 | all four alleles identified; the record is not one identity |
Per-variant tables, the queries behind each answer and the four wrong-name findings are in CIVIC_UNRESOLVED, which holds the residue class by class; the procedure itself is CIVIC_IDENTITY_PROTOCOL, written to be re-run against any source that publishes a variant name and no identifier.
Adopted on 2026-09-01 as RM159 — 33 of the 34 — taking coverage to 270/290 variants (93.1%) and
507/533 evidence rows (95.1%), from 237/290 (81.7%) and 474/533 (88.9%). The identities ship as a
constant read by civic build, keyed to the exact variant name each was derived from, so the build
stays offline and byte-reproducible; the four states a curated row can land in are counted in
release.json. unresolvable_identity falls from 59 rows to 26.
The one held back is CIViC 4968 TP53 R72P: it resolves, and its identity is the reference allele
(NC_000017.11:g.7676154G=) because the name has reference and alternate inverted. ref == alt is not
a variant row, so the identity exists and this representation cannot carry it.
An unrelated service then confirmed all 33. civic reproduce cross-examines every placed
coordinate against the GRCh38 reference through refget: 57 of 57 read, 0 mismatches, up from 24
before the adoption.
What this round overturned in the previous one, and what the error cost. The earlier pass
concluded that "the one remaining lever is worth 2 variants, not 31" and that the residue was "close
to a permanent floor rather than a backlog". Both are false. The mistake was one misread requirement:
it looked for a representative_transcript on the record, found it on 2 of the 31, and wrote off
the other 29 as having "nothing to build an unambiguous expression from". But the numbering frame a
c. fragment needs is a property of the gene, not of the record — for VHL it was established by
reading the 114 variants in this same corpus that CIViC did resolve, all of which publish both a
name and an ENST00000256474.2:c.… expression that agree; elsewhere from MANE Select, with CDKN2A
needing the exon table to choose p16 over p14ARF. A per-gene fact tested as a per-record one cost 39
variants, declared permanently unreachable.
What survives from that pass is the narrower claim, and it is still true: five of the nine
coordinate-bearing variants can never be reached by any identity pass, because the source states
their extent as unknown (c.1-?_340+?del) and the ClinGen registry refuses to parse such expressions
outright. An allele registry that cannot hold them is a stronger statement than a count.
Four findings from this round that outweigh the count¶
- Two CIViC names are wrong in a way nothing in a lookup can flag.
TP53 R72P(4968) has reference and alternate inverted — codon 72 isCCC= Pro on GRCh38, so the identity is the reference-identity allele CA178298 (c.215C=, rs1042522).CHEK2 IVS2+1G>A(788) converts structurally toc.319+1, but the true answer isc.444+1, because legacy papers number CHEK2 exons from the first coding exon; both readings are real registered alleles 9 kb apart. Two more names are internally inconsistent:VHL L178P (c.532C>T)(2459) pairs a missense protein name with a synonymous cDNA change, andRUNX1 R135FSX177(804) names a protein consequence where the allele is an intronic splice-donor deletion. - Three unresolved records duplicate CIViC's own resolved entries — 3743≡1770 (CA020360),
2459→1748 (rs5030822), and half of 4210→1739 (rs5030821). The identity was in CIViC's table one row
over. The first has a downstream consequence: an identity pass that reaches both maps two variant
ids onto one allele, which touches camp grouping and the drafter's
already_presentpath. - The premise that a name-only record is a famous allele with a forgotten rs-number is false. Of the 20 VHL indels resolved, only 9 carry an rs-number at all and 9 of the CAIDs carry no external record of any kind. The identity exists; the fame does not.
- A transcript-version story that this survey's companion helped set is wrong.
NM_000551.3andNM_000551.4have byte-identical CDS, and submitting either returns the same CAID; thec.197_220del→c.198_221delshift is HGVS 3′-rule renormalization inside a repeat, not a version effect. Believing the version story invites correcting a whole gene's positions by one and teaches a reader to wave through a genuine one-base mismatch.
What could be done next, each with the number that sizes it¶
Sized here, decided elsewhere. Nothing below is a plan — docs/probes/ is evidence.
Already taken, and listed so a reader does not re-propose them. The two largest items this section
originally carried have shipped: the ClinGen CAID pass (RM153 — 64 variants, taking recovery from
48% to 70%) and publishing a snapshot (civic publish, CC0, nothing legal in the way). The CAID
pass then grew a third leg nobody had sized — anchoring one-sided indels — which recovered all 35
remaining registry rows and took recovery to 237/290 = 82%.
Still open, with a real trade-off to decide first:
- Read the API for what the download does not carry — identity, and HPO ids. Two things behind
one trade-off.
myVariantInfoholdsdbsnpRsidand ClinVar's GRCh38 HGVS, which lifts reach over the API's own view from 133-of-290 to 318-of-376. Andphenotypescarries realHP:CURIEs (HP:0003621) where the bulk file gives only labels (Hemangioblastoma) — 385 of the 533 direction rows have a phenotypes cell and none of them is an id. Sincetrait_efo_idis a multi-valued ontology-CURIE column, an HPO id would sit beside theDOID:rather than replacing it. But mixing a live API into a build forfeits byte-reproducibility, so this belongs as anenrich-time pass beside the CAID one, never insidecivic build. - Decide what a conjunction-named variant is — the instrument exists, the drafter does not use
it. CIViC encodes combination genotypes two ways and only one decomposes: 6 profile-level
" AND "profiles (dropped by count, correctly) and 24 variants carrying a conjunction inside their ownname(rs1801270 and rs1059234). Nothing in the builder looks at a variant's name, so nothing drops or splits them; the two in this release are removed by the identity filter, which is harmless by accident — and the CAID pass is exactly what would let them through.
What the 2026-09-01 round settled is that this needs no schema change. A two-alteration record
is a haplotype plus a diplotype, which is what haplotypes.csv and diplotypes.csv have been
since 0.4 and what hfe_compound_het demonstrates.
Record 3298 (VHL P81S and L188V) was authored that way and compiled: it validates, compiles in
both modes to the same digest, and round-trips with content_signature intact. Its two alleles are
rs104893829 and rs5030824, both fully identified. Size today: one authorable record — the other
conjunction record, 4210, fails on provenance rather than on identity, because ClinVar attributes
only its missense half to the paper CIViC cites. Full working in
CIVIC_UNRESOLVED § class E.
The one thing an author must state rather than derive is phase. The paper behind 3298 reports
co-segregation through six family members and never uses the word cis; cis follows from the
co-segregation, and that inference belongs in the row's conclusion where a reader can see it, not
silently in the choice of haplotype.
3. A currency check. Dated releases appear monthly and are immutable, so "is this snapshot stale?"
is answerable by asking the download index for a later date, against the source rather than the
cache. release.json already records dataset and a sha256 per input file.
4. Read SUBMITTED items. The bulk file is accepted-only, so today's snapshot is the reviewed
subset by construction. Including submitted items roughly doubles the corpus, requires the API
rather than the download, and changes every number in this document — including taking the
contested-variant count from 0 to 3. If ever done, status belongs on the row as confidence with
confidence_unit, unconverted.
Answered in part on 2026-09-01, and not by the API. The submitted evidence is published as
<date>-civic_accepted_and_submitted.vcf in the same dated directory, so the corpus half of this
item shipped as RM169 — 507 rows on 270 variants to 1,149 on 397, byte-reproducibly. What the
VCF cannot do is the citation half, because it carries none of the records at issue.
There is still a worked instance where the basis costs something specific rather than just rows.
Variant 1955, one of the two records nothing resolves, carries a second evidence item — EID 9969,
PMID 12202531 (Dollfus 2002) — which is SUBMITTED, and is therefore in the API and in no file the
builder reads. Unlike the two Hum Mutat papers the accepted item cites, it is free full text,
so it is the only reachable lead on that record. The status basis is not only a denominator
question; here it hides the evidence that could settle an identity. Working in
CIVIC_LEGACY_INSERTIONS, and filed as RM160 on the strength of
it — open, with the reproducibility bargain stated as options rather than decided.
Deliberately not worth doing, each closed on a measurement rather than an opinion:
- A
direction-axis concordance record. Genuinerisk-vs-protectiveopposition is 0 at every scope and status basis, and nothing else in the enricher fillsdirectionat all — a concordance record needs two authorities and this axis has one. What would reopen it is a second direction-bearing source appearing, not more CIViC. - CIViC as a
clin_sigauthority. Five germline ACMG-tier calls, zero benign-class. Dead on arithmetic, and no growth in the somatic majority changes it. - Mining
assertionsfor direction evidence. Structurally impossible, not merely thin:AssertionSignificancehas noPREDISPOSITIONorPROTECTIVENESSmember, so the query is a type error. This cannot change without CIViC altering its schema. - Lifting the GRCh37 coordinates over. Refused and sized: 13 evidence rows on 9 variants, honest recovery at most one, and the one precise event in the class lifts exactly to the wrong allele. Full working in CIVIC_UNRESOLVED.
One thing to check before trusting anything above. Every count here is over one dated release, and
the source is actively curated, so re-derive rather than quote if a decision turns on a margin. What
that caution used to say — that the nightly already disagreed with 01-Aug-2026 on reach, 159 against
133 — was itself the artefact described above, and the re-survey below is what replaced it.
What this survey concludes, and what it deliberately does not¶
It concludes nothing. docs/probes/ is evidence. The readings the measurements support are
recorded in ROADMAP_HISTORY.md — RM152 for the adoption, RM153 for the
identity residue — and in the 0.7 proposal addendum; if a future reader finds those and this document
disagreeing, those win and this one is the thing that went stale.
What is worth carrying forward in one place:
- CIViC is a somatic resource. The germline quarter is real but small, and its clinical-significance half is 5 usable calls with zero benign-class — the concordance route is dead on arithmetic.
- Its germline mass sits on the
directionaxis, 1,458 rows, every one carrying a stated direction. - Genuine
risk-vs-protectiveopposition is 0, at every scope probed, under every status basis. - The identity obstacle was real, is now mostly closed, and its size always depends on which surface
you read — always name the file. Over the dated bulk release a snapshot must build from, recovery
went 138 → 202 → 237 → 270 of 290 variants (48% → 70% → 82% → 93%): the builder's own reading of
published identifiers, then the ClinGen CAID pass, then anchoring one-sided indels, then reading the
identities CIViC states in a variant's name (RM159). The API's own view is
wider still (318 of 376 coordinate-bearing) because
dbsnpRsidandclinvarHgvsGenomicare MyVariant.info enrichment the download does not carry — but reading it would cost the snapshot its reproducibility. - Nothing is lifted over, and nothing needs to be. Coordinates are GRCh37 or absent, never GRCh38; every placed row is placed by an identifier the source itself publishes, and 24 of 24 of those placements were confirmed against the GRCh38 reference sequence by an unrelated service. The liftover-only residue is 9 variants and its honest recovery is at most one.
- The 53 that carried no identifier were opened on 2026-09-01, and 34 of them resolve — from the
c.and protein fragments CIViC publishes in the variant's ownname, against a numbering frame established per gene rather than per record. Thirty-three of them are now in the builder, rows. Thirty-three were adopted into the builder on the same day (RM159), taking it to 270/290 and 507/533 — the odd one out isTP53 R72P, whose identity is the reference allele and therefore not aref/altrow. The earlier reading of this residue as "close to a permanent floor" was wrong, and wrong for one reason: a per-gene fact was tested as a per-record one. What survives is narrower and still true — 5 of the 9 coordinate-bearing ones state their own extent as unknown, which the allele registry refuses to parse, and 6 more name a class of event rather than an allele. - The round found four wrong names, three self-duplicates and one falsified trap —
TP53 R72Phas ref and alt inverted;CHEK2 IVS2+1G>Aconverts to the wrong exon; two records pair a protein name with an allele of another kind; three unresolved records duplicate CIViC's own resolved ones; and the "NM_000551.4shifts CDS numbering" story is 3′-rule renormalization, not a version effect. Each of these would have survived a lookup that only asked whether an expression resolves. - The status default is
NON_REJECTEDand most of CIViC isSUBMITTED. State the basis or the numbers mean nothing. - Disease ids are free and complete: 533 of 533 direction rows carry a DOID, and
trait_efo_idtakes any ontology CURIE, soDOID:1612belongs in the column rather than in prose. HPO ids are API-only — the download serves phenotype labels, and a label is not an id.