Part of the VCF-RDFizer documentation. What the conversion emits:
conversion.md. How this table is measured:
validation-methodology.md.
What of a VCF file VCF-RDFizer represents in RDF, and what the semantic validation suite actually verifies. One row per VCF element. This is the tracking artifact for the coverage work and the source table for publication.
Two independent questions per row, deliberately kept apart:
- Represented — does the conversion emit RDF for this element at all?
- Validated — would a corruption of it be detected? Backed by a named
mutation in
test/validation_mutations.py, not by inspection.
Last measured mutation score: 96/113 (85%) across 60 distinct mutations, measured against the VCF Core vocabulary. Regenerate with:
VCF_RDFIZER_MUTATION_REPORT=mutation-score.json \
python -m unittest test.test_validation_mutation_unitThe score fell from 97% because the denominator grew, not because anything regressed: the migration added 18 mutations covering the layers VCF Core introduced, and 10 of those are recorded gaps. That is the methodology working as intended — a new capability that nothing checks shows up as a lower score rather than as silent confidence. Every gap is listed under Remaining gaps with what would close it.
| VCF element | records.tsv |
RDF term | Represented | Validated by | Mutation |
|---|---|---|---|---|---|
| CHROM | CHROM |
vcfc:chrom |
yes | q01, q11 |
corrupt_chrom |
| POS | POS |
vcfc:pos (xsd:integer) |
yes | q01, q11, preflight_position_datatype |
corrupt_pos, drop_pos, retype_pos_as_string |
| POS ↔ record binding | — | — | yes | q11_record_digest |
permute_pos |
| ID | ID |
vcfc:recordId |
yes | q11_record_digest |
(covered by digest) |
| ID components | ID |
vcfc:hasIdentifier → vcfc:RecordIdentifier (identifierValue, componentIndex) |
yes | q09_predicate_census, q10_class_census |
— |
| REF | REF |
vcfc:ref |
yes | q02, q03, q11 |
corrupt_alt |
| ALT | ALT |
vcfc:alt |
yes | q02, q03, q11 |
corrupt_alt |
| REF/ALT ↔ record binding | — | — | yes | q11_record_digest |
permute_ref_alt |
| QUAL | QUAL |
vcfc:qual (xsd:decimal / vcfc:Null) |
yes | q09, q11 |
drop_qual, drop_all_qual, corrupt_qual |
| FILTER | FILTER |
vcfc:filter |
yes | q04 (exact lexical), q11 |
drop_filter, corrupt_filter_lexical |
| FILTER outcome | FILTER |
vcfc:filterStatus → vcfc:FiltersPassed / FiltersFailed / FiltersNotApplied |
yes | q09_predicate_census, q10_class_census |
— |
| FILTER codes | FILTER |
vcfc:hasFilterCode → vcfc:FilterCode (filterCodeValue, componentIndex, declaredByFilter) |
yes | q09_predicate_census, q10_class_census |
— |
| INFO (raw) | INFO |
vcfc:infoRaw |
yes | q11_record_digest |
corrupt_info_raw |
| INFO (structured) | INFO |
vcfc:hasInfoValue → vcfc:InfoFieldValue → vcfc:declaredBy, vcfc:fieldIndex |
yes | q09, q12_info_value_digest |
drop_info_value, corrupt_info_value, retype_info_value |
INFO values carry vcfc:fieldValue plus a typed fieldValueInteger /
fieldValueDecimal when the declaration says Number=1 and the value parses;
a Flag entry carries vcfc:fieldValueBoolean true. A multi-valued field is no
longer lexical-only: Number=A/R/LA/LR/G/LG/P values are decomposed
into ordered vcfc:FieldValueItem resources joined to their allele with
vcfc:forAllele, or carrying a genotype/GT ordinal.
| VCF element | RDF term | Represented | Validated by | Mutation |
|---|---|---|---|---|
| REF as an ordered allele | vcfc:hasReferenceAllele → vcfc:ReferenceAllele (alleleIndex 0) |
yes | — | — |
| Each ALT item, in source order | vcfc:hasAltAllele → vcfc:AltAllele (alleleIndex 1..n) |
yes | — | — |
| ALT syntactic category | vcfc:alleleKind |
yes | — | — |
| Symbolic ALT reserved type | vcfc:svType → vcfc:SymbolicAlleleType |
yes | — | — |
Symbolic ALT ↔ ##ALT declaration |
vcfc:declaredByAlt |
yes | — | — |
| Breakend structure | vcfc:Breakend, breakendOrientation, breakendReplacementString, isSingleBreakend |
yes | — | — |
| CHROM ↔ contig declaration | vcfc:chromosome |
yes | — | — |
| Indexed field values | vcfc:hasValueItem → vcfc:FieldValueItem (valueIndex, itemValue, forAllele, tupleArity) |
yes | — | — |
| VCF element | RDF term | Represented | Validated by | Mutation |
|---|---|---|---|---|
SVLEN |
vcfc:svLength on the allele |
yes | — | — |
SVCLAIM |
vcfc:svClaim → vcfc:SVClaim |
yes | — | — |
IMPRECISE, NOVEL |
vcfc:isImprecise, vcfc:isNovel |
yes | — | — |
EVENT + EVENTTYPE |
vcfc:inEvent → vcfc:VariantEvent with vcfc:eventType |
yes (both keys required) | — | — |
CIPOS, CIEND |
vcfc:posConfidenceInterval / endConfidenceInterval → FALDO InRangePosition |
yes | — | — |
CILEN, CICN |
vcfc:ConfidenceInterval with ciLower / ciUpper |
yes | — | — |
RN + RUS/RUL/RUC/RB |
vcfc:TandemRepeatAllele → vcfc:RepeatSequence |
yes (RN required) |
— | — |
gVCF <*> with END |
vcfc:ReferenceBlock, endPosition, referenceBlockLength |
yes (END required) |
— | — |
| VCF element | RDF term | Represented | Validated by | Mutation |
|---|---|---|---|---|
| FORMAT declaration | vcfc:formatRaw |
yes | q09_predicate_census |
— |
| FORMAT keys, in order | vcfc:hasFormatKey → vcfc:FormatKey (fieldIndex, declaredBy) |
yes | q09_predicate_census, q10_class_census |
— |
| Sample identity | vcfc:sampleId / vcfc:sampleName |
yes | preflight_sample_gt_inventory, q09 |
drop_sample_call |
| GT values (lexical) | vcfc:hasFormatValue → vcfc:fieldValue |
yes | q05, q06, q13 |
flip_genotype |
| Non-GT FORMAT (DP, GQ, AD, PL…) | vcfc:FormatFieldValue / vcfc:FormatValueVector |
yes | q13_format_value_digest |
drop_format_value_dp, corrupt_format_value_dp, corrupt_format_vector |
| Reusable sample identity | vcfc:SampleSet → vcfc:VCFSample (sampleName, sampleIndex); vcfc:forSample from a SampleCall |
yes, both profiles | — | — |
#CHROM sample columns |
vcfc:ColumnHeaderLine → vcfc:hasGenotypeColumns |
yes | — | — |
| FORMAT value ↔ declaration | vcfc:declaredBy → vcfc:FormatFieldDefinition |
yes | — | — |
Parsed genotype layer — expanded profile only. The condensed profile keeps
these values inside vcfc:encodedValues, which is the point of the profile.
| VCF element | RDF term | Represented | Validated by | Mutation |
|---|---|---|---|---|
GT parsed |
vcfc:hasGenotype → vcfc:Genotype (genotypeString, ploidy) |
expanded only | — | — |
Phasing (| vs /) |
vcfc:phasingStatus → vcfc:Phased / vcfc:Unphased / vcfc:MixedPhasing |
expanded only | — | — |
| Per-position phase indicator | vcfc:phaseIndicator on each vcfc:GenotypeAlleleCall |
expanded only | q09_predicate_census |
— |
| Each GT position | vcfc:hasAlleleCall → vcfc:GenotypeAlleleCall (callIndex, isNoCall, calledAllele) |
expanded only | — | — |
FT |
vcfc:sampleFilter |
expanded only | — | — |
PS/PSL/PSO/PSQ |
vcfc:inPhaseSet → vcfc:PhaseSet |
expanded only | — | — |
LAA |
vcfc:hasLocalAlleleSet → vcfc:LocalAlleleSet → vcfc:hasLocalAllele |
expanded only | — | — |
LAA membership order |
vcfc:hasLocalAlleleMembership → vcfc:LocalAlleleMembership (localAllele, localIndex) |
expanded only | — | — |
CN/CNQ/CNL/CNP |
vcfc:copyNumber and friends |
expanded only | — | — |
HAP/AHAP |
vcfc:haplotypeId, vcfc:ancestralHaplotypeId |
expanded only | — | — |
M*/DPM*/ADM*, numeric or aliased |
vcfc:BaseModification with a ChEBI modifiedResidue |
expanded only | — | — |
| VCF element | RDF term | Represented | Validated by | Mutation |
|---|---|---|---|---|
## line count and keys |
vcfc:HeaderLine + vcfc:headerKey |
yes | q08_header_line_census |
drop_header_line |
| Line type | FileFormatHeaderLine, INFOHeaderLine, ContigHeaderLine, … |
yes | q10_class_census |
untype_header_line |
##fileformat |
vcfc:fileFormat |
yes | q07_file_metadata |
corrupt_file_metadata |
##reference |
vcfc:referenceGenome |
yes | q07_file_metadata |
drop_reference_genome |
##source |
vcfc:sourceSoftware |
yes | q07_file_metadata |
— |
##fileDate |
vcfc:fileDate (xsd:date when the form allows) |
yes | q09_predicate_census |
drop_file_date |
##FILTER |
vcfc:FilterDefinition + vcfc:filterId |
yes | q09, q10 |
drop_filter_definition |
##ALT |
vcfc:AltDefinition + vcfc:altId |
yes | q09, q10 |
drop_alt_definition |
##contig |
vcfc:ContigHeaderLine + contigId/contigLength (xsd:integer)/contigMd5/contigAssembly/contigUrl (xsd:anyURI) |
yes | q09, q10 |
drop_contig_attribute |
##INFO / ##FORMAT definitions |
vcfc:InfoFieldDefinition / vcfc:FormatFieldDefinition with fieldId/fieldNumber/fieldType/fieldDescription |
yes | q09, q10 |
drop_info_definition |
Number arity and fixed count |
vcfc:fieldArity → vcfc:VCFNumberArity, vcfc:fieldNumberInteger |
yes | — | — |
##INFO Source / Version |
vcfc:fieldSource, vcfc:fieldVersion |
yes | — | — |
| Structured-line attributes (all of them) | vcfc:hasAttribute → vcfc:HeaderAttribute (attributeKey, attributeValue, attributeIndex) |
yes | — | — |
#CHROM column-header line |
vcfc:ColumnHeaderLine + vcfc:hasColumnHeader |
yes | — | — |
| Header line order | vcfc:lineIndex (xsd:integer) |
yes | — | — |
##assembly |
vcfc:AssemblyHeaderLine + vcfc:assemblyUrl |
yes | — | — |
##pedigreeDB |
vcfc:PedigreeDBHeaderLine + vcfc:pedigreeDbUrl |
yes | — | — |
##META |
vcfc:MetaHeaderLine / vcfc:MetaDefinition + vcfc:metaAllowedValue |
yes | — | — |
##SAMPLE |
vcfc:SampleHeaderLine → vcfc:declaresSample → vcfc:SampleDeclaration |
yes | — | — |
##PEDIGREE |
vcfc:PedigreeHeaderLine + pedigreeMother/pedigreeFather/pedigreeOriginal/pedigreeAncestor + ancestorRole |
yes | — | — |
| Header line values | vcfc:headerValue |
yes | q09 (count only) |
— |
vcfc:contigCount |
derived scalar | yes | count only, not value | corrupt_contig_count |
An unrecognized ## key is typed as vcfc:StructuredHeaderLine or
vcfc:UnstructuredHeaderLine according to the form of its value, which keeps it
queryable without inventing a subclass the vocabulary does not define.
Every structured line carries its vcfc:HeaderAttribute resources whether or
not it also has dedicated properties. vcfc:StructuredHeaderLineShape requires
at least one, and they are the only way an implementation-defined attribute
stays queryable. This is why --header-representation basic does not produce a
conformant graph.
| Property | Validated by | Mutation |
|---|---|---|
| N-Triples syntax | Raptor (rapper -c) |
— |
| SHACL shape conformance | --shacl-shapes (opt-in, independent layer) |
— |
| Per-record property cardinality | preflight_record_cardinality + exact count |
drop_pos |
| Missing-token policy | preflight_missing_token_conformance, fatal under --strict-conformance |
plain_dot_literal |
| Representation profile | preflight_representation_profile |
wrong_representation_profile |
| Record count | q01/q02/q04 totals | drop_record, duplicate_record |
Record order (vcfc:recordIndex) |
SPARQL SHACL profile: uniqueness, nondecreasing POS within CHROM, contiguous CHROM blocks | — |
| Expanded/condensed exclusivity | SPARQL SHACL profile | wrong_representation_profile |
| No extraneous triples | q09_predicate_census (extra rows) |
spurious_predicate |
| No blank nodes | preflight_blank_nodes |
introduce_blank_node, blank_node_object |
| No empty or whitespace-only terms | preflight_empty_values |
empty_literal, whitespace_only_literal |
| No duplicate statements | preflight_duplicate_triples (parsed vs distinct) |
duplicate_triple, duplicate_whole_graph |
| Class inventory | q10_class_census |
untype_header_line |
| Per-record identity | q11_record_digest |
permute_pos, permute_ref_alt |
-
vcfc:contigCountis counted, not read. A wrong derived contig total is not detected. Closing it needs the value in a comparison, not just the predicate in the census. -
Header line values are not compared.
q08compares how many lines carry each key andq10their types, butvcfc:headerValueitself is only counted. The structured attributes that matter (filterId,contigId, …) are covered. -
The census assumes the shipped mapping. A custom
--ruleschanges the inventory and IRI templates by design, soq09–q13fall back to report-only. That is correct, but it means a custom mapping is validated only by the aggregate comparisons. -
The new layers are counted but their values are not compared. The allele layer, header attributes, value items and the parsed genotype layer all appear in the predicate and class censuses, so dropping one is caught. Corrupting a value inside one is not:
corrupt_allele_value,corrupt_allele_kind,corrupt_attribute_value,corrupt_value_item_allele,corrupt_called_allele,flip_phasing_statusandwrong_declaration_ownerare all recorded as undetected. Closing them needs digests over those resources, the wayq11already covers the record fields — or leaning on the vocabulary's own consistency SHACL profile, which checks several of these agreements directly.The ID, FILTER and FORMAT-key decompositions join this group rather than forming a new one:
q09/q10expect an exact count of eachvcfc:RecordIdentifier,vcfc:FilterCodeandvcfc:FormatKey, derived from the source columns, so dropping one or emitting a spurious one is caught — butidentifierValue,filterCodeValueand the component ordinals are not compared against the file. No mutation targets them yet. -
Ordering is checked by shapes, not by queries.
vcfc:lineIndexandvcfc:recordIndexare counted, butcorrupt_record_indexis undetected by the query layer. The SPARQL SHACL profile enforces uniqueness, nondecreasing POS within a CHROM block, and contiguous CHROM blocks, so the coverage exists — in a different layer. -
Version overlays have no mutation. The converter detects each input's VCF version and follows it, and
drop_version_class/wrong_version_classcover the file class. The version-dependent behaviour — per-ALT versus per-recordCIPOSlinkage, the families a version does not define — is covered by unit tests and by the version overlays themselves, not by the mutation harness, because the fixture declares a single version. -
SHACL conformance is not yet a test. It was checked by hand and found seven real modelling bugs; wiring it in would make that a standing invariant. It needs
pyshaclandrdflibas dev dependencies.
The conversion targets the VCF Core vocabulary,
https://w3id.org/vcf-core/vocab# (prefix vcfc:), published at 2.0.0 and
covering VCF 4.1 through 4.5. It replaces the retired
https://w3id.org/vcf-rdfizer/vocab#, which is not redirected: it serves a
deprecation document in which every former term is present, deprecated, and
linked to its successor. Only one term was renamed rather than re-namespaced —
vcfr:DenseRepresentation became vcfc:ExpandedRepresentation.
The converter references the namespace, not a version IRI. The vocabulary's release cadence is independent of this converter's, and pinning a version here would go stale without adding anything: the namespace is what the emitted IRIs actually contain.
VCF Core supplies a conformance overlay per VCF version, and the converter
follows each input's own ##fileformat line automatically. Coverage:
| Version | Overlay | Detected | Notes |
|---|---|---|---|
| 4.0 | none claimed by the vocabulary | reported as unrecognized | converts with 4.5 rules and no vcfc:VCF4xFile class |
| 4.1 | vcf-4.1.shacl.ttl |
✓ vcfc:VCF41File |
no Number=R; CIPOS is one pair per record |
| 4.2 | vcf-4.2.shacl.ttl |
✓ vcfc:VCF42File |
Number=R added |
| 4.3 | vcf-4.3.shacl.ttl |
✓ vcfc:VCF43File |
|
| 4.4 | vcf-4.4.shacl.ttl |
✓ vcfc:VCF44File |
Number=P; tuples become per-ALT; CILEN/CICN/EVENTTYPE added |
| 4.5 | vcf-4.5.shacl.ttl |
✓ vcfc:VCF45File |
Number=LA/LR/LG/M; local alleles and base modifications |
See conversion.md for what each difference
changes in the emitted graph.
VCF Core 2.0.0 defines 338 terms. The converter emits from most of them, and its output is conformant, but the vocabulary can express more than the converter currently derives.
Five of the ten families previously listed here as entirely unemitted are now emitted. Two are complete; one term each remains in the other three:
| Family | Emitted | Still unemitted |
|---|---|---|
| Parsed FILTER | filterStatus, FiltersPassed/Failed/NotApplied, FilterCode, hasFilterCode, filterCodeValue, declaredByFilter |
— |
| Local-allele membership | LocalAlleleMembership, localAllele, localIndex |
— |
| Parsed FORMAT keys | FormatKey, hasFormatKey, fieldIndex |
keyPattern |
| Parsed record IDs | RecordIdentifier, hasIdentifier, identifierValue, componentIndex |
aliasOf — which identifier aliases which is not in the file |
| Mixed phasing | MixedPhasing, phaseIndicator |
allelePhaseSet |
Five families remain entirely unemitted:
| Family | Terms | What it would add |
|---|---|---|
| Padding semantics | PaddingInterpretation, paddingRule, paddingSide, paddingAnchorPosition, paddingBaseCount and their individuals |
Which base of REF/ALT is the VCF padding base, and why |
| Repeat units | RepeatUnit, hasRepeatUnit, repeatUnitBases, rucConfidenceInterval, rbConfidenceInterval |
RUB-level detail below a repeat sequence |
| Breakend mates | mateBreakend, partnerBreakend, insertedSequence, isTelomereBreakend |
Resolving a breakend to the record describing its mate |
| Reference blocks | hasReferenceBlock, blockAllele |
A record-level link to its gVCF block |
| Raw/decoded values | rawValue, decodedValue, percentEncodingPolicy, sampleFieldsRaw |
Percent-decoded companions beside the lossless source values |
None of these is required for conformance. Across 19 graphs — every example VCF the vocabulary ships, plus a stress matrix for 4.0 through 4.5 in both representation profiles — the complete profile set reports zero violations without them. They are enrichment the vocabulary supports and the converter does not yet derive.
This matters for benchmarking in one direction only: a benchmark of conversion throughput, graph size or compression is measuring the converter's actual output and is unaffected. A benchmark of query coverage against the vocabulary would be measuring a subset, and should say so.
The default mapping's output validates against the complete published
profile set — the portable and SPARQL profiles, the consistency profile, and all
five version overlays — with zero violations, in both representation
profiles, checked with pySHACL using the bundled ontology plus every
ontology/versions/*.ttl and RDFS inference, which is the configuration the
vocabulary's own tests/validate_shacl.py uses. See
validation-migration-notes.md
for exactly what was covered.
The only results reported are sh:Warnings where an input VCF omits the
Source and Version attributes that VCF 4.5 recommends on ##INFO
declarations. Those are properties of the input, and the converter deliberately
does not fabricate them.
- Validation methodology — how the coverage in this table is measured
- Validation — running the validator
- Conversion — how each represented element is emitted
- Roadmap — the vocabulary and coverage gaps above, with their planned fixes