From 32fe08a12424d36554c6dc101e35c78ff754d642 Mon Sep 17 00:00:00 2001 From: fei <204683769+feiiiiii5@users.noreply.github.com> Date: Sat, 19 Sep 2026 03:38:57 +0800 Subject: [PATCH] fix(analyzer): stop PhoneRecognizer leaking a match's region into later matches analyze() assigned region_code_for_number()'s result back to the region it was iterating, so an international number that parsed cleanly overwrote the region used to explain every later match in that pass. Hold it per match instead. --- CHANGELOG.md | 1 + .../generic/phone_recognizer.py | 14 +++++++------- presidio-analyzer/tests/test_phone_recognizer.py | 8 ++++++++ 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6d08119c7..12bfd9071f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ All notable changes to this project will be documented in this file. - The `kr` language code is deprecated in favor of the ISO 639-1 code `ko`. `KrRrnRecognizer` and `KrPassportRecognizer` still accept `kr` in `supported_languages` as a backward-compatibility alias for registries configured against their original class defaults; the alias will be removed in . `KrBrnRecognizer`, `KrDriverLicenseRecognizer` and `KrFrnRecognizer` have only ever defaulted to `ko`, so `kr` was removed from their `default_recognizers.yaml` entries in this release. (#2236) #### Fixed +- `PhoneRecognizer.analyze` assigned the region detected for a match back to the `region` variable it was iterating over, so an international-format number that parsed successfully overwrote the region used to explain every later match in that pass. With the default recognizer, `"My international number is +44 1234 567890, and my US one is (415) 555-0132"` labelled the US number `Recognized as GB region phone number`; swapping the two numbers hid it. Detected spans, scores and entity types were unaffected — only `analysis_explanation` named a region that never matched the number. The detected region is now held in a per-match `matched_region`, and the `NumberParseException` branch reports the region the match was actually found under. - `PhoneRecognizer.DEFAULT_SUPPORTED_REGIONS` used `"UK"`, which is not a valid `phonenumbers` (libphonenumber) region code — region codes are ISO 3166-1 alpha-2, where the United Kingdom is `"GB"`. The `"UK"` entry was a no-op, so UK numbers in national/local format (e.g. `020 7946 0958`) were never detected by default; only international-format `+44 …` numbers matched, because they carry the country code and match under any region. Replaced `"UK"` with `"GB"`. - Language model recognizers (`BasicLangExtractRecognizer`, `AzureOpenAILangExtractRecognizer`) configured in a recognizer registry YAML now honour `config_path` (and other recognizer-specific kwargs). Previously these entries were validated by the strict `PredefinedRecognizerConfig` schema, which has no `config_path` field and does not allow extra keys, so `config_path` was silently dropped and the recognizer fell back to its bundled default model configuration. Added a `LangExtractRecognizerConfig` model (`extra="allow"`) and registered both recognizer class names in `CONFIG_MODEL_MAP`. - `BasicLangExtractRecognizer` now honours values under `langextract.model.provider.language_model_params` (including `timeout` and `num_ctx`). Previously these were silently dropped because `langextract.extract()` ignores its `language_model_params` argument when a pre-built `ModelConfig` is passed via `config=`, causing Ollama-backed recognizers to fall back to langextract's 120s default regardless of the configured timeout. The recognizer now merges `language_model_params` into `ModelConfig.provider_kwargs`, which is the path that reaches the provider constructor. Explicit entries under `provider.kwargs:` still take precedence. Also fixed a `TypeError` when `kwargs:` or `language_model_params:` is `null` in the YAML. (#1943, Thanks @lsternlicht) diff --git a/presidio-analyzer/presidio_analyzer/predefined_recognizers/generic/phone_recognizer.py b/presidio-analyzer/presidio_analyzer/predefined_recognizers/generic/phone_recognizer.py index 1df82a8e0e..b6ae4be23d 100644 --- a/presidio-analyzer/presidio_analyzer/predefined_recognizers/generic/phone_recognizer.py +++ b/presidio-analyzer/presidio_analyzer/predefined_recognizers/generic/phone_recognizer.py @@ -70,14 +70,14 @@ def analyze( ): try: parsed_number = phonenumbers.parse(text[match.start : match.end]) - region = phonenumbers.region_code_for_number(parsed_number) - results += [ - self._get_recognizer_result(match, text, region, nlp_artifacts) - ] + matched_region = phonenumbers.region_code_for_number(parsed_number) except NumberParseException: - results += [ - self._get_recognizer_result(match, text, region, nlp_artifacts) - ] + matched_region = region + results += [ + self._get_recognizer_result( + match, text, matched_region, nlp_artifacts + ) + ] return EntityRecognizer.remove_duplicates(results) diff --git a/presidio-analyzer/tests/test_phone_recognizer.py b/presidio-analyzer/tests/test_phone_recognizer.py index fbbe3ffe6e..b74cb7a977 100644 --- a/presidio-analyzer/tests/test_phone_recognizer.py +++ b/presidio-analyzer/tests/test_phone_recognizer.py @@ -123,6 +123,14 @@ def test_when_phone_with_leniency_then_succeed( 2, ["PHONE_NUMBER", "PHONE_NUMBER"], ((16, 30), (60, 77),), 0.4, ['Recognized as US region phone number, using PhoneRecognizer','Recognized as FR region phone number, using PhoneRecognizer']), + # The international number comes first here: it is parsed successfully and + # used to be assigned back to the loop variable, so the national-format + # number that follows was explained with the previous match's region. + # Every case above puts the national number first, which hides that order. + ("My international number is +44 1234 567890, and my US one is (415) 555-0132", + 2, ["PHONE_NUMBER", "PHONE_NUMBER"], + ((27, 42), (61, 75),), 0.4, + ['Recognized as GB region phone number, using PhoneRecognizer','Recognized as US region phone number, using PhoneRecognizer']), # fmt: on ], )