Skip to content

Writing an annotator

An annotator predicts an effect for a variant on a transcript. Register a custom implementation when you want to compare your model through Varcode's public annotation interface. For the built-in experiments, see Experimental annotators.

Required method

An annotator needs a name and an annotate_on_transcript method. It may also provide a version for provenance.

Return a MutationEffect for a supported input, or Python's NotImplemented singleton for an input your implementation cannot handle. No capability list is required.

For example, this adapter takes a callable that returns an effect or None:

class MyAnnotator:
    name = "my_model"

    def __init__(self, model):
        self.model = model

    def annotate_on_transcript(self, variant, transcript):
        prediction = self.model(variant, transcript)
        if prediction is None:
            return NotImplemented
        return prediction

Register an instance, then select it explicitly:

import varcode

annotator = MyAnnotator(my_model)
varcode.register_annotator(annotator)
effects = variant.effects(annotator=annotator.name)

Here my_model is your prediction callable and variant is a loaded variant.

Unsupported inputs and errors

Public APIs convert NotImplemented into Unresolved(mechanism="unsupported_annotation", reason=...), preserving the selected annotator's provenance. They do not silently fall back to the default. For example, selecting protein_diff for an SV returns Unresolved, including inside use_annotator("protein_diff").

Returning None directly is a plugin error. Exceptions retain the ordinary raise_on_error behavior; do not turn unexpected failures into unsupported inputs.

Germline context

An optional annotate_with_context(variant, transcript, germline_ctx, phase_resolver=None) method has the same return contract. Without it, nonempty germline context produces Unresolved rather than being ignored or sent to another implementation. Empty context uses annotate_on_transcript.

effects(), effect_on_transcript(), and predict_variant_effect_on_transcript() use the same selection rules, including the scoped default. Combined haplotype construction is still collection-owned; see the current limitation.

Default implementation

The default owns point-variant prediction, structural handling, and the existing germline-aware point-edit path. Structural code lives in varcode.effects.structural as internal helpers; both the default and the transcript model's fusion path call those helpers directly. There is no separate structural annotator or automatic router between implementations.

Structural transcript models can retain the builder label structural_variant in their provenance, while the effect collection records the selected annotator. A stored builder label is not a selectable annotator name.

Testing an integration

Test supported inputs, explicit refusal, context-dependent refusal, and genuine errors separately. Check direct transcript calls as well as collections, and preserve provenance and unresolved results when serializing.

Compare implementations only on inputs they both support. Passing a parity test for point edits does not establish SV support. When sending results to other tools, retain candidate evidence, transcript identity, and sequence completeness; a protein string alone does not establish a complete expressed protein.