refactor(management): 种子函数移入management包+深拷贝冻结配置+清理死代码
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,10 @@ DEFAULT_SCORE_CONFIG 为 v1.0 试算起点——所有阈值标「试算」,调
|
|||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import copy
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
# direction: +1 越大越好 / -1 越小越好;caliber 注明与看板差异
|
# direction: +1 越大越好 / -1 越小越好;caliber 注明与看板差异
|
||||||
METRIC_DEFINITIONS: dict[str, dict] = {
|
METRIC_DEFINITIONS: dict[str, dict] = {
|
||||||
"msg_sent": {"name": "发出消息(1:1)", "unit": "条", "direction": 1,
|
"msg_sent": {"name": "发出消息(1:1)", "unit": "条", "direction": 1,
|
||||||
@@ -103,3 +107,37 @@ def validate_config(config: dict) -> list[str]:
|
|||||||
if src not in METRIC_DEFINITIONS:
|
if src not in METRIC_DEFINITIONS:
|
||||||
problems.append(f"{key}.{code} 引用未定义指标 {src}")
|
problems.append(f"{key}.{code} 引用未定义指标 {src}")
|
||||||
return problems
|
return problems
|
||||||
|
|
||||||
|
|
||||||
|
async def _seed_score_config(session) -> str | None:
|
||||||
|
"""种子默认评分配置;已存在同版本返回 None(幂等)。纯会话函数可测。
|
||||||
|
|
||||||
|
同 ensure_starter_words 先例:域包提供纯会话种子函数,CLI 只做接线。
|
||||||
|
config_json 用 deepcopy 冻结快照——JSON 列须整列重赋值,深拷贝防止
|
||||||
|
长驻 API 进程未来的原地修改波及模块常量。
|
||||||
|
"""
|
||||||
|
from app.db.models import EmployeeScoreConfig
|
||||||
|
|
||||||
|
problems = validate_config(DEFAULT_SCORE_CONFIG)
|
||||||
|
if problems:
|
||||||
|
raise ValueError(f"score config invalid: {problems}")
|
||||||
|
exists = (
|
||||||
|
await session.execute(
|
||||||
|
sa.select(EmployeeScoreConfig.id).where(
|
||||||
|
EmployeeScoreConfig.version == DEFAULT_SCORE_CONFIG["version"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).scalar_one_or_none()
|
||||||
|
if exists is not None:
|
||||||
|
return None
|
||||||
|
session.add(
|
||||||
|
EmployeeScoreConfig(
|
||||||
|
version=DEFAULT_SCORE_CONFIG["version"],
|
||||||
|
name=DEFAULT_SCORE_CONFIG["name"],
|
||||||
|
status="active",
|
||||||
|
config_json=copy.deepcopy(DEFAULT_SCORE_CONFIG),
|
||||||
|
created_by="seed-cli",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
await session.flush()
|
||||||
|
return DEFAULT_SCORE_CONFIG["version"]
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ Three invocations (plus analytics backfills, see below)::
|
|||||||
python -m app.sync.cli sensitive-scan --full
|
python -m app.sync.cli sensitive-scan --full
|
||||||
python -m app.sync.cli first-reply-facts
|
python -m app.sync.cli first-reply-facts
|
||||||
python -m app.sync.cli employee-metrics
|
python -m app.sync.cli employee-metrics
|
||||||
|
python -m app.sync.cli score-config
|
||||||
|
|
||||||
Auth + DB
|
Auth + DB
|
||||||
---------
|
---------
|
||||||
@@ -471,43 +472,12 @@ async def _cmd_employee_metrics(args: argparse.Namespace) -> int:
|
|||||||
await engine.dispose()
|
await engine.dispose()
|
||||||
|
|
||||||
|
|
||||||
async def _seed_score_config(session) -> str | None:
|
|
||||||
"""种子默认评分配置;已存在同版本返回 None(幂等)。纯会话函数可测。"""
|
|
||||||
from app.db.models import EmployeeScoreConfig
|
|
||||||
from app.management.metric_definitions import (
|
|
||||||
DEFAULT_SCORE_CONFIG,
|
|
||||||
validate_config,
|
|
||||||
)
|
|
||||||
|
|
||||||
problems = validate_config(DEFAULT_SCORE_CONFIG)
|
|
||||||
if problems:
|
|
||||||
raise ValueError(f"score config invalid: {problems}")
|
|
||||||
exists = (
|
|
||||||
await session.execute(
|
|
||||||
sa.select(EmployeeScoreConfig.id).where(
|
|
||||||
EmployeeScoreConfig.version == DEFAULT_SCORE_CONFIG["version"]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
).scalar_one_or_none()
|
|
||||||
if exists is not None:
|
|
||||||
return None
|
|
||||||
session.add(
|
|
||||||
EmployeeScoreConfig(
|
|
||||||
version=DEFAULT_SCORE_CONFIG["version"],
|
|
||||||
name=DEFAULT_SCORE_CONFIG["name"],
|
|
||||||
status="active",
|
|
||||||
config_json=DEFAULT_SCORE_CONFIG,
|
|
||||||
created_by="seed-cli",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
await session.flush()
|
|
||||||
return DEFAULT_SCORE_CONFIG["version"]
|
|
||||||
|
|
||||||
|
|
||||||
async def _cmd_score_config(args: argparse.Namespace) -> int:
|
async def _cmd_score_config(args: argparse.Namespace) -> int:
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
engine, session_maker = await _init_db(settings.db_url)
|
engine, session_maker = await _init_db(settings.db_url)
|
||||||
try:
|
try:
|
||||||
|
from app.management.metric_definitions import _seed_score_config
|
||||||
|
|
||||||
async with session_maker() as session:
|
async with session_maker() as session:
|
||||||
version = await _seed_score_config(session)
|
version = await _seed_score_config(session)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ def test_metric_definitions_cover_scored_metrics():
|
|||||||
# 每个参与评分的 metric_code 都有元数据(名称/单位/方向)
|
# 每个参与评分的 metric_code 都有元数据(名称/单位/方向)
|
||||||
for dim in DEFAULT_SCORE_CONFIG["dimensions"].values():
|
for dim in DEFAULT_SCORE_CONFIG["dimensions"].values():
|
||||||
for code in dim.get("metrics", {}):
|
for code in dim.get("metrics", {}):
|
||||||
md = METRIC_DEFINITIONS[code if code in METRIC_DEFINITIONS else "active_share"]
|
|
||||||
# band 型指标的 key 带 _band 后缀,spec 里有 metric 字段:
|
# band 型指标的 key 带 _band 后缀,spec 里有 metric 字段:
|
||||||
spec = dim["metrics"][code]
|
spec = dim["metrics"][code]
|
||||||
src = spec.get("metric", code)
|
src = spec.get("metric", code)
|
||||||
@@ -46,7 +45,7 @@ def test_metric_definitions_cover_scored_metrics():
|
|||||||
|
|
||||||
|
|
||||||
async def test_seed_and_idempotent(make_session):
|
async def test_seed_and_idempotent(make_session):
|
||||||
from app.sync.cli import _seed_score_config # 纯会话函数
|
from app.management.metric_definitions import _seed_score_config # 纯会话函数
|
||||||
s = make_session
|
s = make_session
|
||||||
v1 = await _seed_score_config(s)
|
v1 = await _seed_score_config(s)
|
||||||
await s.commit()
|
await s.commit()
|
||||||
|
|||||||
Reference in New Issue
Block a user