Skip to content

fix(text): idempotent Unicode cleaning, fewer false encoding alerts, international phones and punycode emails - #370

Merged
kevincostner17 merged 1 commit into
mainfrom
fix/text-quality
Sep 15, 2026
Merged

kevincostner17 merged 1 commit into
mainfrom
fix/text-quality

Conversation

@kevincostner17

Copy link
Copy Markdown
Contributor

Summary

  • clean_text_value is now idempotent and in the configured Unicode form (clean_text_value output is not NFC or idempotent when a zero-width char precedes an accent #316). Normalization ran before zero-width/control-character removal, so "Jose" + U+200B + U+0301 came out decomposed and a second pass changed it. The configured normalization now re-runs after the removal and custom steps (before whitespace collapse, strip and truncate); the transform name is recorded once.
  • Fewer false lint_text_encoding alerts (lint_text_encoding flags ordinary Japanese, Spanish and Portuguese text as high severity #317). Modifier letters (category Lm, e.g. ー, 々) and the ordinal indicators ª/º no longer count as their own script, and KATAKANA-HIRAGANA/IDEOGRAPHIC characters join the Japanese family, so コーヒー, 人々 and Nº 5 are no longer mixed_script. The à + ASCII-space mojibake pattern was dropped because it matched uppercase Portuguese (MANHà DE SOL); the NBSP form is still detected.
  • International phones and punycode emails in validate_fields (validate_fields rejects valid '+49 (0) 30 ...' phone numbers and punycode-TLD emails #318). Phone validation now allows up to 30 formatting characters and checks for 7-15 digits (E.164), so +49 (0) 30 12345678 passes. The same rule applies in detect_value_type, the per-value check and the vectorised suspect scan. Email TLDs may be xn-- punycode.

Behaviour changes

  • Phones with more than 15 digits are now rejected (previously only a 17-character shape limit applied).
  • Ã followed by a plain space is no longer reported as mojibake.
  • Follow-up, not in this PR: semantic/semantic_types.py keeps its own phone/email patterns for type inference.

Tests

New tests/test_text_quality_fixes.py:

  • repro for each issue;
  • idempotence and normal-form checks over tricky inputs (zero-width chars, BOM, bidi marks or control chars before combining marks, NBSP, fullwidth letters) for NFC/NFD/NFKC/NFKD;
  • guards that real mojibake (école, don’t, São Paulo, the NBSP form) and real mixed script (Cyrillic/Greek in Latin text) are still detected;
  • valid and invalid phone numbers (too few digits, 16 digits, letters, too long) and emails (a@b.c, a@b.xn--, x@y), checked through both the scalar and the vectorised path.

Verification

  • ruff check .: clean
  • mypy src/freshdata: clean (202 files)
  • pytest -m "not online and not large" on Python 3.12: 4379 passed, 6 skipped
  • pytest -m "not online and not large" on Python 3.9: 4375 passed, 10 skipped
  • 50,000 random inputs: clean(clean(x)) == clean(x) with default settings

Closes #316
Closes #317
Closes #318

…rnational phone and punycode email validation

clean_text_value (#316): Unicode normalization ran first, before the
control-character and zero-width removal steps. Removing a ZWSP between a
base letter and a combining mark ("Jose" + U+200B + U+0301) left a
decomposed sequence, so the output was not in the configured form and a
second pass changed it again. The configured normalization now re-runs
after all removal and custom steps (before whitespace collapse, strip and
truncation). The transform name is recorded once.

lint_text_encoding (#317): _script_of used the first word of the character
name as the script, so modifier letters such as the katakana-hiragana
prolonged sound mark (U+30FC) and the ideographic iteration mark (U+3005),
and the ordinal indicators U+00AA / U+00BA, became their own "script" and
ordinary Japanese and Spanish text was flagged as high-severity
mixed_script. Modifier letters (category Lm) and the ordinal indicators
are now script-neutral, and KATAKANA-HIRAGANA / IDEOGRAPHIC name heads
join the CJK_JP family. The "Ã" + ASCII space alternative is removed from
_MOJIBAKE_RE because it matched uppercase Portuguese ("MANHÃ DE SOL"); the
NBSP form is still caught by the existing Ã[\x80-\xbf] range.

validate_fields (#318): the phone regex capped the formatted string at 17
characters, rejecting "+49 (0) 30 12345678". The shape regex now allows up
to 30 formatting characters and the length check is on the digit count
(7-15, per E.164), applied the same way in detect_value_type, the per-value
check and the vectorised suspect scan. The email TLD now also accepts
punycode internationalized TLDs (xn--...).

Closes #316
Closes #317
Closes #318
@coderabbitai

coderabbitai Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 9b79c27d-3366-4939-a3eb-42cdbbc88c0d


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

FreshData benchmark report — performance

  • freshdata: ?
  • python: ?
  • platform: ?
fixture n_rows n_cols p50 s p95 s peak MB repair % false-repair % preserve % trust monotonic export %

Authored-code reduction (Metric 6)

@kevincostner17
kevincostner17 merged commit a89a2c3 into main Sep 15, 2026
21 checks passed
kevincostner17 added a commit that referenced this pull request Sep 15, 2026
Add Unreleased Added, Changed and Fixed entries for merged PRs #351,
#352, #353, #354, #356, #357, #358, #360, #361, #362, #363, #364, #365,
#367, #368, #369, #370, #371, #372, #374, #375, #377, #378 and #379.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant