fix(cma): KR 名称污染存量清理 + 透传点护栏 (cma-kr-name-pollution-cleanup-20260916)
ci/woodpecker/push/main Pipeline was canceled
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(独立复核) - 原会话因迭代上限停手,本提交代收口(工作已复核完成)
This commit is contained in:
@@ -7,7 +7,11 @@ from app.database import get_db
|
||||
from app.deps import get_entity_id
|
||||
from app.auth_middleware import require_auth, require_role
|
||||
from app.models import StrategicMap, OperationLog, MapObjective, Objective, KR, KPIDefinition, ReviewRecord, User
|
||||
from app.utils.kr_name import sanitize_kr_name
|
||||
import json
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/cma/maps", tags=["战略地图"],
|
||||
dependencies=[Depends(require_role("ceo", "finance"))],
|
||||
@@ -481,6 +485,16 @@ def _sync_objectives_krs(m, db):
|
||||
if kpi and kpi.threshold_green:
|
||||
op2, tgt2 = _parse_kr_operator_target(kpi.threshold_green)
|
||||
op, tgt = op2, (tgt2 if tgt is None else tgt)
|
||||
# 护栏(2026-09-16):名称尾部「≥/≤+数字」与目标值不一致 = 历史污染截断值,
|
||||
# 在此透传点(JSON/模板名 → krs.title)规整,防止污染继续传染(判据见 app/utils/kr_name.py)
|
||||
clean_title, _polluted = sanitize_kr_name(title, kr_item.get("target_value"), tgt)
|
||||
if _polluted:
|
||||
logger.warning(
|
||||
"KR名称污染已规整: map=%s objective=%s %r -> %r (target=%s)",
|
||||
m.id, objective.id, title, clean_title, tgt,
|
||||
)
|
||||
title = clean_title
|
||||
kr_item["name"] = clean_title # 同步回写 JSON,保持展示口径=落库口径
|
||||
kr = existing_krs.get(title)
|
||||
if kr is None:
|
||||
kr = KR(
|
||||
|
||||
@@ -11,6 +11,10 @@ from app.database import get_db
|
||||
from app.deps import get_entity_id
|
||||
from app.auth_middleware import require_role
|
||||
from app.models import Objective, ActionPlan, KR, ObjectiveKPI, KPIDefinition
|
||||
from app.utils.kr_name import sanitize_kr_name
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/cma/okr", tags=["OKR目标管理"],
|
||||
dependencies=[Depends(require_role("ceo", "finance", "it"))],
|
||||
@@ -329,6 +333,14 @@ def sync_krs(objective_id: int, data: dict, db: Session = Depends(get_db),
|
||||
kpi_id = kpi.id if kpi else None
|
||||
operator, target_value = _inherit_kpi_direction(db, kpi_id, item.get("operator"), item.get("target_value"))
|
||||
|
||||
# 护栏(2026-09-16):画布节点/接口传来的 KR 名若带历史污染尾巴(尾部「≥/≤+被截断的数字」)
|
||||
# 在此规整,防污染经 krs.title 继续传染(判据见 app/utils/kr_name.py)
|
||||
_tv = item.get("target_value")
|
||||
title, _polluted = sanitize_kr_name(title, _tv if _tv is not None else target_value)
|
||||
if _polluted:
|
||||
logger.warning("KR名称污染已规整: objective=%s %r -> %r (target=%s)",
|
||||
objective_id, item.get("title") or item.get("name"), title, _tv)
|
||||
|
||||
kr = None
|
||||
if item.get("id") and int(item["id"]) in existing:
|
||||
kr = existing[int(item["id"])]
|
||||
|
||||
@@ -7,6 +7,7 @@ from typing import Optional
|
||||
from app.database import get_db
|
||||
from app.auth_middleware import require_role
|
||||
from app.models import OKRTemplate, StrategicMap
|
||||
from app.utils.kr_name import sanitize_kr_name
|
||||
|
||||
router = APIRouter(prefix="/api/cma/okr-templates", tags=["OKR模板库"],
|
||||
dependencies=[Depends(require_role("ceo", "finance", "it"))],
|
||||
@@ -273,7 +274,8 @@ def apply_okr_template(template_id: int, data: dict, db: Session = Depends(get_d
|
||||
"kpis": [],
|
||||
"krs": [
|
||||
{
|
||||
"name": kr.get("name", ""),
|
||||
# 护栏(2026-09-16):模板名 → 新地图 KR 名 透传点,污染名(尾部符号+截断数字)在此规整
|
||||
"name": sanitize_kr_name(kr.get("name", ""), kr.get("target_value"))[0],
|
||||
"target_value": kr.get("target_value", ""),
|
||||
"weight": kr.get("weight", 33),
|
||||
"metric_kpi_id": kr.get("metric_kpi_id"),
|
||||
|
||||
Reference in New Issue
Block a user