WSD adds a decision only when one text span has competing concepts
The wrapper’s WSD keeps every candidate. It can mark one candidate as the preferred sense, or leave the group undecided when profiles are missing, direct evidence is absent, the score is weak, or the candidates are too close.
The ambiguity starts after dictionary lookup
Dictionary lookup can attach more than one CUI to the same source characters. That can happen because a word has multiple meanings, the terminology contains related candidates, or separate cTAKES annotation objects cover the same span.
The USDHUB graph/context annotator leaves candidate count and order intact. It owns only the WSD marker and within-group score fields, so the original dictionary candidates remain reviewable whether it decides or abstains.
tools.wsd.GraphContextWsdAnnotator is wrapper-owned code. It is not the stock Apache cTAKES/YTEX WSD component. Its profiles are built deterministically from the pinned dictionary and selected UMLS terms, definitions, semantic types, and bounded relations. It is not a neural model, does not learn from patient notes, and does not update itself during a run.
Read the implementation: tools/wsd/GraphContextWsdAnnotator.java.
WSD changes the markers, not the candidate rows
| Path | Whose mechanism | Candidate-row behavior | USDHUB use |
|---|---|---|---|
| Apache/YTEX WSD | The separate Apache cTAKES/YTEX implementation. | It has its own resources and output contract; this wrapper does not present it as the mechanism behind the USDHUB WSD columns. | Not loaded by the reviewed production recipe. |
| USDHUB graph/context WSD | Wrapper-owned deterministic scorer. | Retains every candidate. It writes one Y plus retained N alternatives only when profiles, direct evidence, score, and margin all pass; otherwise all WSD cells stay blank. | Default on for the production star recipe; clinical status remains not-evaluated. |
--no-wsd | No disambiguation annotator. | Retains every candidate and leaves every WSD marker and score blank. | Explicit recorded comparison, with a different analysis_id. |
What changes inside one retained ambiguity group
| State | Candidate A | Candidate B | Row count |
|---|---|---|---|
WSD disabled with --no-wsd | CUI retained; WSD fields blank | CUI retained; WSD fields blank | 2 |
| WSD enabled, evidence passes | CUI retained; one candidate gets Y and score | CUI retained; other candidate gets N and score | 2 |
| WSD enabled, system abstains | CUI retained; WSD fields blank | CUI retained; WSD fields blank | 2 |
Synthetic span: “mass” with context “near the right kidney”
Switch between a decision and an abstention
Both rows remain. Complete profiles, direct evidence for the leading candidate, score, and margin allow Y and N to be written.
The synthetic body-structure candidate treats “mass” as a physical structure, while the quantitative candidate treats it as an amount or measurement. Those deliberately different senses make the scoring path visible; the phrase, candidate labels, codes, and numbers are teaching data, not a clinical result.
The default-on change makes this comparison explicit. A normal USDHUB run validates and uses the active WSD model. --no-wsd remains available for a controlled comparison and creates a different analysis_id.
The scorer combines five recorded evidence sources
For one ambiguity group, the annotator reads a bounded window around the source span, normalizes the words, and compares them with each candidate profile. The same group is scored together, which means the displayed values are relative to the competing candidates for that mention.
| Evidence | Weight | What it compares |
|---|---|---|
| Lexical context | 0.45 | Words and bigrams near the mention against the candidate’s ranked terminology profile. |
| Graph context | 0.35 | Other nearby concept candidates against a bounded UMLS relation neighborhood for the candidate. |
| Semantic type | 0.08 | The candidate’s UMLS semantic types against contextual concept/type evidence. |
| Preferred-term evidence | 0.07 | The local mention/context against the candidate’s preferred terminology label. |
| Prior | 0.05 | A bounded frequency/ranking prior from the built profile, used as the smallest component. |
- Sentence window
- 2
- Character ceiling
- 1,200
- Temperature
- 0.15
- Minimum raw score
- 0.08
- Minimum probability margin
- 0.35
- Profile status
usdhub-wsd-v1
The temperature converts raw candidate scores into the displayed within-mention proportions. It does not calibrate the value against patient truth, diagnosis accuracy, or a clinician gold label.
Read the deterministic model builder: scripts/build_wsd_model.py. The dictionary page shows which terms can enter these profiles.
Four requirements must pass before the wrapper writes a decision
- Group all distinct CUI candidates on the same begin/end span, even when cTAKES created them on separate annotation objects.
- Clear any WSD-owned marker/score state from an earlier pass without changing candidate order.
- Load the model profile for every candidate. Any missing profile makes the group abstain.
- Calculate the five evidence components and weighted raw score.
- Require the leading candidate to have direct lexical, graph, or preferred-term evidence.
- Require the leading raw score to be at least
0.08. - Normalize the group and require the leading candidate’s margin over the runner-up to be at least
0.35. - When all four requirements pass, write one
Y,Non the retained alternatives, and normalized scores that sum to one. - When any requirement fails, leave every WSD field blank.
| Sanity invariant | Failure meaning |
|---|---|
| A singleton mention has blank WSD fields. | There was no ambiguity group to resolve. |
| An abstention has blank flags and blank scores. | The wrapper must not leave partial evidence looking like a decision. |
A decision has exactly one Y. | More or fewer winners makes the group internally invalid. |
All decision flags are Y or N. | No third state is serialized as a decision. |
| Scores are finite, within 0–1, and sum to one. | The normalized group output is malformed. |
The model is built for one exact dictionary database
The model builder starts from a deterministic WSD seed exported from the validated cTAKES dictionary. It then scans the selected UMLS 2026AA sources only for CUIs that can actually appear in that dictionary.
| Model content | Accepted build count |
|---|---|
| Candidate profiles | 293,770 |
| Dictionary terms | 468,335 |
| Ranked UMLS terms | 832,876 |
| Definitions | 11,631 |
| Directed relation edges | 73,238 |
| Ambiguous CUIs represented | 2,347 |
The term-free build receipt records settings, counts, source identifiers, file hashes, dictionary database hash, and source-release rows. The model itself remains licensed terminology-derived data and is not committed to the public repository.
A default-on run fails before Java when the WSD assets do not match
The runtime resolves an explicit CLI model first, then environment configuration, then the active dictionary release’s controlled model path. It does not silently fall back to WSD off.
- Hash the complete SQLite model.
- Read the term-free build receipt and compare its recorded model hash.
- Compare dictionary release, UMLS release, dictionary ID, and dictionary database hash with the selected strict dictionary manifest.
- Open the SQLite model read-only and run
quick_check. - Require the expected schema, completed-build marker, and non-empty profile table.
- Write
wsd_validation.jsonand bind its hash into the run manifest and SQLite metadata.
The generated effective piper records the WSD profile ID, context limits, thresholds, temperature, and weights. That means a setting change alters the normalized pipeline hash and therefore the analysis_id.
Read the gate: scripts/validate_wsd_model.py validates the model before Java, and scripts/check_run.py checks the completed WSD rows.
Run an explicit WSD-off comparison
bash scripts/run_pipeline.sh \
--input /approved/usdhub/batch \
--output /approved/usdhub/results-wsd-off \
--no-wsdStartup reuses a completed model validation without weakening the Java check
Before Java starts, the wrapper hashes the complete WSD model, matches its build receipt and dictionary binding, opens SQLite, and runs quick_check. It passes the validated real path and SHA-256 into Java so the annotator does not repeat the same full database scan.
| Stage | Current requirement |
|---|---|
| Wrapper preflight | Full model hash, build receipt, dictionary binding, SQLite quick_check, schema, completion marker, and profile count. |
| Java reuse condition | The wrapper-validation flag, resolved model path, and complete SHA-256 must all match. |
| Java checks that still run | Expected schema, completed-build marker, and a non-empty profile table. |
| Standalone or mismatched use | The annotator performs the full validation path instead of trusting a partial signal. |
This removes duplicate startup work only when the wrapper and Java agree on the exact model bytes. It does not turn off model validation.
See where WSD validation sits inside the complete startup lifecycle.
Default-on engineering behavior and clinical validation remain separate
The current wrapper 2.1 direct fixture contained 13 ambiguous mentions. The model abstained on all 13, so that execution did not force a winner when the configured evidence gates were not satisfied. The separate synthetic fixture produced one winner row, one loser row, and six blank singleton rows, which checks the decision path without treating a synthetic phrase as clinical validation.
A clinical review needs an ambiguity gold set sampled from the intended notes, candidate-level adjudication, accuracy/coverage for decisions, abstention rate, error analysis by semantic type and source vocabulary, and a locked evaluation set that was not used to tune thresholds.