feat(management): 指标元数据+试算评分配置v1.0+种子CLI——权重校验/幂等落库

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-09 16:51:56 +08:00
parent d523813435
commit afc71e61ef
4 changed files with 225 additions and 2 deletions
+61 -2
View File
@@ -51,6 +51,8 @@ import signal
import sys
from typing import Any
import sqlalchemy as sa
from app.config import get_settings
from app.db import models # noqa: F401 (register tables on Base.metadata)
from app.db.base import Base
@@ -164,6 +166,17 @@ def _build_parser() -> argparse.ArgumentParser:
),
)
# ``score-config`` ----------------------------------------------------
p_score = sub.add_parser(
"score-config",
help="Seed the default score config (idempotent).",
description=(
"Insert DEFAULT_SCORE_CONFIG (v1.0, trial) into "
"employee_score_config as active. Skips if the version "
"already exists. Batch B."
),
)
# ``scheduler`` -----------------------------------------------------------
p_sched = sub.add_parser(
"scheduler",
@@ -423,8 +436,6 @@ async def _cmd_employee_metrics(args: argparse.Namespace) -> int:
settings = get_settings()
engine, session_maker = await _init_db(settings.db_url)
try:
import sqlalchemy as sa
from app.db.models import MsgDailyStats, PhoneCall
from app.storage.employee_metrics import (
recompute_call_days,
@@ -460,6 +471,52 @@ async def _cmd_employee_metrics(args: argparse.Namespace) -> int:
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:
settings = get_settings()
engine, session_maker = await _init_db(settings.db_url)
try:
async with session_maker() as session:
version = await _seed_score_config(session)
await session.commit()
print(f"[score-config] {'seeded ' + version if version else 'already present, skipped'}")
return 0
finally:
await engine.dispose()
def main(argv: list[str] | None = None) -> int:
"""CLI entry point. Returns a process exit code."""
# Configure logging so the scheduler's JSON line + the orchestrator's
@@ -488,6 +545,8 @@ def main(argv: list[str] | None = None) -> int:
return asyncio.run(_cmd_first_reply_facts(args))
if args.command == "employee-metrics":
return asyncio.run(_cmd_employee_metrics(args))
if args.command == "score-config":
return asyncio.run(_cmd_score_config(args))
except KeyboardInterrupt:
# So `python -m app.sync.cli scheduler` exits cleanly on Ctrl-C.
return 0