From 3039114e5f7f2e7b3574e4e950ad944eb1aa6545 Mon Sep 17 00:00:00 2001 From: Vamsi Viswanath Bethamsetty Date: Sat, 31 Oct 2026 00:01:32 +0530 Subject: [PATCH] Keep street address matches on one line --- RELEASE_NOTES.md | 1 + phileas/filters/street_address_filter.py | 5 ++++- tests/test_street_address_detection.py | 23 +++++++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index bcfbeda..de5d2b2 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -4,6 +4,7 @@ Notable changes to the `phileas-redact` package, most recent first. ## Unreleased +* The `streetAddress` filter no longer matches across a line break. The parts of an address were joined by `\s`, which includes line breaks, so a number at the end of one line and a word and a street type on the next were read as an address: in `Cedar Falls, IA 50613`, a blank line, and `Dear Dr. Rivera,`, the text from `50613` to `Dr` was detected, with `Dr` read as Drive. The parts are now joined by spaces and tabs, as in Phileas (Java), so an address on one line and the street line of a multi-line address block are found as before. Parts separated by other whitespace, such as a no-break space, no longer form an address. This is the parity port of the Phileas (Java) fix for philterd/phileas#474. * The `ssn` filter no longer detects the `NN-NNNNNNN` form. That shape is an Employer Identification Number, not a Social Security Number, and is already covered by the `ein` filter. A policy that enables only `ssn` therefore no longer detects values such as `12-3456789`; enable `ein` to detect them. SSN forms (`NNN-NN-NNNN`, `NNN NN NNNN`, and nine digits with no hyphen) are unchanged. ## Version 1.1.0 diff --git a/phileas/filters/street_address_filter.py b/phileas/filters/street_address_filter.py index d4c9633..352989a 100644 --- a/phileas/filters/street_address_filter.py +++ b/phileas/filters/street_address_filter.py @@ -21,9 +21,12 @@ from .base import BaseFilter, FilterType +# The parts are joined by spaces and tabs only, as in the Java filter, so a match does not +# run from one line into the next (a ZIP code at the end of a line and "Dear Dr." on the +# next are not an address). _PATTERNS = [ re.compile( - r"\b\d{1,5}\s+(?:[A-Za-z0-9#.,\-]+\s+){1,4}" + r"\b\d{1,5}[ \t]+(?:[A-Za-z0-9#.,\-]+[ \t]+){1,4}" r"(?:Street|St|Avenue|Ave|Boulevard|Blvd|Road|Rd|Lane|Ln|Drive|Dr|Court|Ct|Place|Pl|Way|Circle|Cir|Terrace|Ter|Trail|Trl)\b", re.IGNORECASE, ), diff --git a/tests/test_street_address_detection.py b/tests/test_street_address_detection.py index 757dc64..1f8041d 100644 --- a/tests/test_street_address_detection.py +++ b/tests/test_street_address_detection.py @@ -116,6 +116,26 @@ def test_long_street_name_within_word_limit(street_filter): assert spans[0].text == addr +def test_no_match_across_line_breaks(street_filter): + # A ZIP code at the end of a line and "Dear Dr." on the next line are not an address. + text = "Address: 214 Juniper Court, Cedar Falls, IA 50613\n\nDear Dr. Rivera," + spans = street_filter.detect(text) + assert [s.text for s in spans] == ["214 Juniper Court"] + + +@pytest.mark.parametrize( + "text,expected", + [ + ("214 Juniper Court\nCedar Falls, IA 50613", "214 Juniper Court"), + ("123 Main St Apt 4B\r\nSpringfield, IL 62704", "123 Main St"), + ("Jane Doe\n1 Martin Luther King Jr Blvd\nAtlanta, GA 30303", "1 Martin Luther King Jr Blvd"), + ], +) +def test_street_line_of_address_block(street_filter, text, expected): + spans = street_filter.detect(text) + assert [s.text for s in spans] == [expected] + + def test_default_context_returns_results(street_filter): spans = street_filter.detect("123 Main Street", context="default") assert len(spans) == 1 @@ -146,6 +166,9 @@ def test_custom_context_still_detects(street_filter): "123 Main Streets", # trailing 's' breaks the suffix word boundary "123 Main Streetss", "123456 Main Street", # number has six digits (exceeds \\d{1,5}) + "Cedar Falls, IA 50613\nDear Dr. Rivera,", # parts on different lines + "Cedar Falls, IA 50613\r\nDear Dr. Rivera,", + "Unit 12\nMain Street", ], ) def test_no_detection(street_filter, text):