Harmonization
After raw XML is fetched and parsed, four engines apply in-place transformations to specific columns of the output DataFrame. All engines deduplicate unique values before processing for performance.
Date Engine
Module: biometaharmonizer.date_engine
Class: DateEngine
The date engine converts any date string to ISO 8601 truncated representation and populates two output columns:
``collection_date`` — ISO 8601 point date (
YYYY,YYYY-MM, orYYYY-MM-DD). This field is alwaysNaNfor any range or approximate input — without exception.``collection_date_range`` — the verbatim original string, set only for range/approximate inputs;
NaNfor all point-date inputs.
Range detection runs before dateutil.parser to prevent silent
misparsing. For example, 2018-2020 would be misparsed by dateutil as
2018-01-20, so it is caught by _YEAR_ONLY_RANGE first.
Range pattern evaluation order (first match wins):
_INSDC_SLASH_RANGE— numeric INSDC slash:2004-07/2004-12_YEAR_ONLY_RANGE— year-only range where start ≠ end:2018-2020_NUMERIC_DASH_RANGE— numeric dash or “to” word:2021-01-15 - 2021-03-20_NAMED_MONTH_SAME_YEAR— named-month same year:July-December 2004_NAMED_MONTH_CROSS_YEAR— named-month cross-year:Oct 2020-Feb 2021_SEASON_RANGE— season strings:Spring 2019,Winter 2020-2021_APPROX_DATE— approximate prefixes:~2015,circa 2010,early March 2020,late 2019,mid-2018
Bare two-digit year strings (e.g. "95") are always rejected and produce
a warning log.
The main public method is parse_with_range(),
which returns a two-column DataFrame. The legacy
parse() method returns only
collection_date as a Series.
Date Parsing Examples
The following table shows representative inputs and their normalized outputs.
Input string |
collection_date |
collection_date_range |
|---|---|---|
|
|
NaN |
|
|
NaN |
|
|
NaN |
|
|
NaN |
|
|
NaN |
|
|
NaN |
|
NaN |
|
|
NaN |
|
|
NaN |
|
|
NaN |
|
|
NaN |
|
|
NaN |
|
|
NaN |
|
|
NaN |
|
|
NaN |
|
|
NaN |
|
|
NaN |
|
|
NaN |
|
|
NaN |
NaN |
|
NaN |
NaN |
|
NaN |
NaN |
|
NaN |
|
Geo Engine
Module: biometaharmonizer.geo_engine
Class: GeoEngine
The geo engine parses NCBI geo_loc_name strings into five structured output
columns. The expected input format is "Country: Region, Locality"; the
fallback format is "Country, Locality" (no colon).
Output columns populated:
geo_country— normalised country display name (e.g."United Kingdom")geo_region— sub-national region as submitted (e.g."England")geo_locality— locality or sub-region as submittedgeo_iso3166— ISO 3166-1 alpha-2 country code (e.g."GB"), or the string"HISTORICAL"for defunct countries, or NaN if not resolvablegeo_sea_ocean— ocean or sea name for marine samples (e.g."Pacific Ocean")
The public method is parse(),
which accepts a pandas.Series and returns a five-column
pandas.DataFrame.
Special handling rules:
UK sub-countries:
"England","Scotland","Wales","Northern Ireland"are all mapped to ISO code"GB"and display name"United Kingdom".Country aliases:
"Turkey"/"Türkiye"→"TR";"Namibia"→"NA";"DR Congo"/"DRC"/"Congo-Kinshasa"→"CD";"Burma"/"Myanmar (Burma)"→"MM";"Palestine"/"Gaza"/"West Bank"→"PS".Historical countries:
"USSR","Soviet Union","Yugoslavia","Czechoslovakia","German Democratic Republic","Zaire", and others are taggedgeo_iso3166 = "HISTORICAL"and a WARNING is logged.Coordinate-only entries: values matching the coordinate pattern are not parsed into geo columns and return all-NaN geo outputs.
Parenthetical qualifiers in country names: trailing parenthetical suffixes such as
"United Kingdom (England, Wales & N. Ireland)"are stripped before country lookup so the comma inside the parentheses does not break parsing.Parenthetical qualifiers in water body names: ocean and sea names that include a regional qualifier in parentheses (e.g.
"Pacific Ocean (NE)") have the parenthetical portion stripped before the water-body lookup, so the matched value stored ingeo_sea_oceanis the canonical name without the qualifier (e.g."Pacific Ocean").Ocean/sea lookup: when the country token (after stripping parenthetical qualifiers) matches one of the named ocean/sea entries, the value is stored in
geo_sea_oceaninstead ofgeo_country.Bare “Korea” ambiguity: the string
"Korea"without a North/South qualifier is resolved to South Korea (geo_iso3166 = "KR") and an INFO-level log is emitted to flag the ambiguity:INFO biometaharmonizer.geo_engine: 'Korea' resolved to South Korea (KR) — verify if North Korea was intended.
Geo Parsing Examples
Input string |
geo_country |
geo_region |
geo_locality |
geo_iso3166 |
|---|---|---|---|---|
|
Russia |
Novosibirsk |
Akademgorodok |
RU |
|
USA |
California |
San Diego |
US |
|
United Kingdom |
NaN |
NaN |
GB |
|
United Kingdom |
Yorkshire |
NaN |
GB |
|
Germany |
Bavaria |
NaN |
DE |
|
NaN |
NaN |
NaN |
NaN |
|
NaN |
NaN |
NaN |
NaN |
|
USSR |
NaN |
NaN |
HISTORICAL |
|
Turkey |
Istanbul |
NaN |
TR |
|
NaN |
NaN |
NaN |
NaN |
|
China |
NaN |
Shanghai |
CN |
One Health Classifier
Module: biometaharmonizer.one_health
Class: OneHealthClassifier
The One Health classifier assigns each record to a standardized category
using deterministic, multi-layer semantic analysis. All biological knowledge
is loaded from one_health_dictionaries.json; no terms are hardcoded in the
Python source.
Constructor Parameters
OneHealthClassifier(dictionary_path=None, fuzzy_threshold=92)
``dictionary_path`` (default
None) — path to a customone_health_dictionaries.jsonfile. WhenNone, the bundled file atsrc/biometaharmonizer/schemas/one_health_dictionaries.jsonis used. RaisesFileNotFoundErrorif a non-Nonepath does not exist, andValueErrorif the file is missing required top-level keys.``fuzzy_threshold`` (default
92) — minimumrapidfuzz.fuzz.WRatioscore (0–100) for a fuzzy match to be accepted. Increase for stricter matching (fewer false positives); decrease to accept more approximate matches. Only applies whenrapidfuzzis installed.
rapidfuzz Optional Dependency
Fuzzy matching is powered by the rapidfuzz library (>=3.0.0). This
dependency is optional at runtime: if rapidfuzz is not importable,
the classifier logs a WARNING and disables the fuzzy fallback layer
gracefully — all other classification layers (tier1 patterns, host
dictionary, synonym map, setting inference) remain active:
WARNING biometaharmonizer.one_health:
rapidfuzz not installed; fuzzy fallback disabled.
pip install rapidfuzz>=3.0.0
Records that would have been classified by fuzzy matching alone receive
one_health_category = "Unclassified" when rapidfuzz is absent.
Text Preprocessing Pipeline
Before any category lookup the classifier runs the following preprocessing steps on every value, in order:
Null check — values matching
NULL_PATTERNSreturnUnclassifiedimmediately.Underscore normalization — underscores are replaced with spaces (e.g.
"Environmental_bathroom"→"Environmental bathroom").Institution/culture-collection stripping — values that contain institution keywords (
university,laboratory,hospital, etc.) or match patterns frominstitution_patterns/culture_collection_prefixesin the dictionary have those tokens removed. If nothing remains after stripping,Unclassifiedis returned.“Animal origin” pattern —
"a <specimen> of <animal> origin"patterns are resolved directly viahost_to_categorybefore abbreviation expansion.Abbreviation expansion (
abbreviation_map) — tokens matching entries in theabbreviation_mapdictionary section are expanded to their canonical forms (e.g."CSF"→"cerebrospinal fluid","GIT"→"gastrointestinal tract").Synonym normalization (
synonym_map) — multi-word phrases matching entries in thesynonym_mapdictionary section are replaced with a canonical form (e.g."clinical isolate"→"clinical","gut flora"→"intestinal microbiota") using longest-match-first ordering.Processing term extraction — terms in
processing_terms(e.g."frozen","lyophilized") are detected and stored inone_health_processing; the matched token is removed from the working string so it does not interfere with category matching.Setting term extraction — terms in
setting_patterns(e.g."hospital","clinical") are detected and stored inone_health_setting; the matched token is removed from the working string.
Classification Layers
After preprocessing, the working string passes through classification in this priority order:
Tier1 pattern matching — compiled regex patterns from
tier1_patternsin the dictionary, applied in the order defined bytier1_order. Returns the first matching category.Fuzzy matching (
rapidfuzz) — if no tier1 pattern matched andrapidfuzzis available,WRatiosimilarity is computed against the fullontology_mapcorpus (filtered to remove ambiguous terms). The best match abovefuzzy_thresholdis returned.Unclassified — if neither layer produces a result.
Valid output categories for one_health_category:
Human— isolates from human clinical specimens or hostsAnimal— domestic and companion animals, livestock, veterinary samplesPlant— plant material, rhizosphere, phytopathological samplesFood— food products, ingredients, food-processing environmentsEnvironmental— soil, sediment, air, water, biofilms not otherwise classifiedUnclassified— no category could be determined with sufficient confidence
The one_health_category column is always a string; it is never NaN.
Unclassifiable records receive the string "Unclassified".
Multi-field Evidence Integration (classify_multi_field)
classify_multi_field()
runs a two-pass evidence integration over up to six input fields:
isolation_source, host, env_medium, env_local_scale,
env_broad_scale, sample_type.
Field weights (used in the confidence formula):
Field |
Weight |
|---|---|
|
1.00 |
|
1.00 |
|
0.85 |
|
0.80 |
|
0.70 |
|
0.50 |
A corroboration bonus of 0.10 is added when two or more fields agree on the same category.
Setting-inference fallback: If no field produces any positive category
signal but a setting term was detected during preprocessing (e.g. the value
contained "hospital" or "clinical"), the setting is mapped to a
category via setting_to_category and setting_confidence from the
dictionary JSON. In this case one_health_source_field is set to the
special value "setting_inference" to indicate that the category came
from setting context r