Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions packages/flow/openadapt_flow/runtime/identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,8 +364,31 @@


def squash(text: str) -> str:
"""Lowercase and remove ALL whitespace (OCR-tolerant comparison form)."""
return "".join(text.lower().split())
"""Lowercase and drop whitespace and OCR-volatile name punctuation.

Comma and period on letter tokens are stripped because thin identity-band
crops routinely make RapidOCR read ``Smith,`` as ``Smith.`` (and the
reverse). Leaving them in the comparison form turned a glyph-collapse
ABSTAIN into a false affirmative MISMATCH on native Windows while Mac
stayed consistent.

Periods that separate digits (``01.01.1900``) are kept: collapsing them
turns a date into a digit run that the glyph-vulnerable-identifier gate
then mistreats as an MRN-like token and spuriously abstains.
"""
lowered = text.lower()
out: list[str] = []
for i, ch in enumerate(lowered):
if ch.isspace():
continue
if ch in ",.":
prev = lowered[i - 1] if i else ""
nxt = lowered[i + 1] if i + 1 < len(lowered) else ""
if prev.isdigit() and nxt.isdigit():
out.append(ch) # date / decimal separator
continue
out.append(ch)
return "".join(out)


def band_region(point: Point, band_height: int, viewport: tuple[int, int]) -> Region:
Expand Down
30 changes: 30 additions & 0 deletions packages/flow/tests/test_identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,23 @@ def test_tokenize(self):
assert tokenize(" Jane Li \n panel ") == ["jane", "li", "panel"]
assert tokenize("") == []

def test_comma_period_name_punctuation_is_ocr_noise(self):
# Recorded thin-band context often stores "Smith." while the live
# band (or another engine) returns "Smith," — same patient row.
match = band_match(
"100512 Smith. John 01/15/1980 Active",
"100512 Smith, John 01/15/1980 Active",
)
assert match.coverage == 1.0
assert match.contradicted_chars == 0
assert match.unexplained_name_tokens == 0
# A real near-name sibling must still mismatch.
bad = band_match(
"100512 Smith. John 01/15/1980 Active",
"100512 Smith, Joan 01/15/1980 Active",
)
assert bad.contradicted_chars > 0 or bad.unexplained_name_tokens > 0


class TestOperatingPoint:
"""Pin the ROC-chosen decision parameters and their boundaries
Expand Down Expand Up @@ -456,6 +473,19 @@ def test_coverage_ignores_scattered_short_blocks(self):
assert coverage("abcdef", "abcdef") == 1.0
assert coverage("", "x") == 0.0

def test_squash_drops_comma_period(self):
# Thin identity-band crops make RapidOCR read "Smith," as "Smith."
# (Windows native); both must compare equal so glyph abstain is not
# overwritten by a false punctuation mismatch.
assert squash("Smith,") == squash("Smith.")
assert squash("Smith, John") == "smithjohn"
assert squash("Jr.") == "jr"
# Date / digit separators must survive: collapsing "01.01.1900" into
# "01011900" makes the glyph-vulnerable-identifier gate spuriously
# abstain on a clean MRN + period-separated DOB pair (CI fuzz).
assert squash("01.01.1900") == "01.01.1900"
assert squash("01/01/1900") == "01/01/1900"

def test_band_region_clamps_to_viewport(self):
assert band_region((500, 10), 64, (1280, 800)) == (0, 0, 1280, 64)
assert band_region((500, 795), 64, (1280, 800)) == (0, 736, 1280, 64)
Expand Down
Loading