Skip to content

Resolve classifier labels and taxa-list matches through synonym_of, not name-only #1405

Description

@mihow

Summary

Class masking, and the sibling classification-time taxon lookup, decide whether a classifier
label belongs to a species by matching the label string against Taxon.name or
Taxon.search_names. Neither step ever follows Taxon.synonym_of to a canonical row, and
neither consults gbif_taxon_key or inat_taxon_id. A regional taxa list built from an
external source that happens to use a synonym, or a classifier label that does, will silently
fail to match a taxa-list entry recorded under the accepted name — even though both rows
describe the same species and Antenna already has a field meant to say so.

Where

  • AlgorithmCategoryMap.with_taxa() (ami/ml/models/algorithm.py:131-135) — resolves each
    classifier label to a Taxon via Q(name__in=...) | Q(search_names__overlap=...), then
    keys a dict by taxon.name. Whichever row matches is used as-is; synonym_of is never
    dereferenced.
  • get_or_create_taxon_for_classification() (ami/ml/models/pipeline.py:738-741) — the same
    lookup shape, independently implemented, with the same gap. Both sites carry an identical
    @TODO standardize the Taxon search / lookup comment pointing at each other; the
    duplication and the gap have been flagged twice without being addressed.
  • ClassMaskingTask (ami/ml/post_processing/class_masking.py) then compares the resolved
    Taxon object's identity against taxa_list.taxa.all() by row equality (effectively by
    primary key). If the two paths above resolve the same species to two different Taxon
    rows — one a synonym, one accepted — the comparison fails even though the species matches.

Why it matters now

Two things make this concrete rather than theoretical:

  • Taxon.synonym_of (ami/main/models.py:4394) is populated by import_taxa on import
    (import_taxa.py:433-435), so synonym relationships already exist in the data — they are
    just never read back during matching.
  • The regional taxa list work (Generate a project taxa list from a region, and track which species the models can predict #1367) builds lists from GBIF and iNaturalist, both of which
    routinely return currently-accepted names for a species whose classifier label may have
    been trained under an older synonym, or the reverse. A name-only join has no way to close
    that gap; a small number of the "in region but not in the classifier" cases that PR's
    Phase 0 spike measured are plausible candidates for exactly this failure, rather than
    genuine regional absence.

Proposed direction (to discuss)

  • A single shared resolver — replacing both call sites — that takes a label string and
    returns a canonical Taxon: match on name or search_names as today, then if the
    matched row has synonym_of set, follow it to the accepted taxon before returning.
  • Extend the same resolver to consult gbif_taxon_key / inat_taxon_id when a source
    supplies them (the regional-list pipeline already carries GBIF keys per candidate species),
    so an ID match can succeed even where the name text differs.
  • Class masking's list-membership check should then be a straightforward equality on the
    resolved canonical Taxon, which is already the intent of comparing Taxon objects by
    identity — the fix is upstream of masking, in how a label becomes a Taxon in the first
    place.

What we still need to verify

  • How much of the ~30% "region-attested but not in the classifier" gap measured for Vermont,
    and the equivalent gap for Newfoundland, is genuine absence versus a synonym or authorship
    mismatch. That audit is worth doing with this fix in hand, since it changes the denominator.
  • Whether search_names already contains enough synonym coverage in practice to make this a
    small-impact fix, or whether most of the value is in the synonym_of chain specifically.
    Not yet measured.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    post-processingPost-processing task framework and tasks: size filter, class masking, rank rollup, tracking

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions