Skip to content

Explore two CIViC bundles

Data files

Example files: civic-assertion-251-bundle.json · civic-assertion-9-bundle.json · civic-gks-bundle-v0.1.0.schema.json

Rendered from notebooks/civic/explore-civic-bundles.ipynb.

This notebook uses two small CIViC examples to show how ga4gh.gkm.bundles loads and explores GKM bundles. You will discover what each bundle contains, work with GKM Python objects, and follow relationships between them. Both examples use the same Python workflow even though CIViC defines their collection names and organization.

Set up

Import the bundles package

In [1]
import json
from pathlib import Path

from ga4gh.gkm import bundles

Locate the bundles and their schema

A bundle is a producer-defined JSON file containing related GKM objects grouped into named collections. A separate bundle schema describes that organization and identifies the GKM schema versions it uses. These examples include:

  • civic-aid-9-bundle.json, which contains a Tier II clinical-significance assertion under the AMP/ASCO/CAP Guidelines (2017).
  • civic-aid-251-bundle.json, which contains a likely-oncogenic assertion under the ClinGen/CGC/VICC Guidelines for Oncogenicity (2022).
  • civic-gks-bundle-v0.1.0.schema.json, which defines the CIViC-specific collections and identifies the GKM schemas they use.

The two bundles have the same organization, so they share one schema. The path setup works when Jupyter starts either at the repository root or in notebooks/civic.

In [2]
bundle_dir = Path("bundles")
if not bundle_dir.is_dir():
    bundle_dir = Path("notebooks/civic/bundles")

if not bundle_dir.is_dir():
    message = "Run this notebook from the repository root or notebooks/civic"
    raise FileNotFoundError(message)

aid_9_path = bundle_dir / "civic-assertion-9-bundle.json"
aid_251_path = bundle_dir / "civic-assertion-251-bundle.json"
schema_path = bundle_dir / "civic-gks-bundle-v0.1.0.schema.json"

Check GKM version compatibility

The bundle schema identifies the GKM product versions used by its collections. Check that those versions are compatible with the installed GKM Python libraries before interpreting the data.

This checks GKM versions; it does not perform full JSON Schema validation. load_bundle(..., schema=...) runs the same check automatically.

In [3]
with schema_path.open(encoding="utf-8") as stream:
    civic_schema = json.load(stream)

bundles.check_gkm_version_compatibility(civic_schema)
bundles.supported_gkm_versions()

Output:

{'gks-core': '1.1.0',
 'vrs': '2.1.0-snapshot.2026-02.2',
 'cat-vrs': '1.1.0-snapshot.2026-02.3',
 'va-spec': '1.1.0-snapshot.2026-06.1'}

Load and inspect

Register and load the bundles

The bundle registry associates a short name with a bundle file, its schema, and producer information. This is useful when an application will load the same source more than once. Registration is local; it does not download or publish the bundle.

replace=True makes this cell safe to rerun. After registration, load_bundles() loads both examples by name and uses their registered schemas for the compatibility check.

In [4]
for name, source in {
    "civic-assertion-9": aid_9_path,
    "civic-assertion-251": aid_251_path,
}.items():
    bundles.registry.register(
        bundles.BundleRegistration(
            name=name,
            source=source,
            schema=schema_path,
            producer="CIViC",
        ),
        replace=True,
    )

loaded = bundles.load_bundles("civic-assertion-9", "civic-assertion-251")
loaded

Output:

{'civic-assertion-9': Bundle(name='civic-assertion-9', collections=17),
 'civic-assertion-251': Bundle(name='civic-assertion-251', collections=17)}
In [5]
aid_9 = loaded["civic-assertion-9"]
aid_251 = loaded["civic-assertion-251"]
aid_9, aid_251

Output:

(Bundle(name='civic-assertion-9', collections=17),
 Bundle(name='civic-assertion-251', collections=17))

Handle an unknown bundle

Package-specific errors share the BundleError base class and identify the operation that failed. For example, loading an unknown registered name raises BundleNotFoundError.

In [6]
try:
    bundles.load_bundle("civic-assertion-missing")
