ci/woodpecker/push/main Pipeline was canceled
- kr_name.py/krName.ts: sanitize_kr_name 判据(尾部≥/≤数字≠目标值→截断;==目标值→保留) - maps.py/okr.py/okr_templates.py: 模板名/JSON→krs.title 透传点护栏 - test: 28 passed(独立复核) - 原会话因迭代上限停手,本提交代收口(工作已复核完成)
55 lines
2.3 KiB
Python
55 lines
2.3 KiB
Python
"""KR 名称规整(历史名称污染防护)。
|
|
|
|
背景:2026-09-11 前 `MapCanvasDialogs.vue` 的逐键写入点会把「方向符号 + 已输入的首位数字」
|
|
拼进 KR 名称(输入 780 → 名称变 `营业收入≥7`)。写入口已删,但**存量数据仍在传染**:
|
|
对话框用模板 `preset_krs` 预填 KR 名 → 沿用到新目标/新地图。
|
|
|
|
判据(勿一刀切):
|
|
- 名称尾部「≥/≤ + 数字」且该数字 **≠** 目标值 → 截断值(污染)→ 去掉尾部片段;
|
|
- 数字 **==** 目标值 → 有意命名(如 `渠道留存率≥90` / `新客户数≥10` / `A级渠道占比≥30%`)→ 原样保留;
|
|
- 目标值里取不到数字(如 `为正` / `TOP3` / 空值)→ 无法判定 → 原样保留(不擅改)。
|
|
|
|
用法(护栏放在「JSON/模板名 → krs.title」的透传点,而不是用户输入点):
|
|
from app.utils.kr_name import sanitize_kr_name
|
|
clean, changed = sanitize_kr_name(name, target_value)
|
|
"""
|
|
import re
|
|
from typing import Any, Optional, Tuple
|
|
|
|
_NUM = re.compile(r"[-+]?\d*\.?\d+")
|
|
_TAIL = re.compile(r"^(?P<base>.*?)\s*(?P<sym>[≥≤])\s*(?P<num>\d+(?:\.\d+)?)\s*$")
|
|
|
|
|
|
def target_number(target_value: Any) -> Optional[float]:
|
|
"""从目标值里取数字(兼容 780 / '780' / '≥780' / '≤45天' / '≥20%');取不到返回 None。"""
|
|
if target_value is None:
|
|
return None
|
|
if isinstance(target_value, bool):
|
|
return None
|
|
if isinstance(target_value, (int, float)):
|
|
return float(target_value)
|
|
m = _NUM.search(str(target_value))
|
|
return float(m.group(0)) if m else None
|
|
|
|
|
|
def sanitize_kr_name(
|
|
name: Any,
|
|
target_value: Any = None,
|
|
target_number_value: Optional[float] = None,
|
|
) -> Tuple[str, bool]:
|
|
"""返回 (规整后名称, 是否发生变更)。target_number_value 可显式传入已解析的目标数(优先)。"""
|
|
if not isinstance(name, str):
|
|
return ("" if name is None else str(name)), False
|
|
m = _TAIL.match(name)
|
|
if not m:
|
|
return name, False
|
|
tn = target_number_value if target_number_value is not None else target_number(target_value)
|
|
if tn is None:
|
|
return name, False
|
|
if abs(float(m.group("num")) - tn) < 1e-9:
|
|
return name, False
|
|
clean = m.group("base").strip()
|
|
if not clean:
|
|
return name, False
|
|
return clean, True
|