Typing schemes¶
A typing document is the DATABASE-SIDE scoring spec that turns screening hits into phenotype
calls — serotypes, serogroups, pathotypes. Install one with gapit db build --typing FILE
and the database can designate any sample you screen against it. This page is the full
gapit.typing/2 reference plus a cookbook: six researched designation schemes encoded as
validated, self-tested example documents.
- For the database-side mechanics (
--typing, validation, manifests), see Databases. - For the designation output document (
gapit.typing_result/1), see Outputs. - The JSON Schema of a typing document prints with
gapit schema typing.
The two-stage designation workflow¶
Gene-kind designation is a two-stage pipeline (the rightsholder design): gapit screen
detects genes, gapit typing designates from the results.
$ gapit screen contigs.fa --db ecoli_dec --output result.tsv --quiet
$ gapit typing result.tsv --quiet
FILE SCHEME PHENOTYPE GENES CONFIDENCE SCORE RUNNER_UP NOTES
contigs.fa gb4789_6 EHEC escV;stx2a;uidA high 1.0000 EAEC (1.0000) ...
contigs.fa risk_monitoring EHEC escV;stx2a;uidA high 1.0000 EAEC (1.0000) ...
Stage 1 is pure gene detection: a typed gene database screens byte-identically to an
untyped one — the frozen 15-column abricate table, in every format. Stage 2 reads one or
more gapit/abricate screen result tables (TSV or CSV), resolves the database from the
rows' shared DATABASE column, folds every (FILE, GENE) to its best row by
(%IDENTITY, %COVERAGE) (first row wins ties — the same per-gene fold the report hits
got), and evaluates the database's typing.json over each FILE's genes.
The two stages also compose straight through a pipe — the canonical one-liner:
$ gapit screen 1.fna --db ecoli_dec --quiet | gapit typing --quiet
gapit typing reads its table from stdin whenever stdin is not a terminal (piped or
redirected); a bare invocation at a terminal keeps printing help. The explicit - marker
— gapit typing - — reads stdin even with a terminal attached, until EOF; mixing -
with file arguments is a usage error (v1 takes either stdin or files, never both). A
piped table's JSON source renders as ["-"].
Usage: gapit typing RESULT.tsv [RESULT2.tsv ...] [-D datadir] [-f tsv|json|md] [-o FILE]
[-q], or piped as above (- for explicit stdin). The default TSV carries one row per
(FILE, scheme) with the columns FILE, SCHEME,
PHENOTYPE, GENES, CONFIDENCE, SCORE, RUNNER_UP, NOTES — ambiguous calls render
- for the phenotype (and runner-up) and carry the candidate pair in NOTES; GENES
follows the phenotype with the FILE's present gene names (sorted, ;-joined), the same
list on each of the FILE's scheme rows — the hits that drove the call, in the open. json emits
gapit.typing_result/1 (full score breakdowns plus the same per-file genes list;
gapit schema typing_result); md renders
the same eight columns under a frontmatter naming the db and source tables. Typed errors:
mixed DATABASE values are DATABASE_MISMATCH, a table with no data rows TYPING_NO_DATA
(the database is only knowable from rows), a database without a typing.json
TYPING_NO_SCHEME, and a cluster database TYPING_CLUSTER_DB — cluster typing never goes
through the command.
The cluster exception. A cluster database's designation stays integrated in
gapit screen: its unit of typing is the best locus call, which only the minimap2
cluster engine can compute — there is no re-readable screen table for it. Screening a
typed cluster database annotates the cluster document directly — a single-scheme db
annotates gapit.cluster/1 (the typed-only PHENOTYPE TSV column, best.phenotype +
best.phenotype_detail in JSON/MD), and a multi-scheme db renders gapit.cluster/2
(one call per scheme in a phenotypes map, per-scheme winner-evidence TSV columns).
One document, two engines¶
A gapit.typing/2 document is a list of named schemes; each scheme is a list of
rules plus the decision thresholds applied to them:
{
"schema": "gapit.typing/2",
"schemes": [
{
"name": "serogroup",
"rules": [ /* one rule per candidate phenotype */ ],
"cutoff": 0.9,
"ambiguity_margin": 0.05,
"fallback": "NG"
}
]
}
Which engine evaluates the document depends on the database kind:
| Database kind | Input | Engine | Where the calls appear |
|---|---|---|---|
| gene | marker FASTA | blastn gene path | gapit typing over the screen results: gapit.typing_result/1 (JSON) and the eight-column TSV/MD table |
| cluster (one scheme) | GBK/GFF loci | minimap2 locus path | integrated in gapit screen: best.phenotype + best.phenotype_detail of gapit.cluster/1, plus the typed-only PHENOTYPE TSV column |
| cluster (multi-scheme) | GBK/GFF loci | minimap2 locus path | integrated in gapit screen: files[].phenotypes of gapit.cluster/2 (one call per scheme, compose rendered from its ingredients), plus per-scheme winner-evidence TSV columns |
Two hard constraints by kind:
- A gene database evaluates
weighted_genesandexact_setrules only (no loci exist to feedcluster_match/learned_linear). - A cluster database renders one of two contracts, picked by scheme count: a document
with exactly one scheme (a
gapit.typing/1document included) renders the byte-frozengapit.cluster/1— one phenotype slot per file,best.phenotype— while a document with more than one scheme rendersgapit.cluster/2: every scheme is called (schemes evaluate even without a best-locus call, so fallbacks always render), each file carries aphenotypesmap keyed by scheme name in document order,bestdrops its phenotype fields, and the TSV/CSV columns derive from the document: each compose scheme's value column renders first (SEROTYPE), then every non-compose scheme carries its call column plus its winning locus's evidence (<SCHEME>_LOCUS/_COVERAGE/_IDENTITY/_PRESENT/ _PARTIAL/_MISSING; theo_groupevidence prefix shortens toO) —-cells where no winner exists./2builds guarantee globally-unique gene ids: a/genename shared across loci is resolved at build time to its/locus_tag, or tolocus:id@startwhen no free locus_tag exists. A single compose-only scheme is unparseable (its placeholders must reference a non-composing sibling), so scheme count alone picks the contract.
Rule and scheme primitives¶
Every rule is discriminated by its model. Full details live in gapit schema typing;
this is the working reference.
Rule models¶
| Model | Scores | Key fields |
|---|---|---|
weighted_genes |
gene-presence sum, normalized to [0, 1] by the positive-weight total | weights, negative (count against the score when present), identity_floor (required), coverage_floor (optional), require_any (any-of gate that zeroes the rule when unsatisfied) |
exact_set |
1.0 iff every requires gene is present AND at least one requires_any gene is present (when that set is non-empty) AND every excludes gene is absent, else 0.0 |
requires, requires_any (any-of gate), excludes, floors defaulting to 90/90 |
cluster_match |
weighted coverage + identity + key-genes components over the best covered locus, each component floored | coverage{weight,floor}, identity{weight,floor}, key_genes{weight,genes} |
learned_linear |
sigmoid over weighted named features + bias | features (gene:<id>:<cov\|ident\|present>, cluster:<locus>:<coverage\|identity>), weights, bias, trained_on |
A gene "counts" as present for weighted_genes/exact_set when its best surviving hit (or
gene call, on the cluster path) clears the rule's floors. Keep coverage + identity weights
of a cluster_match rule below the cutoff: key genes should decide, so a fully covered but
key-gene-less locus cannot steal the call.
Scheme fields¶
| Field | Meaning |
|---|---|
name |
[a-z0-9_]+, unique in the document; the designation output keys on it |
cutoff |
top rule must reach it to call at all, else the fallback |
ambiguity_margin |
top − second must reach it, else the call is ambiguous (null phenotype, top two listed) |
fallback |
low-confidence output string (NT, NG, OUT, KUT, …) |
control_gene |
gate: when this gene is absent, the scheme outputs its fallback with the note control gene absent |
unique_group + mixed_phenotype |
when more than one gene of a group is present, call mixed_phenotype with the pair in ambiguous (mixed-infection signal) |
compose |
"{o_group}:{k_group}": a rule-less scheme rendered from sibling schemes' calls after they evaluate — fallback strings flow through, an ambiguous ingredient yields a null composition carrying that ingredient's variants |
notes (rules) |
free-text strings surfaced verbatim on the winning call — caveats, citations, flags |
One tie exception: a 1.0 tie that involves a satisfied exact_set resolves by declaration
order, not the margin (marker tables are mutually exclusive by construction, so a tie means
two rows matched and the earlier-declared one wins). Non-exact ties stay ambiguous.
The cookbook¶
Six example documents live in
tests/data/typing/schemes/,
one marker FASTA or GBK loci file each. Every document validates against the current schema,
and tests/test_typing_schemes.py / tests/test_typing_schemes_cluster.py build each fixture
database and assert the phenotype calls below. All sequences are synthetic — they encode
the scheme logic, not curated content; real curation is future work per provider licensing.
The one curated exception sits beside them: the bundled lm_doumith panel (real public-domain
INSDC sequences, its own test module) described first below.
Listeria serogroups — the bundled lm_doumith panel (gene db)¶
lm_doumith is the sixth bundled database: the Doumith markers extracted from public-domain
INSDC records and the doumith_serogroup scheme (gapit.typing/2) — the same five-marker
multiplex-PCR semantics as LisSero, with prs as the genus-level control_gene and 95/95
floors on every rule (Doumith et al. 2004, J Clin Microbiol 42:3819):
| Call | requires | excludes |
|---|---|---|
IVb-v |
ORF2819, ORF2110, lmo0737 |
lmo1118 |
IVb (4b, 4d or 4e) |
ORF2819, ORF2110 |
lmo0737, lmo1118 |
IIb (1/2b, 3b or 7) |
ORF2819 |
lmo0737, lmo1118, ORF2110 |
IIc (1/2c or 3c) |
lmo0737, lmo1118 |
ORF2819, ORF2110 |
IIa (1/2a or 3a) |
lmo0737 |
lmo1118, ORF2819, ORF2110 |
IVb-v — the 4b variant carrying the lineage-II lmo0737 cassette (Huang 2011 "unusual
4b"; FDA 4bV; ST382/ST554 emergent clones) — is declared before IVb so a shared
ORF2819+ORF2110 profile carrying lmo0737 resolves to the variant. Uppercase gene ids
(ORF2819, ORF2110) travel verbatim through build, screen, and typing. Two-stage use,
zero network (the bundle materializes on first screen):
$ gapit screen isolates.fa --db lm_doumith --output lm.tsv --quiet
$ gapit typing lm.tsv --quiet
FILE SCHEME PHENOTYPE GENES CONFIDENCE SCORE RUNNER_UP NOTES
lm_doumith_iia.fa doumith_serogroup IIa lmo0737;prs high 1.0000 IVb-v (0.0000) ...
lm_doumith_ivb.fa doumith_serogroup IVb ORF2110;ORF2819;prs high 1.0000 IVb-v (0.0000) ...
Fallback untypeable (4a/4c, atypical profile, or non-Lm Listeria); without prs the
control gate fires (control gene absent). Known limits ride the calls as notes: 4b/4d/4e
share every marker, EGD-e types as IIc (documented in Doumith 2004), and HGT of the ORF
markers can fake a IIb profile — see the lm_doumith section in the
databases guide.
Listeria serogroups — the Doumith table (gene db)¶
doumith.json + doumith.fa: the complete five-marker multiplex-PCR table from Doumith et
al. 2004 (J Clin Microbiol 42:3819) — prs as the scheme control_gene, and one exact_set
rule per serogroup over lmo0737, lmo1118, orf2819, orf2110:
| Serogroup | requires | excludes |
|---|---|---|
1/2a-3a |
lmo0737 |
lmo1118, orf2819, orf2110 |
1/2c-3c |
lmo0737, lmo1118 |
orf2819, orf2110 |
1/2b-3b-7 |
orf2819 |
lmo0737, lmo1118, orf2110 |
4b-4d-4e |
orf2819, orf2110 |
lmo0737, lmo1118 |
Fallback NT (4a/4c and atypical profiles). The 4b rule carries the caveat notes, including
the 4b (IVb-v1) horizontal-gene-transfer trap: 4b strains acquired the lineage-II
lmo0737 cassette, match no plain pattern, and fall back to NT — the test suite pins
exactly that call (J Clin Microbiol 2022, PMID 36472431).
Shigella / EIEC — ShigaTyper semantics (gene db)¶
shigella.json + shigella.fa: a skeleton of ShigaTyper (Wu et al. 2019, Appl Environ
Microbiol 85:e00165-19) with ipaH_c as the control gene and the wzx variants wired as one
unique_group (mixed_phenotype: "mixed Shigella serotypes" — ShigaTyper's "multiple wzx"
checkpoint becomes a mixed call). Highlights:
- S. sonnei form I (
exact_setoverSs_wzx/Ss_wzy) is declared before the form II rule (weighted_genesonSs_methylase): a form I sample whose antigen is phase-variable satisfies both at 1.0, and the exact-tie exception resolves to form I. - S. dysenteriae 1 requires
Sd1_wzx+Sd1_rfp, with a note on the rfp-negative variant needing its own rule in a full panel. - S. flexneri serotype conversions as exact sets over
gtr/Oac(2a,2b,3a) plus aY/novelcatch-all (requiresthe Sf base,excludesthe listed conversion genes) — with a full panel that rule splits into Y and ShigaTyper's "novel serotype". - EIEC approximates ShigaTyper's checkpoint 3:
exact_setrequiresEclacYandexcludesthe exemption genesSb9_wzx/Sb15_wzx;cadAand ipaB plasmid-coverage nuances are documented as unmodelled in the note.
N. meningitidis serogroups — meningotype panel (gene db)¶
meningotype.json + meningotype.fa: ctrA control gene, one weighted_genes rule per
serogroup over the Mothershed et al. 2004 (J Clin Microbiol 42:320–328) real-time panel
(sacB/A, synD/B, synE/C, xcbB/X, synF/Y, as named by the meningotype tool) — and
W/Y through the EX7E allele probes synG_EX7E_P / synG_EX7E_G at identity_floor
99.5 (see below). Fallback NG.
V. parahaemolyticus O and K — the Kaptive pattern (cluster db)¶
vp_ok.json + vp_ok.gbk: OAgc/CPSgc-like loci after the Kaptive databases of
van der Graaf-van Bloois et al. 2023 (Microb Genom 9:mgen001007; 16 O and 71 K serotypes).
Three schemes:
o_group—cluster_matchrules (coverage 0.55 / identity 0.25 / key genes 0.2, floors 95/95) with fallbackOUT(O untypeable). TheO3/O13rule encodes the combined label: the O3 and O13 loci contain the same genes and cannot be distinguished by gene content.k_group— the same shape over the K loci, fallbackKUT(K untypeable).serotype— the"{o_group}:{k_group}"compose scheme documenting the panel output.
The vp_ok cookbook document installs as one merged database rendering
gapit.cluster/2: every scheme is called per file (o_group, k_group, then the
serotype compose rendered from its ingredients — O3/O13:K6 in one scan), and its
TSV row puts the composed serotype FIRST and then gives each scheme its call plus the
winning locus's own evidence (coverage, identity, gene counts, missing ids):
FILE SEROTYPE O_GROUP O_LOCUS O_COVERAGE ... K_GROUP K_LOCUS K_COVERAGE .... The
historical /1 install —
two databases (vp_o, vp_k) built from the same GBK with one scheme each,
exactly how Kaptive ships separate O and K databases — remains fully valid: a
single-scheme document keeps rendering byte-frozen gapit.cluster/1, and the split
is still what you want when the O and K calls belong to different pipelines. The
test suite asserts the merged /2 goldens (cluster2_vp_o3k6, cluster2_vp_none)
and the split /1 calls (O3/O13, K6) side by side.
V. cholerae O1/O139 and Ogawa/Inaba (cluster db)¶
cholerae.json + cholerae.gbk: loci wbe (O1: wbeV, wbeW, wbeT) and wbm (O139:
wbmV, wbmW, wbfZ) plus a non-typeable wbc locus, with two schemes:
serogroup—cluster_matchO1 / O139 with fallbacknon-O1/non-O139. The O139 rule's note records the wbfZ trap: thewb*cassette is exchanged through the conservedgmhD/rjgjunctions, so environmental serogroups can carry O139-like cassettes and trip single-genewbfZassays (Sozhamannan et al. 1999, Infect Immun 67:6215) — the rule keys on the wholewbmlocus instead.subserotype— Ogawa/Inaba through the wbeT allele probe: ogawa =weighted_genesonwbeTatidentity_floor99.9 (the determinant is single mutations in the wbeT methyltransferase; Stroeher et al. 1992, PNAS 89:2566; BMC Microbiol 2013, 13:173); inaba =wbeVpositive withwbeT: -2.0innegative(the Inaba definition: wbeT not clearing the allele floor), with the Hikojima note — rare, unstable, no stable reference, not determinable. A mutated-wbeT sample callsO1on the serogroup database andinabaon the subserotype database at the same time (thecluster_vc_inabagolden).
Diarrheagenic E. coli — the dual-scheme designation (gene db)¶
dec.json + dec.fa: the DEC designation as two schemes over one gene database — the
typing/2 multi-scheme showcase. Both schemes use exact semantics (cutoff 1.0, margin 0.0),
uidA as the control_gene, fallback non-DEC, and one severity-ordered rule ladder
(EHEC > STEC/EPEC > ETEC > EIEC > EAEC — hybrids surface as the runner_up, decided by
declaration order):
| Pathotype | requires | requires_any | excludes |
|---|---|---|---|
EHEC |
escV |
stx1a/stx1b/stx2a/stx2b |
|
STEC |
stx1a/stx1b/stx2a/stx2b |
escV |
|
EPEC |
escV, bfpB |
||
EPEC_atypical |
escV |
bfpB, every stx subunit |
|
ETEC |
lt/sth/stp |
||
EIEC |
invE |
||
EAEC (gb4789_6) |
aggR/pic/astA |
||
EAEC (risk_monitoring) |
aggR |
gb4789_6encodes the GB 4789.6-2016 panel semantics: EAEC is any of aggR/pic/astA.risk_monitoringencodes the current food-safety risk-monitoring variant (最新食品安全风险监测方案): aggR is mandatory — pic/astA alone are insufficient. A pic+astA isolate without aggR isEAECunder gb4789_6 andnon-DECunder risk_monitoring — the headline divergence between the two schemes, locked by theecoli_dec_typinggolden (both calls in onegapit.typing_result/1document; the bundled database carries the same document).EPEC_atypicalis declared after EHEC/STEC so stx-positive isolates never land there; escV+/bfpB−/stx− isolates fall through the typical-EPEC rule onto it.- The fixture mirrors the rightsholder database's duplicated records (pic ×2, sth ×3 there;
one duplicate each in the fixture): duplicate records collapse by gene name — blastn's
-culling_limit 1keeps the best subject per query span and the engine folds one gene call per name.
The allele probe trick (and its successor)¶
Two cookbook schemes discriminate alleles — not genes — using presence/absence primitives:
- EX7E (meningotype): the synG EX7E motif peptide reads P in serogroup W, G in Y, S in
dual W/Y (meningotype's
menwycheck). The fixture carries the two allele sequences as separate probe records atidentity_floor99.5: only the exact allele clears the floor, because a single SNP in a ~120 bp probe already drops below 99.5% identity. - wbeT (cholerae): Ogawa vs Inaba is single mutations in wbeT, so ogawa requires wbeT at 99.9 and inaba is the negative rule — an inactivating mutation (98% identity) still maps, still counts as a present gene for the locus rules, but fails the allele floor.
Two practical constraints:
- The probe records must stand alone in the database. gapit runs blastn with abricate's
-culling_limit 1(parity is a feature), and a long record containing the probe region would shadow the probe's alignment — so the meningotype fixture has no full-length synG record next to the probes. - An allele between the probes (the dual W/Y S motif, or a novel wbeT missense) clears neither floor and lands on the fallback — allele-level ambiguity, surfaced as a low confidence call.
When a future allele_match primitive lands (pattern + allele table, judged on the aligned
query sequence the way meningotype translates its EX7E window), both tricks supersede to a
single rule with explicit alleles.
Calibration and the learned_linear outlook¶
Tuning thresholds against labeled assemblies is what
scripts/cluster_calibration.py
is for (developer tool): point it at a typed cluster database and a label TSV and it prints
the per-expected-phenotype score distribution, the called×expected agreement matrix, and the
divergence list:
$ python scripts/cluster_calibration.py mycps --datadir ./db labels.tsv
That report is the loop a future learned_linear training flow consumes: the feature
vectors (gene:<id>:cov|ident|present, cluster:<locus>:coverage|identity) are exactly the
calibration columns, so a trained model drops into the same scheme as one more rule.
Building and typing a cookbook scheme¶
$ gapit db build doumith doumith.fa --typing doumith.json
{"db":"doumith","records":5,"dbtype":"nucl","destination":".../doumith"}
$ gapit screen lm_4b.fa --db doumith --output lm_4b.tsv --quiet
$ gapit typing lm_4b.tsv --quiet
FILE SCHEME PHENOTYPE GENES CONFIDENCE SCORE RUNNER_UP NOTES
lm_4b.fa doumith 4b-4d-4e orf2110;orf2819;prs high 1.0000 1/2a-3a (0.0000) ...
$ gapit screen vc_inaba.fa --db vc_subserotype --quiet
FILE BEST_LOCUS TYPE PHENOTYPE COVERAGE IDENTITY PRESENT PARTIAL MISSING_IDS
vc_inaba.fa wbe O1 inaba 100.00 99.44 3 0 -
The second block is the cluster exception: a typed cluster database designates inside
gapit screen (no second stage — there is no screen table to re-read).
The cluster O/K pattern: the merged /2 install (one scan, every scheme) and the historical /1 split (one database per scheme):
$ gapit db build vp_ok vp_ok.gbk --typing vp_ok.json # ONE merged db → gapit.cluster/2
$ gapit screen sample.fa --db vp_ok --quiet
FILE SEROTYPE O_GROUP O_LOCUS O_COVERAGE ... K_GROUP K_LOCUS K_COVERAGE ...
sample.fa O3/O13:K6 O3/O13 OL3_or_O13 100.00 ... K6 KL6 100.00 ...
$ gapit db build vp_o vp_ok.gbk --typing vp_o_group.json # /1 split (one scheme each)
$ gapit db build vp_k vp_ok.gbk --typing vp_k_group.json
$ gapit screen sample.fa --db vp_o --quiet # → O3/O13
$ gapit screen sample.fa --db vp_k --quiet # → K6