WSD adds a decision only when one text span has competing concepts
WSD receives only the competing dictionary candidates attached to one exact text span, and it never removes them. It writes one preferred marker only when profiles are complete, the leading candidate has direct context evidence, its score passes the minimum, and its margin over the next candidate is wide enough. The controlled 858-note benchmark produced three wrapper decisions and one was wrong, so the marker remains an unevaluated extraction attribute rather than a clinical probability.
The ambiguity starts after dictionary lookup
Dictionary lookup, the step where cTAKES (Apache's clinical text-processing system) searches the local terminology database, can attach more than one CUI (a concept code from the UMLS, the Unified Medical Language System) 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 distinct candidates on one begin/end span form an ambiguity group, the unit WSD scores together.
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. An abstention writes nothing: every WSD field in the group stays blank while the candidate rows stay in place.
tools.wsd.GraphContextWsdAnnotator is wrapper-owned code, not the stock component from YTEX, the Apache cTAKES/YTEX module whose stock WSD ships with cTAKES. Its profiles are built deterministically from the pinned dictionary and selected UMLS terms, definitions, semantic types (the UMLS codes for what kind of meaning a concept is), 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.
The matched run kept the same candidates in every arm
The wrapper 2.1.1 controlled test used 858 notes and produced 57,939 concept rows and 3,755 medication rows in every arm: SQLite (the single-file database that holds the canonical result), XMI (the optional per-note XML dump of the complete cTAKES analysis), WSD-on, and WSD-off. The exact candidate projection hash matched across all arms. That is the control: the comparisons below changed the writer or WSD decision fields, not the underlying dictionary candidates.
| Path | Whose mechanism | Candidate-row behavior | USDHUB use |
|---|---|---|---|
| Apache/YTEX WSD as shipped | The separate Apache cTAKES/YTEX implementation. | It scores one cTAKES annotation object at a time. Separate objects can cover the same exported span, so an exact-span result can contain several winners. | Measured as a comparator, not loaded by the USDHUB recipe. |
| Apache/YTEX scorer after same-span grouping | The unmodified native scorer with candidate grouping normalized to the CSV span. | Unions candidates at one begin/end span before calling the native scorer. This separates the scorer from the annotation-object grouping problem. | Measured as a second comparator. |
| USDHUB graph/context WSD | Wrapper-owned deterministic scorer. | Retains every candidate. It writes one Y plus retained N alternatives only when all four gates pass; otherwise all WSD cells stay blank. | Default on for the star recipe (the wrapper's default ordered engine list); 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 (the recorded identity of the exact extraction definition). |
Population behavior on 2,437 ambiguous spans
| Exact-span method | One winner | Abstained | Invalid multiple winners |
|---|---|---|---|
--no-wsd | 0 | 2,437 | 0 |
| USDHUB wrapper WSD | 3 | 2,434 | 0 |
| Native YTEX as shipped | 348 | 0 | 2,089 |
| Native scorer after same-span grouping | 2,423 | 14 | 0 |
Native YTEX as shipped produced multiple winners on 2,089 spans because its processing unit is an IdentifiedAnnotation, cTAKES's Java object for one recognized mention, not the final exact-span row group. The normalized comparator kept the native scorer but grouped the candidates first. It made a decision on 2,423 spans and abstained on 14. The wrapper made only 3 decisions and abstained on 2,434. That is conservative by design: a group without strong evidence stays visibly undecided instead of carrying a weak winner.
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.
Release 2.1.19 keeps the default-on path self-contained. Normal bash scripts/setup.sh downloads the separate checksum-pinned CtakesWSD-runtime_usd-star-2026AA-r1.tgz release asset, verifies the archive and the model/receipt identities, checks the dictionary binding, and stages the approved pair. A normal USDHUB run then validates and uses that 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 (adjacent word pairs) 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 ID
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.
The decision path
One ambiguity group moves through nine ordered steps. Four of them are gates: complete profiles, direct evidence, a raw score of at least 0.08, and a margin of at least 0.35. Any gate that fails makes the group abstain.
- 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.
- Gate 1, complete profiles. Load the model profile for every candidate. Any missing profile makes the group abstain.
- Calculate the five evidence components and the weighted raw score.
- Gate 2, direct evidence. Require the leading candidate to have direct lexical, graph, or preferred-term evidence.
- Gate 3, raw score. Require the leading raw score to be at least
0.08. - Gate 4, margin. Normalize the group and require the leading candidate's margin over the runner-up to be at least
0.35. - When all four gates pass, write one
Y,Non the retained alternatives, and normalized scores that sum to one. - When any gate 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 build receipt is a small JSON file recording settings, counts, source identifiers, file hashes, the dictionary database hash, and source-release rows; it is term-free, so it carries no licensed term text. The model itself remains licensed terminology-derived data and is not committed to Git. Release 2.1.19 distributes the exact reviewed model separately from the source repository in the checksum-pinned WSD runtime asset, together with its receipt, distribution review, and third-party notices.
A default-on run fails before Java when the WSD assets do not match
Setup downloads and stages the release-matched WSD asset before the large cTAKES/dictionary bundle. At run time, the wrapper 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 (the JSON file naming the expected dictionary identity and hashes).
- Open the SQLite model read-only and run
quick_check, SQLite's own corruption scan. - 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, the exact engine list the Java process received, 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-wsdThe wrapper used less compute in the isolated same-XMI comparison
The wrapper validates the model once before Java, and Java reuses that proof only when the resolved path and complete SHA-256 match; the mechanism and the measured warm-trial startup medians are on the startup page.
Same frozen XMI, isolated WSD process
| Comparator | WSD call | External wall | Maximum RSS |
|---|---|---|---|
| USDHUB same-span wrapper | 0.573 s | 8.49 s | 803,956 KB |
| Native scorer after same-span grouping | 0.812 s | 18.58 s | 2,441,916 KB |
In the wrapper 2.1.1 controlled benchmark, both processes read the same 858 frozen XMI files with the same heap settings (the fixed Java memory allocation). The wrapper process used 54.31% less wall time (elapsed clock time) and 67.08% less maximum RSS (peak resident memory), and its WSD calls used 29.43% less time. These methods make very different numbers of decisions, so the compute result does not establish clinical superiority.
The blinded review found one real wrapper error
The wrapper 2.1.1 private review contained 60 method-stratified ambiguity cases from all five source groups. Reviewers saw bounded context and candidate terminology evidence, but not the method, score, filename, HubID, or NoteID. Three independent agent runs judged every case before the method key was opened, and all three agreed on all 60 cases.
| Method on the 60 reviewed cases | Correct decisions | Wrong decisions | Abstentions | Invalid multiple winners |
|---|---|---|---|---|
--no-wsd | 0 | 0 | 60 | 0 |
| USDHUB wrapper WSD | 2 | 1 | 57 | 0 |
| Native YTEX as shipped | 12 | 10 | 0 | 38 |
| Native scorer after same-span grouping | 21 | 25 | 14 | 0 |
The stool failure shows what the score can get wrong
For one stool span, the wrapper and normalized native scorer selected the medical-device concept C0183622. All three reviewers selected the feces concept C0015733. In the wrapper score, both candidates had zero graph evidence, equal semantic evidence, and the same prior. The device concept received more lexical overlap and the exact preferred-text feature, leaving its probability margin only 0.012 above the configured decision threshold.
That is a concrete failure mechanism: a terminology label can overlap the local words while still being the wrong clinical sense. The case was not used to retune the threshold or add a special rule.
The 60 cases deliberately included every rare wrapper decision and every normalized-native abstention. They do not represent the prevalence of all 2,437 spans. The reviewers were agents, not clinicians. A clinical claim needs a locked prevalence-weighted sample with independent clinician adjudication, measured decision coverage, selective accuracy, wrong-decision rate, safe abstention, missed decisions, and error analysis by semantic type.