Edit on GitHub

starplast.identity

Gene identity: resolve every string the literature uses for a gene to one canonical gene_id.

This layer exists because the literature does not use one identifier. The same gene appears as TGME49_208830, as the pre-2012 accession TGME49_008830, as the GT1-strain accession TGGT1_208830, as the symbol GRA16, and as TgGRA16. Before this module, build_graph matched only current ME49 accessions and ToxoDB symbols, so every other form was silently discarded -- it did not even fail loudly, because unmatched accessions were dropped by an intersection with the node table.

Design decisions:

  • Ambiguity is recorded, never guessed. A string resolving to more than one gene is excluded from lookup AND kept in ambiguous with its collision set, so the cost of the exclusion is reportable instead of invisible. This follows the project's existing rule for ambiguous symbols.
  • Every match carries a kind. Accession matches are near-certain; symbol matches in a full-text body are the ones that can collide with host-gene names. Downstream code can therefore report coverage by confidence tier rather than as one undifferentiated number.
  • Cross-strain accessions map by numeric suffix. TGGT1_208830 -> TGME49_208830. This is the VEuPathDB naming convention for syntenic orthologs, and it is verified, not assumed: of the ME49/GT1 pairs sharing a suffix and an OrthoMCL release, 99.53% share an orthogroup (VEG: 99.46%). The mapping is accepted only when the suffix exists in the node table.
  • Symbols need a shape that survives free text. A symbol is matchable only if it contains a digit or is four or more capitals -- "AAP" would otherwise match prose, while "GRA16" and "MORC" would not.

Build the index from data/toxodb_identity.tsv (see fetch_names.py) plus the node table.

KINDS = ('accession', 'accession_prev', 'accession_strain', 'symbol', 'alias')
ACC_RX = re.compile('\\bTG[A-Z0-9]{2,6}_(\\d{5,6})[A-Za-z]?\\b', re.IGNORECASE)
PF_ACC_RX = re.compile('\\b(?:PF3D7_(\\d{6,7})|PF[A-Z]\\d{4}[wc]|PF\\d{2}_\\d{4}|MAL\\d+P\\d+\\.\\d+)\\b', re.IGNORECASE)
TOKEN_RX = re.compile('[^\\W\\d_][\\w-]{2,}')
def norm(s: str) -> str:

Normalize a candidate string to a lookup key: upper case, no hyphens/spaces/underscores.

BLOCKED_SYMBOLS = frozenset({'CEP', 'VAND', 'B73', 'RUB', 'TGTOUCAN', 'B41', 'CTG', 'VEG', 'PLK', 'PRU', 'GAB', 'ME49', 'COUG', 'BOF', 'QHO', 'WH3', 'WH6', 'P89', 'FOU', 'TGCKAU', 'TGCATBR5', 'MAS', 'RH', 'ARI', 'GT1'})
def matchable_symbol(sym: str) -> bool:

True if a symbol is specific enough to match in running prose without catching English.

@dataclass
class GeneIndex:

Canonical gene ids plus every string that resolves to exactly one of them.

GeneIndex( canonical: set = <factory>, lookup: dict = <factory>, ambiguous: dict = <factory>, suffix: dict = <factory>, strict: set = <factory>, accession_rx: object = re.compile('\\bTG[A-Z0-9]{2,6}_(\\d{5,6})[A-Za-z]?\\b', re.IGNORECASE), canonical_prefix: str = 'TGME49', symbol_prefix: str = 'Tg')
canonical: set
lookup: dict
ambiguous: dict
suffix: dict
strict: set
accession_rx: object = re.compile('\\bTG[A-Z0-9]{2,6}_(\\d{5,6})[A-Za-z]?\\b', re.IGNORECASE)
canonical_prefix: str = 'TGME49'
symbol_prefix: str = 'Tg'
def stats(self) -> dict:

Counts of how many accessions resolved, and how many were ambiguous.

def find(self, text: str):

Yield (gene_id, kind) for every gene mentioned in text. Duplicates are yielded per hit.

def build_index( node_ids, identity_tsv: str, log=<built-in function print>, accession_rx=re.compile('\\bTG[A-Z0-9]{2,6}_(\\d{5,6})[A-Za-z]?\\b', re.IGNORECASE), canonical_prefix: str = 'TGME49', symbol_prefix: str = 'Tg') -> GeneIndex:

Build a GeneIndex over node_ids from a VEuPathDB identity table.

The table's shape is the same on both sites -- gene_id, gene_name, previous_ids -- so the only thing that differs between arms is what an accession looks like in prose, which is why that is an argument rather than a constant.

STRAIN_PREFIXES = {'GT1': 'TGGT1', 'VEG': 'TGVEG'}
def add_strain_accessions( ix: GeneIndex, strain_tsvs: dict, log=<built-in function print>) -> GeneIndex:

Register GT1/VEG accessions, which map to ME49 by numeric suffix (see module docstring).

Two passes, and the second one matters. The first walks the strain TSVs, which is how the accessions carrying a trailing letter get registered. But the rule this module documents is that a cross-strain accession is accepted when its suffix exists in the node table -- it says nothing about the accession having been enumerated in a file. Relying on the file alone made resolution depend on that file's completeness, and it is not complete: TGGT1_212960, _251570, _297960 and _310430 are absent from the 8,637-row GT1 list while all four ME49 genes are in the node table, so four genes of the splitCas9 imaging screen silently failed to join. The second pass closes that by registering the implied accession for every suffix the node table knows.

It can only add. Each suffix maps to exactly one ME49 gene, so no existing resolution changes.