except bundles.BundleNotFoundError as error:
    print(error)

Output:

Unknown bundle 'civic-assertion-missing'

Inspect the collections

The Starter Kit preserves the producer's collection names. For example, CIViC's sequenceReference collection maps sequence identifiers to sequence-reference objects. Names such as sequenceReference, evidence, and assertion come from CIViC's bundle schema. Use collection_names() to discover what a loaded bundle contains.

In [7]
aid_9.collection_names()

Output:

('sequenceReference',
 'location',
 'variant',
 'feature',
 'molecularProfile',
 'disease',
 'phenotype',
 'conditionSet',
 'therapy',
 'therapyGroup',
 'variantOrigin',
 'source',
 'method',
 'organization',
 'proposition',
 'evidence',
 'assertion')

Access a collection

Each named collection is represented by a BundleCollection, which maps the producer's object identifiers to objects. Collections support attribute access, mapping syntax, and the explicit collection() method.

In [8]
sequence_references = aid_9.sequenceReference

assert isinstance(sequence_references, bundles.BundleCollection)
assert sequence_references is aid_9["sequenceReference"]
assert sequence_references is aid_9.collection("sequenceReference")

sequence_references

Output:

BundleCollection(name='sequenceReference', size=3)

Handle an unknown collection

Attribute access with an unknown collection name raises BundleCollectionNotFoundError, which is also an AttributeError.

In [9]
try:
    aid_9.sequenceReferences  # noqa: B018
except bundles.BundleCollectionNotFoundError as error:
    print(error)

Output:

Unknown collection 'sequenceReferences'

List object identifiers

Use keys() to see which object identifiers are available in the collection.

In [10]
list(sequence_references.keys())

Output:

['SQ.6CnHhDq_bDCsuIBf0AzxtKq_lXYM7f0m',
 'SQ.EJQv9rQmiD76iFmXsLwCy2dJHOFO3bpj',
 'SQ.pnAqCRBrTsUoBghSD1yp_jXWSmlbdh4g']

Work with GKM objects

Use a typed GKM object

When an object is supported by an installed GKM reference library, ga4gh.gkm.bundles loads it as that library's Python model instead of leaving it as a raw dictionary. The example below retrieves a VRS SequenceReference by its identifier.

In [11]
sequence_id = "SQ.6CnHhDq_bDCsuIBf0AzxtKq_lXYM7f0m"
sequence_reference = sequence_references[sequence_id]

sequence_reference.type, sequence_reference.refgetAccession

Output:

('SequenceReference', 'SQ.6CnHhDq_bDCsuIBf0AzxtKq_lXYM7f0m')

Handle an unknown identifier

Looking up an unknown identifier in a BundleCollection raises BundleObjectNotFoundError, which is also a KeyError.

In [12]
missing_sequence_id = "SQ.not-found"

try:
    sequence_references[missing_sequence_id]
except bundles.BundleObjectNotFoundError as error:
    print(error)

Output:

"Unknown identifier 'SQ.not-found' in collection 'sequenceReference'"

Resolve a local reference

The assertion does not contain a second copy of its proposition. Its proposition field instead contains a local JSON Pointer such as #/proposition/..., which identifies a path within the same bundle. resolve() follows that reference.

In [13]
assertion_9 = aid_9.assertion["civic.aid:9"]
proposition_9 = aid_9.resolve(assertion_9["proposition"])

resolved_proposition = {
    "localReference": assertion_9["proposition"],
    "resolvedObject": proposition_9.model_dump(mode="json", exclude_none=True),
}
print(json.dumps(resolved_proposition, indent=2))

Output:

{
  "localReference": "#/proposition/civic.proposition:f0SrtLbW05PqfqLs-hOK4tZxI3xO3kMO",
  "resolvedObject": {
    "id": "civic.proposition:f0SrtLbW05PqfqLs-hOK4tZxI3xO3kMO",
    "type": "VariantClinicalSignificanceProposition",
    "subjectVariant": "#/molecularProfile/civic.mpid:1594",
    "geneContextQualifier": "#/feature/civic.gid:154",
    "alleleOriginQualifier": "#/variantOrigin/civic.variantOrigin:SOMATIC",
    "predicate": "hasClinicalSignificanceFor",
    "objectCondition": "#/disease/civic.did:2950"
  }
}

