From fa9b3429c7724d17bb007c156d31914e63d2e465 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Fri, 7 Aug 2026 16:57:05 +0800 Subject: [PATCH] feat: add pinyin initial abbr helper for CRM names. Co-authored-by: Cursor --- backend/app/utils/pinyin_abbr.py | 58 ++++++++++++++++++++++++ backend/requirements.txt | 1 + backend/tests/test_pinyin_abbr.py | 74 +++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 backend/app/utils/pinyin_abbr.py create mode 100644 backend/tests/test_pinyin_abbr.py diff --git a/backend/app/utils/pinyin_abbr.py b/backend/app/utils/pinyin_abbr.py new file mode 100644 index 000000000..d54ab9401 --- /dev/null +++ b/backend/app/utils/pinyin_abbr.py @@ -0,0 +1,58 @@ +"""名称拼音首字母缩写:库内存小写,检索时大小写不敏感。""" +from __future__ import annotations + +import re +from typing import Optional + +from pypinyin import Style, lazy_pinyin + +_MAX_LEN = 100 +_ALNUM = re.compile(r"[a-zA-Z0-9]") + + +def normalize_name_pinyin_abbr(value: Optional[str]) -> str: + raw = (value or "").strip().lower() + if not raw: + return "" + return raw[:_MAX_LEN] + + +def build_name_pinyin_abbr(name: Optional[str]) -> str: + text = (name or "").strip() + if not text: + return "" + parts: list[str] = [] + for ch in text: + if "\u4e00" <= ch <= "\u9fff": + pys = lazy_pinyin(ch, style=Style.FIRST_LETTER) + if pys and pys[0]: + parts.append(str(pys[0]).lower()[0]) + elif _ALNUM.match(ch): + parts.append(ch.lower()) + # else: drop punctuation / spaces / % / brackets + return "".join(parts)[:_MAX_LEN] + + +def resolve_name_pinyin_abbr( + *, + name: Optional[str], + submitted: Optional[str] = None, + existing: Optional[str] = None, + submitted_provided: bool = False, +) -> Optional[str]: + """保存时解析拼音字段。 + + - submitted_provided=True:用提交值(空则按 name 生成) + - 否则:已有非空 existing 则保留;existing 空则按 name 生成 + """ + if submitted_provided: + normalized = normalize_name_pinyin_abbr(submitted) + if normalized: + return normalized + generated = build_name_pinyin_abbr(name) + return generated or None + existing_norm = normalize_name_pinyin_abbr(existing) + if existing_norm: + return existing_norm + generated = build_name_pinyin_abbr(name) + return generated or None diff --git a/backend/requirements.txt b/backend/requirements.txt index 9a30635ef..9973c920c 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -18,3 +18,4 @@ Pillow>=10.0.0 requests>=2.31.0 openpyxl>=3.1.0 alibabacloud_ocr_api20210707>=3.1.3 +pypinyin>=0.53.0 diff --git a/backend/tests/test_pinyin_abbr.py b/backend/tests/test_pinyin_abbr.py new file mode 100644 index 000000000..4d18c5e63 --- /dev/null +++ b/backend/tests/test_pinyin_abbr.py @@ -0,0 +1,74 @@ +from app.utils.pinyin_abbr import ( + build_name_pinyin_abbr, + normalize_name_pinyin_abbr, + resolve_name_pinyin_abbr, +) + + +def test_empty_and_whitespace(): + assert build_name_pinyin_abbr("") == "" + assert build_name_pinyin_abbr(" ") == "" + assert normalize_name_pinyin_abbr(None) == "" + assert normalize_name_pinyin_abbr(" AbC ") == "abc" + + +def test_chinese_and_alnum(): + # pypinyin FIRST_LETTER: 工g业y硫l酸s(非设计稿中的 gylls) + assert build_name_pinyin_abbr("55% 工业硫酸") == "55gyls" + # pypinyin: 苯b骈p三s氮d唑z + BTA(非设计稿中的 bstnzbta) + assert build_name_pinyin_abbr("苯骈三氮唑BTA") == "bpsdzbta" + assert build_name_pinyin_abbr("PAC") == "pac" + + +def test_truncate_to_100(): + long_name = "测" * 120 + out = build_name_pinyin_abbr(long_name) + assert len(out) == 100 + + +def test_resolve_submitted_provided_nonempty(): + assert ( + resolve_name_pinyin_abbr( + name="工业硫酸", + submitted=" CustomAbbr ", + existing="old", + submitted_provided=True, + ) + == "customabbr" + ) + + +def test_resolve_submitted_provided_empty_generates(): + assert ( + resolve_name_pinyin_abbr( + name="PAC", + submitted=" ", + existing="old", + submitted_provided=True, + ) + == "pac" + ) + + +def test_resolve_not_submitted_keeps_existing(): + assert ( + resolve_name_pinyin_abbr( + name="工业硫酸", + submitted=None, + existing=" KeepMe ", + submitted_provided=False, + ) + == "keepme" + ) + + +def test_resolve_not_submitted_empty_existing_generates(): + assert ( + resolve_name_pinyin_abbr( + name="PAC", + submitted=None, + existing="", + submitted_provided=False, + ) + == "pac" + )