You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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.nameorTaxon.search_names. Neither step ever followsTaxon.synonym_ofto a canonical row, andneither consults
gbif_taxon_keyorinat_taxon_id. A regional taxa list built from anexternal 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 eachclassifier label to a
TaxonviaQ(name__in=...) | Q(search_names__overlap=...), thenkeys a dict by
taxon.name. Whichever row matches is used as-is;synonym_ofis neverdereferenced.
get_or_create_taxon_for_classification()(ami/ml/models/pipeline.py:738-741) — the samelookup shape, independently implemented, with the same gap. Both sites carry an identical
@TODO standardize the Taxon search / lookupcomment pointing at each other; theduplication and the gap have been flagged twice without being addressed.
ClassMaskingTask(ami/ml/post_processing/class_masking.py) then compares the resolvedTaxonobject's identity againsttaxa_list.taxa.all()by row equality (effectively byprimary key). If the two paths above resolve the same species to two different
Taxonrows — 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 byimport_taxaon import(
import_taxa.py:433-435), so synonym relationships already exist in the data — they arejust never read back during matching.
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)
returns a canonical
Taxon: match onnameorsearch_namesas today, then if thematched row has
synonym_ofset, follow it to the accepted taxon before returning.gbif_taxon_key/inat_taxon_idwhen a sourcesupplies them (the regional-list pipeline already carries GBIF keys per candidate species),
so an ID match can succeed even where the name text differs.
resolved canonical
Taxon, which is already the intent of comparingTaxonobjects byidentity — the fix is upstream of masking, in how a label becomes a
Taxonin the firstplace.
What we still need to verify
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.
search_namesalready contains enough synonym coverage in practice to make this asmall-impact fix, or whether most of the value is in the
synonym_ofchain specifically.Not yet measured.
Related
with_taxa())"known but deferred" shape