dereference() replaces every reachable local pointer with inline data, producing a self-contained JSON-compatible view. The complete result can be large, so this example displays only a few fields from the expanded variant and condition.

In [14]
inline_proposition_9 = aid_9.dereference(proposition_9)
inline_preview = {
    "type": inline_proposition_9["type"],
    "subjectVariant": {
        "id": inline_proposition_9["subjectVariant"]["id"],
        "name": inline_proposition_9["subjectVariant"]["name"],
    },
    "objectCondition": {
        "id": inline_proposition_9["objectCondition"]["id"],
        "name": inline_proposition_9["objectCondition"]["name"],
    },
}
print(json.dumps(inline_preview, indent=2))

Output:

{
  "type": "VariantClinicalSignificanceProposition",
  "subjectVariant": {
    "id": "civic.mpid:1594",
    "name": "ACVR1 G328V"
  },
  "objectCondition": {
    "id": "civic.did:2950",
    "name": "Diffuse Midline Glioma, H3 K27-altered"
  }
}

Compare assertions

The two bundles have the same CIViC-defined organization, but their assertions answer different scientific questions. A small helper can apply the same workflow to both and compare selected fields.

In [15]
def assertion_summary(bundle: bundles.Bundle, assertion_id: str) -> dict[str, str]:
    """Summarize one assertion and its referenced proposition.

    :param bundle: Bundle containing the assertion.
    :param assertion_id: Collection key for the assertion.
    :return: Selected assertion, classification, and proposition fields.
    """
    assertion = bundle.assertion[assertion_id]
    proposition = bundle.resolve(assertion["proposition"])
    coding = assertion["classification"]["primaryCoding"]
    return {
        "assertion": assertion_id,
        "classification_system": coding["system"],
        "classification": coding["code"],
        "proposition_type": str(proposition.type),
        "predicate": str(proposition.predicate),
    }


summaries = [
    assertion_summary(aid_9, "civic.aid:9"),
    assertion_summary(aid_251, "civic.aid:251"),
]
print(json.dumps(summaries, indent=2))

Output:

[
  {
    "assertion": "civic.aid:9",
    "classification_system": "AMP/ASCO/CAP Guidelines, 2017",
    "classification": "tier ii",
    "proposition_type": "VariantClinicalSignificanceProposition",
    "predicate": "hasClinicalSignificanceFor"
  },
  {
    "assertion": "civic.aid:251",
    "classification_system": "ClinGen/CGC/VICC Guidelines for Oncogenicity, 2022",
    "classification": "likely oncogenic",
    "proposition_type": "VariantOncogenicityProposition",
    "predicate": "isOncogenicFor"
  }
]

Prepare data for another tool

Suppose a review tool needs one assertion and its linked variant and condition, rather than the complete CIViC bundle. to_dict() provides JSON-compatible bundle values, which you can select and combine with the dereferenced proposition to create that smaller payload.

The resulting review_payload is application-specific data, not a CIViC bundle. This walkthrough keeps it in memory; an application could serialize it as JSON when needed.

Use Bundle.to_dict() for the complete bundle and model_dump() for an individual GKM Python model.

In [16]
bundle_data = aid_9.to_dict()
review_payload = {
    "assertion": bundle_data["assertion"]["civic.aid:9"],
    "proposition": inline_proposition_9,
}

review_summary = {
    "assertionId": review_payload["assertion"]["id"],
    "classification": review_payload["assertion"]["classification"]["name"],
    "variant": review_payload["proposition"]["subjectVariant"]["name"],
    "condition": review_payload["proposition"]["objectCondition"]["name"],
}
print(json.dumps(review_summary, indent=2))

Output:

{
  "assertionId": "civic.aid:9",
  "classification": "Tier II",
  "variant": "ACVR1 G328V",
  "condition": "Diffuse Midline Glioma, H3 K27-altered"
}