A source that asserts and refutes: the two corpora measured¶
Subjects: CIViC (the two-row shape) and STRchive (the one-field shape), the two corpora
docs/ROADMAP.md § RM170 names.
CIViC basis: the 01-Aug-2026 dated bulk release (three TSVs + civic_accepted_and_submitted.vcf),
plus the live GraphQL API read on 2026-09-01.
STRchive basis: dashnowlab/STRchive data/STRchive-loci.json at main, sha256
306618801d03bb48eb69a206a9ea3d83dbbcc1317f7673a6f694c7de0b227794, byte-identical to tag v2.26.0.
Date of analysis: 2026-09-01
This is evidence, never contract, the standing rule for everything under docs/probes/. Nothing
here proposes a design. Every number below was re-derived; where a figure disagrees with the roadmap
entry, the entry is quoted and the disagreement is stated rather than smoothed.
0. Summary¶
| CIViC | STRchive | |
|---|---|---|
| where the contradiction lives | two evidence rows on one variant | one field, evidence, on one locus |
| subjects carrying it | 3 variants (2161, 2428, 2533 — all VHL) | 4 loci (3 Disputed, 1 Refuted) |
| refutation-only subjects (a rebuttal with no claim beside it) | 2 variants (CHEK2 788, TP53 4968) | not applicable — the field is a grade, not a second row |
| basis-dependent? | yes, totally. 0 on accepted; 3 on accepted+submitted; 3 on ALL |
no. One published file, one value per locus |
| does the built artifact keep it? | yes, direction null + raw words in two columns |
no. StrchiveLocus never parses evidence |
| does an author hear about it? | drafting: an aggregate count, no variant named. Hand-authoring: nothing | nothing, on either path |
| corpus modules affected today | 0 (no reference example authors VHL or CHEK2) | 0 (no reference example uses DMD / NIPA1 / DIP2B / POLG) |
The single most load-bearing measurement: every CIViC refutation that stands against a claim is
SUBMITTED. Both ACCEPTED refutations in the whole database stand against nothing. So the class
RM170 is about does not exist on the basis the builder read before RM169, and exists entirely on the
strength of unreviewed content.
1. CIViC — the two-row shape¶
1.1 Provenance of the inputs¶
The dated files used here are the exact bytes behind the shipped snapshot:
sha256sum 01-Aug-2026-ClinicalEvidenceSummaries.tsv \
01-Aug-2026-MolecularProfileSummaries.tsv \
01-Aug-2026-VariantSummaries.tsv
# e199494202de177145c17527bb3e84b488382c8a47b501e91fa971b38038aa98 evidence
# 72332619da175be97b5f2f02573169ccab214bb1688b5d3fb582a12944a0e9a3 profiles
# 6b09215639ed80f1f0617c887e4f48ac727bbea237ee83f1ac85de2cfa3b9db2 variants
python3 -c "import json;r=json.load(open('data/repro/civic/build-a/release.json'));print({k:v for k,v in r.items() if 'sha' in k})"
All three match data/repro/civic/build-a/release.json. That build is status_basis:
"accepted+submitted", 1,149 rows on 397 variants; build-b is the same basis and the same numbers,
which is RM169's byte-identical-rebuild claim holding.
1.2 The refutation set, re-derived on three bases¶
Base 1 — the accepted bulk TSV, whole file. 4,878 evidence rows, evidence_status accepted on
every one. evidence_direction is Supports 4,343 / Does Not Support 518 / N/A 17.
Those 518 are overwhelmingly somatic therapy claims, and only 2 sit on this format's direction axis:
(evidence_type, significance) |
DNS rows |
|---|---|
| Predictive / Resistance | 218 |
| Predictive / Sensitivity-Response | 166 |
| Functional / Dominant Negative | 40 |
| Prognostic / N/A | 34 |
| Prognostic / Poor Outcome | 30 |
| Oncogenic / Oncogenicity | 10 |
| Diagnostic / Positive | 7 |
| Prognostic / Better Outcome | 5 |
| Functional / Neomorphic | 5 |
| Predisposing / Predisposition | 2 |
| Functional / Loss of Function | 1 |
Base 2 — the accepted_and_submitted VCF, the file the builder joins behind --submitted. 1,333
VCF records, 5,864 CSQ entries, of which 5,772 are evidence and 92 assertion. Evidence status is
2,322 accepted / 3,450 submitted; direction is 4,822 SUPPORTS / 603 NA / 347
DOES_NOT_SUPPORT. Of those 347, 3 are PREDISPOSITION + a germline origin.
Base 3 — the live GraphQL API, status: ALL (which includes REJECTED):
evidenceItems(significance: PREDISPOSITION, evidenceDirection: DOES_NOT_SUPPORT, status: ALL, first: 50)
totalCount = 4. PROTECTIVENESS + DOES_NOT_SUPPORT = 0. So the whole database, rejected
content included, holds exactly four refutations on the direction axis, and they are the same four the
two dated files between them carry. Nothing hides behind the API that the dated pair cannot reach, on
this axis. (PREDISPOSITION + SUPPORTS on ALL is 1,516: 946 submitted, 534 accepted, 36 rejected —
consistent with RM160's NON_REJECTED figure of 1,480.)
1.3 The four refutations, and what stands beside each¶
Read from the API (evidenceItem(id:)) and cross-checked against the built parquet:
| EID | status | variant(s) | molecular profile | claims on the same variant |
|---|---|---|---|---|
| 1302 | ACCEPTED | 4968 TP53 R72P |
5146, single-variant | none |
| 1854 | ACCEPTED | 788 CHEK2 IVS2+1G>A |
769, single-variant | none |
| 10949 | submitted | 2428 VHL G104V (c.311G>T) |
2301, single-variant | 1 — EID 7134, accepted |
| 8721 | submitted | 2161 and 2533 | 5278, a combination profile | 2161: EIDs 5797 + 10733, both submitted · 2533: EID 10770, submitted |
So the assert-and-refute set is {2161, 2428, 2533} and the refutation-only set is {788, 4968}.
1.4 Three corrections to the roadmap entry's table¶
The entry's per-variant table is right about the variants and wrong about three things.
- "only 2428 has a refutation an editor signed off" is inverted. 2428's claim (7134) is
accepted; its refutation (10949) is
SUBMITTED. No refutation anywhere in CIViC that stands against a claim is accepted. The two accepted refutations are CHEK2 788 and TP53 4968, and neither has a claim beside it. - 2161 and 2533 do not carry two refutations, and the entry's table does not say they do — it
writes
ev 8721on both rows. What is not stated there is why: EID 8721 belongs to molecular profile 5278, which CIViC publishes asVHL S183L (c.548C>T) AND VHL D126N (c.376G>A)withvariants[2161, 2533]. It is one statement about a two-variant genotype, and it reads in the parquet as two single-variant rebuttals (§2.2). So the "three known instances" are one single-variant pair (2428) and one combination-profile item counted twice. - A fourth refuted variant sits just outside the entry's class, and it is the informative one.
CHEK2 788 is in the shipped parquet with an
acceptedDoes Not Supportrow and no claim beside it, so it is not an assert-and-refute variant. Taken with TP53 4968 (§1.4 below) it makes the sharper statement: both accepted refutations in the whole database are refutation-only, so the set of pairs the basis question cannot touch is empty.
TP53 4968 is a fifth refuted variant that never enters the snapshot: its
VariantSummaries.tsv row carries no chromosome, no start, no HGVS, no CAID and no aliases, so it is
dropped unresolvable_identity, and it has no GRCh37 position so it is absent from the VCF too. One
of the two accepted refutations in the database is therefore invisible to every consumer of this
artifact.
1.5 Genuine opposition remains 0, on every basis¶
contested_variants counts a variant whose kept rows put it in both the risk and the
protective camp. Measured over the live API on status: ALL, PROTECTIVENESS+SUPPORTS is two
items on two variants — 4980 rs143348853 (submitted) and 258 MTHFR A222V (accepted) — and
neither carries a PREDISPOSITION+SUPPORTS item. The intersection is empty. So
contested_variants: 0 in the release record is correct on the accepted basis, the wider basis, and
ALL, and the entry's account of CIVIC_DIRECTION_MAP mapping Does Not Support to None is
confirmed exactly as written.
2. What the built parquet carries, and what a consumer sees¶
2.1 The rows¶
uv run python -c "
import polars as pl
df = pl.read_parquet('data/repro/civic/build-a/data/civic.parquet')
print(df.filter(pl.col('evidence_direction_raw')=='Does Not Support'))"
Four rows, on variants 788 / 2161 / 2428 / 2533. Every one of them:
direction= null — the withhold, working as designed;significance_raw=Predisposition,evidence_direction_raw=Does Not Support— the raw words kept, which is the only place the rebuttal is legible;evidence_status=acceptedfor 788,submittedfor the other three.
The column that distinguishes the rebuttal from the claim is evidence_direction_raw, and nothing
else. There is no boolean, no code, no reason column. A null direction does today mean exactly
Does Not Support, because CIVIC_DIRECTION_MAP has no other None-valued key and every off-axis
pair is dropped before it reaches a row — but that is an invariant of the map, not something the row
states about itself, and it would stop holding the moment a fifth key is added.
2.2 The one anomaly the fan-out creates¶
_submitted_evidence_row (civic_build.py:825) sets molecular_profile_id from the variant's
single_variant_molecular_profile_id, not from the evidence item's own profile. So EID 8721 is
written as two rows stamped MP 2037 and MP 2406, when the item's actual profile is 5278. The parquet
therefore states, twice, that a combination-genotype refutation is a single-variant refutation, and
the column that would say otherwise has been overwritten.
The class is small and measurable. In the parquet, 1,149 rows carry 1,144 distinct evidence_ids;
the five that repeat are 5634, 6740, 6868, 8721, 8790 — all VHL, all submitted, four of them
Supports. Over the whole VCF the fan-out is wider (108 evidence ids under more than one VCF record,
279 CSQ entries), but only 5 of them survive the germline + direction-axis filter.
Note the asymmetry with the TSV path, which drops a multi-variant profile as combination_profile
(0 such drops in this release). A combination-profile evidence item reaching the builder through the
VCF is fanned out and kept; one reaching it through the TSV is counted and dropped.
2.3 What an author is actually told¶
Drafting. civic_draft already has a withheld class for this — refutation_states_no_direction
— and emits one aggregate line:
N CIViC row(s) refute a predisposition claim rather than making one, so they state no direction and none was drafted. Refuting a risk claim does not establish that a variant is protective, and this provider will not write the opposite of what the source said.
It names no variant. The neighbouring contested_variant line does name its subjects, through
examples(contested); the refutation line has no equivalent.
Hand-authoring. Nothing. There is no VALID_VERIFICATION_CHECKS member that reads the snapshot's
refutation rows against an authored table, so a module that writes a risk row on VHL S183L passes
every gate silently. That is the gap the entry describes, and it is confirmed.
2.4 Incidental: the release record contradicts itself¶
_write_release_json builds notice from an unconditional literal (civic_build.py:1017). The shipped
data/repro/civic/build-a/release.json therefore says
"It is built from the dated bulk TSV release, every row of which is status 'accepted'"
in the same file that records "status_basis": "accepted+submitted" and "status_counts": {"accepted":
507, "submitted": 642}. Not an RM170 matter; recorded because the notice is the string a redistributor
reads and it is now false on the default basis.
3. STRchive — the one-field shape¶
3.1 The real published file¶
curl -sSL -o STRchive-loci.json \
https://raw.githubusercontent.com/dashnowlab/STRchive/main/data/STRchive-loci.json
sha256sum STRchive-loci.json
# 306618801d03bb48eb69a206a9ea3d83dbbcc1317f7673a6f694c7de0b227794
main is byte-identical to tag v2.26.0 (published 2026-08-24), so every count below is pinnable.
82 loci. evidence is present and non-empty on all 82, and every locus carries exactly one value
— the list lengths are {1: 82}, so per-member counts and per-locus counts are the same number here
and will not be if that ever changes.
3.2 The vocabulary, re-derived¶
| value | loci | nearest house VALID_GENE_VALIDITY member |
|---|---|---|
| Definitive | 46 | definitive |
| Limited | 14 | limited |
| Moderate | 8 | moderate |
| Provisional | 6 | none |
| Strong | 4 | strong |
| Disputed | 3 | disputed |
| Refuted | 1 | refuted |
Sum 82. The entry's figures reproduce exactly.
3.3 The vocabulary is published and documented, but it is not closed¶
data/STRchive-loci.schema.json describes the field:
Level of Evidence — "Categorical level of evidence for the association between the locus and disease based on criTRia or similar curation framework. An association that has not yet been curated because it is recently published or limited data is available should be marked as Provisional."
The field is required, its type is array, and its items carry examples plus
"combobox": true — not enum. So the eight suggested strings (Definitive, Strong,
Moderate, Limited, Disputed, Refuted, No Known Relationship, Provisional) are a
suggestion list a curator may type past, and the schema admits any string and any number of them.
Seven of the eight are used; No Known Relationship is used by zero loci.
Two consequences worth measuring rather than assuming:
Provisional(6 loci) is not a ClinGen classification and has no member in this repo'sVALID_GENE_VALIDITY. Calling the field "ClinGen-style" is right about six of its seven observed members and wrong about the seventh, which by the schema's own text means not yet curated — a nobody-asked, not a grade.- The unused suggestion
No Known Relationshipwould also miss:gene_validity.CLASSIFICATION_BY_WORDINGkeys onno known disease relationshipandno reported evidence, neither of which is STRchive's wording.
3.4 A Disputed or Refuted locus is otherwise indistinguishable¶
evidence |
pathogenic band | benign band | intermediate band | locus_structure |
details says "conflicting" |
|
|---|---|---|---|---|---|---|
HD_HTT |
Definitive | 36–250 | 6–26 | 27–35 | 3 elements | no |
FXS_FMR1 |
Definitive | 201–2000 | 5–44 | 45–200 | [] | no |
FRA12A_DIP2B |
Disputed | 273–306 | 6–23 | 139–206 | [] | no |
ALS1_NIPA1 |
Disputed | 11–56 | 6–10 | — | [] | yes |
CPEO_POLG |
Disputed | — | 10–10 | — | 3 elements | yes |
DMD_DMD |
Refuted | 59–82 | 16–33 | — | 2 elements | yes |
Three of the four state a full pathogenic band, structurally identical to a Definitive locus's. The
last column is the whole details string matched against
conflict|disput|refut|controvers|not (been )?(replicat|confirm), not a truncation: three of the four
say so in prose and FRA12A_DIP2B says nothing at all, while neither Definitive locus matches. Two of
the three also link their curation ([@url:https://strchive.org/critria/ALS1_NIPA1]). That is a
sentence in a free-text field, not a value a gate can act on
(@one-side-only-has-two-causes), and on one of the four loci it is not there at all.
3.5 STRchive publishes a second file with the reasoning¶
data/criTRia-curations.tsv (65 rows) carries classification, total_score,
genetic_evidence_score, experimental_evidence_score, publication_count,
publication_interval_years and a prose Description, keyed (Gene, Disease_ID).
- Its
classificationcounts are Definitive 35 / Limited 14 / Moderate 8 / Strong 4 / Disputed 3 / Refuted 1 — noProvisional, consistent with the schema's "not yet curated" reading. - Where the key joins the catalogue, the two files never disagree (0 disagreements over the 58 joining pairs).
- All four Disputed/Refuted loci are curated, with scores:
DMD DMDRefuted, total 0 (0 genetic, 0 experimental, 2 publications);NIPA ALS1Disputed, total 3.0;POLG CPEODisputed, 7.0;DIP2B FRA12ADisputed, 10.5. - The join is imperfect upstream: 7 of 65 curation rows do not match a catalogue
(gene, disease_id)exactly, in two distinct ways, both measured. A gene-name mismatch: the curation writesNIPAwhere the catalogue writesNIPA1. A disease-id granularity mismatch: the catalogue packs several disease ids into one comma-joined string and the curations split them —FMR1isFXS, FXTAS, POF1in the catalogue againstFXSandFXTAS,POF1as two curation rows;ATXN3isSCA3, MJDagainstSCA3; likewiseCSNK1E(EPM, DEEvsEPM),XYLT1(DBQD2, BSSvsDBQD2) andPRDM12(HSAN VIIIvsHSAN-VIII). Any join onto this file needs to say what it did with those 7.
This repo reads none of it. data/criTRia-curations.json, .tsv and the criTRia-SOP.pdf are all in
the published repository and are not fetched anywhere in enricher/.
3.6 What this repo does with evidence today: nothing, deliberately¶
strchive.py's StrchiveLocus docstring says it plainly — "evidence, the HPO terms and the
cross-references are real and are not parsed, because nothing here emits them". The field is dropped
at parse, so it is not merely unwritten; it is unread.
Measured on the drafter, which is the question the entry asks:
uv run just-dna-enricher draft-repeats <spec> --gene DMD --catalogue STRchive-loci.json
# STRchive `details` prose, whole string, six loci (§3.4)
python3 -c "
import json,re
d={x['id']:x for x in json.load(open('STRchive-loci.json'))}
pat=re.compile(r'conflict|disput|refut|controvers|not (been )?(replicat|confirm)',re.I)
for k in ['HD_HTT','FXS_FMR1','FRA12A_DIP2B','ALS1_NIPA1','CPEO_POLG','DMD_DMD']:
print(k,[m.group(0) for m in pat.finditer(d[k].get('details') or '')])"
# the adopted-surface scan (§4.3) — a walked set, never a hand-kept list
uv run python -c "
import re
from just_dna_format import vocab
pat=re.compile(r'disput|refut|conflict|does_not|contradict|no_known',re.I)
for n in dir(vocab):
v=getattr(vocab,n)
if n.startswith('VALID') and isinstance(v,frozenset):
hits=sorted(x for x in v if pat.search(str(x)))
if hits: print(n,hits)"
writes
measure_kind,measure_min,measure_max,measure_tiling,direction,clin_sig,phenotype,trait_efo_id,conclusion,unresolved,source_field,source_element,pmid,gene,repeat_unit
repeat_count,,,,,,,MONDO_0010679,<<REPLACE>>,false,,,,DMD,CTT
DMD is the single Refuted locus in the catalogue, and its drafted row is indistinguishable from
HD_HTT's. The dry run over all four (--gene DMD --gene NIPA1 --gene DIP2B --gene POLG --dry-run)
reports the two things the provider does account for —
3 of 4 locus/loci state a fractional ref_copies ... it is not rounded anywhere.
2 of 4 locus/loci publish a locus_structure ... repeat_alleles.csv keys on a single repeat_unit.
would add 4 row(s) from 4 strchive locus/loci
— and says nothing about evidence. It is not even in the read-and-not-written accounting, because
the parser never carried it that far. check-repeat-bands reads the same StrchiveLocus, so it
cannot see the field either.
So: no, the evidence call would not reach the author at all.
3.7 Incidental: draft-repeats crashes appending into an older header¶
Reproducer:
cp -r reference_examples/htt_repeat_expansion <scratchpad>/dmd3
uv run just-dna-enricher draft-repeats <scratchpad>/dmd3 --gene DMD --catalogue STRchive-loci.json
compiler/src/just_dna_compiler/draft.py:621 → csv.DictWriter.writerows
ValueError: dict contains fields not in fieldnames: 'pmid', 'measure_tiling'
The shipped example's repeat_alleles.csv header has 13 columns and predates pmid and
measure_tiling; the writer takes fieldnames from the existing file and the rendered rows carry
more. --dry-run returns cleanly, and a spec directory with no repeat_alleles.csv writes fine, so
it is specifically the append-into-a-narrower-header path. Raw ValueError, not a diagnosed refusal.
Unrelated to RM170; recorded because it was hit while measuring the drafter.
4. The joint question¶
4.1 The subject of a contradiction, per corpus¶
- CIViC — the subject is a variant, and it is only a subject because two rows were joined on
it. The contradiction is not a property of either row; row 8721 read alone is a well-formed
refutation of nothing in particular, and row 7134 read alone is a well-formed claim. It becomes a
contradiction at the
variant_idgroup-by, and the group-by is over the snapshot, which means the subject only exists after the module's row is matched to a snapshot variant. - STRchive — the subject is a locus, and the contradiction is a property of the record itself,
visible without joining anything. It is also not a contradiction in the CIViC sense: nothing in
STRchive asserts the locus is pathogenic and then denies it. One field grades the whole
locus–disease association, and
Refutedgrades it as not holding while the same record still publishes a pathogenic band. The tension is between a grade and the bands drafted from the same record, not between two claims. - Consequently the two corpora do not share a subject and do not share a shape. CIViC's is inter-row, post-join, per variant. STRchive's is intra-record, pre-join, per locus — and closer to a self-inconsistent record than to a disagreement.
4.2 Overlap between the two corpora: one gene, no locus¶
CIViC snapshot genes (germline direction axis, 01-Aug-2026, accepted+submitted): 24. STRchive
genes: 79 over 82 loci. Intersection: CBL, and one gene only.
No variant/locus overlap at all: CIViC's refuted-side genes are CHEK2 and VHL; STRchive's
Disputed/Refuted genes are DIP2B, NIPA1, POLG, DMD. The two sets are disjoint, and no
reference example in this repo authors any of the six.
4.3 The adopted sources that already publish a refutation or a disputed-validity value¶
Enumerated from enricher/src/just_dna_enricher/, not guessed:
| source | surface | the value | where it lands | what happens today |
|---|---|---|---|---|
| ClinGen gene-validity + GenCC | gene_validity.py → gene_validity.csv |
Disputed / Disputed Evidence, Refuted / Refuted Evidence, No Known Disease Relationship |
mapped to the closed house members disputed / refuted / no_known_disease_relationship, raw kept in classification_raw |
a within-source contradiction is already handled: a group holding both definitive and refuted with nothing ordering them is published as a set, not a verdict, via undecidable_groups, and warned about. Subject = (gene, disease, mode of inheritance, submitter) |
| ClinVar | clin_sig.py, clinical.py |
Conflicting classifications of pathogenicity (three spellings normalized) |
the closed member conflicting in VALID_CLIN_SIG |
recorded; concordance.py maps conflicting → undecided; @clinsig-never-escalates keeps it out of the strict gate |
| CIViC | civic_build.py |
Does Not Support |
direction = null, raw words in two columns |
§2 above |
| STRchive | strchive.py |
Disputed / Refuted |
nowhere — dropped at parse | §3 above |
What was actually checked for the other adopted sources, stated as the scope it is. Every
VALID_* frozenset in just_dna_format.vocab was walked — 26 of them — and matched against
disput|refut|conflict|does_not|contradict|no_known. Exactly three carry such a member:
VALID_CLIN_SIG (conflicting), VALID_GENE_VALIDITY (disputed, refuted,
no_known_disease_relationship), and VALID_WARNING_CODES (whose *_contradicted members are this
tier's own findings, not a source's value). So no house vocabulary that ClinPGx/PharmGKB, CPIC,
PharmVar, the GWAS Catalog, the PGS Catalog, gnomAD constraint, LitVar, PubMind, ACMG SF or the FDA
drug labels land in has a refuting or disputed member. That is a statement about this repo's
adopted surface, measured over vocab.py. It is not a statement that those sources publish
nothing of this shape upstream — no source file of any of them was opened for this question.
So the scope question — "is this a CIViC check or a general one" — has a measured answer available from the adopted set rather than a guess: four adopted sources publish something of this shape, and three of them are already carried somewhere. (Four is a floor on the adopted set, not a total over the sources themselves — see the scoping note just above, and the bullet in §5.)
The house vocabulary VALID_GENE_VALIDITY already
contains disputed and refuted as closed members, and gene_validity already contains the machinery
for publishing an unorderable disagreement as a set. STRchive's evidence is that same vocabulary at
a different grain (a repeat locus rather than a gene–disease–MOI–submitter tuple), minus Provisional,
plus an open list rather than a closed one. CIViC's is the odd one out: the only case where the
contradiction has to be constructed from two rows.
5. What each figure was measured over — and what it is not a claim about¶
Per @probe-names-the-table, every negative here is scoped.
CIViC.
- 518
Does Not Supportrows — over01-Aug-2026-ClinicalEvidenceSummaries.tsv, whole file, all evidence types and origins. Not a claim about the API, and not about this format's axis: 516 of them are outside it. - 2 accepted refutations on the direction axis — over the same TSV, filtered to
variant_origin ∈ {Rare Germline, Common Germline}and(significance, evidence_direction) ∈ CIVIC_DIRECTION_MAP, joined to01-Aug-2026-MolecularProfileSummaries.tsvand-VariantSummaries.tsv. Not a claim about assertions (the 92assertionCSQ entries were not examined), nor aboutPredictive/Prognostic/Functionalrefutations. - "zero assert-and-refute variants on the accepted basis" — over that same joined TSV triple. It is not a claim that CIViC's accepted content is internally consistent generally; only that on the direction axis, no accepted refutation shares a variant with any claim.
- 3 refutations in the VCF, 347 overall — over
01-Aug-2026-civic_accepted_and_submitted.vcf, sha256c6d5656ac65400189968ff3f1037f09e7f1719c3b5ddb4aa7eb36e5fbe35293a, CSQevidenceentries only. The VCF holds no variant lacking a GRCh37 position, so this is not a corpus-wide count; the API call in §1.2 is what closes that gap for this axis. totalCount4 onstatus: ALL— from the livehttps://civicdb.org/api/graphqlon 2026-09-01, which is not the01-Aug-2026release. It agrees with the dated files here; a later read may not. It is a claim aboutsignificance: PREDISPOSITION|PROTECTIVENESS+evidenceDirection: DOES_NOT_SUPPORTonly, and about evidence items — assertions were not queried.- Parquet figures — over
data/repro/civic/build-a/data/civic.parquet,status_basis accepted+submitted, builder 0.7.0. Not a claim about what anaccepted-basis build of the same release would emit; that build was not produced here, and the accepted-basis statements above come from the TSVs directly. - "no reference example authors VHL or CHEK2" —
grep -rl 'VHL\|CHEK2' reference_examples/, empty. Not a claim about any module outside this repository.
STRchive.
- 82 loci, 46/14/8/6/4/3/1 — over
data/STRchive-loci.jsonatmain==v2.26.0, sha2563066188…. Not a claim about any earlier release, and not about the derived BED/JSON catalogs underdata/catalogs/, which carry noevidencecolumn at all and include all four Disputed/Refuted loci without distinction. - "the vocabulary is not closed" — over
data/STRchive-loci.schema.jsonatmain, whoseevidenceitems useexamples+combobox: trueand noenum. Not a claim that curators in practice type outside the list; today's file does not. - criTRia figures — over
data/criTRia-curations.tsvatmain, 65 rows.data/criTRia-curations.jsonand the SOP PDF were not parsed. The 0 disagreements is over the 58 pairs that join exactly; the 7 non-joining rows were listed, not reconciled. - "the evidence call never reaches the author" — established from
StrchiveLocus(which does not carry the field) and confirmed by runningdraft-repeatson all four loci. Not a claim aboutcheck-repeat-bandsfinding nothing useful; it reads the same reduced record, so it cannot see the field either, and that was read from the code rather than exercised on a module with authored bands. -
"one gene of overlap" — CIViC gene column of the built parquet (24 distinct, germline direction axis only) against STRchive's 79. Not a claim about CIViC as a whole, whose gene coverage is far wider than this snapshot's axis.
-
"no adopted source but these four carries a refuting value" — over the 26
VALID_*frozensets inschema/src/just_dna_format/vocab.py, walked as a set rather than listed by hand. Not a claim about what ClinPGx, CPIC, PharmVar, GWAS, PGS, gnomAD, LitVar, PubMind, ACMG SF or the FDA label feed publish; their upstream files were not read for this question, and a value this tier does not model would be invisible to the scan by construction. - STRchive
detailsprose — the full string on six loci, matched againstconflict|disput|refut|controvers|not (been )?(replicat|confirm). Not a claim that no other locus's prose hedges; only these six were read.
Not measured, and not estimated.
- How many authored rows in any real module would be subjects of either finding. No module in
reference_examples/touches the six genes, so the honest answer here is zero and the informative answer needs a corpus this repo does not have. - Whether the three VHL claims or their rebuttal are correct. That is a literature question and nothing here went to the papers (PMIDs 21454469, 33618821, 29789510, 28043156, 24466223, 34439168).
- Whether CIViC's
assertionlayer (92 CSQ entries,AssertionSummaries.tsv) carries a fifth shape. Not probed. evidence_direction = N/A— 17 rows in the TSV, 603 CSQ entries in the VCF. The task's phrasing was "DOES_NOT_SUPPORT(or otherwise refuting)", andN/Awas not inspected for a rebuttal written as an absent direction.CIVIC_DIRECTION_MAPhas noN/Akey, so every one of them is droppednot_direction_axisand none reaches the parquet; whether any is a refutation in substance is unmeasured.
Appendix — commands¶
A — the accepted-basis both-camps hunt (§1.2, §1.4). The single most load-bearing measurement in this document. Run from the directory holding the three dated TSVs.
import csv, collections
ev = list(csv.DictReader(open('01-Aug-2026-ClinicalEvidenceSummaries.tsv'), delimiter='\t'))
var = list(csv.DictReader(open('01-Aug-2026-VariantSummaries.tsv'), delimiter='\t'))
by_profile = {r['single_variant_molecular_profile_id']: r for r in var
if (r.get('single_variant_molecular_profile_id') or '').strip()}
GERM = {'Rare Germline', 'Common Germline'} # CIVIC_GERMLINE_ORIGINS
MAP = {('Predisposition', 'Supports'): 'risk', # CIVIC_DIRECTION_MAP
('Protectiveness', 'Supports'): 'protective',
('Predisposition', 'Does Not Support'): None,
('Protectiveness', 'Does Not Support'): None}
kept = []
for r in ev:
if r['variant_origin'] not in GERM: continue
if (r['significance'], r['evidence_direction']) not in MAP: continue
v = by_profile.get(r['molecular_profile_id'])
if v is None: continue
kept.append((v['variant_id'], v['gene'], v['variant'], r['evidence_id'],
r['evidence_direction'], r['evidence_status']))
byvar = collections.defaultdict(list)
for k in kept:
byvar[k[0]].append(k)
print('rows', len(kept), 'variants', len(byvar)) # 533 / 290
print('both camps:', [v for v, rs in byvar.items()
if any(r[4] == 'Supports' for r in rs)
and any(r[4] == 'Does Not Support' for r in rs)]) # []
print('refutation only:', [v for v, rs in byvar.items()
if all(r[4] == 'Does Not Support' for r in rs)]) # ['4968', '788']
B — the VCF CSQ parse (§1.2, §1.3, §2.2). The field order is read from the header, never positional.
import re, collections
hdr, recs = None, []
for line in open('01-Aug-2026-civic_accepted_and_submitted.vcf'):
if line.startswith('##INFO=<ID=CSQ'):
hdr = re.search(r'Format: ([^"]+)', line).group(1).split('|')
if line.startswith('#'):
continue
f = line.rstrip('\n').split('\t')
m = re.search(r'CSQ=([^;]+)', f[7])
if not m:
continue
for entry in m.group(1).split(','):
d = dict(zip(hdr, entry.split('|')))
d['_vcf_id'] = f[2]
recs.append(d)
ev = [r for r in recs if r['CIViC Entity Type'] == 'evidence']
print(len(recs), len(ev)) # 5864 / 5772
print(collections.Counter(r['CIViC Entity Direction'] for r in ev)) # DOES_NOT_SUPPORT 347
dns = [r for r in ev if r['CIViC Entity Direction'] == 'DOES_NOT_SUPPORT']
print(collections.Counter((r['CIViC Entity Significance'], r['CIViC Entity Status'],
r['CIViC Entity Variant Origin']) for r in dns))
# the fan-out class (§2.2): evidence ids under more than one VCF record
c = collections.Counter(r['CIViC Entity ID'] for r in ev)
print(sum(1 for v in c.values() if v > 1)) # 108 overall, 5 on the axis
for r in ev:
if r['CIViC Entity ID'] == '8721':
print(r['_vcf_id'], r['CIViC Molecular Profile ID'], r['CIViC Molecular Profile Name'])
C — everything else.
# the API
curl -s https://civicdb.org/api/graphql -H 'content-type: application/json' -d '{"query":
"{ evidenceItem(id: 8721) { id status evidenceDirection significance molecularProfile { id name variants { id name } } } }"}'
curl -s https://civicdb.org/api/graphql -H 'content-type: application/json' -d '{"query":
"{ evidenceItems(significance: PREDISPOSITION, evidenceDirection: DOES_NOT_SUPPORT, status: ALL, first: 50) { totalCount nodes { id status molecularProfile { id name variants { id } } } } }"}'
# the parquet
uv run python -c "import polars as pl; df=pl.read_parquet('data/repro/civic/build-a/data/civic.parquet'); \
print(df.group_by(['significance_raw','evidence_direction_raw','evidence_status']).len())"
# STRchive
curl -sSL -O https://raw.githubusercontent.com/dashnowlab/STRchive/main/data/STRchive-loci.json
curl -sSL -O https://raw.githubusercontent.com/dashnowlab/STRchive/main/data/STRchive-loci.schema.json
curl -sSL -O https://raw.githubusercontent.com/dashnowlab/STRchive/main/data/criTRia-curations.tsv
curl -sSL https://raw.githubusercontent.com/dashnowlab/STRchive/v2.26.0/data/STRchive-loci.json | sha256sum
uv run just-dna-enricher draft-repeats <spec> --gene DMD --catalogue STRchive-loci.json