Files
yunke/docs/superpowers/plans/2026-09-10-management-console-batch-f-scheduling-plan.md
T

8.9 KiB
Raw Blame History

云客经营管理后台·批次F-调度子批(管理指标接入定时链)实施计划

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task.

Goal: 管理域三张派生表(first_reply_fact / employee_metric_daily / employee_call_daily)进入生产定时链——4h 滚动 + 夜间全量自愈,数据每天活起来(同步审计 2026-09-10 结论 #5 的管理域部分)。

Architecture: 两处接线:scheduler.py _analytics_tick 加独立事务的管理滚动段(近 3 天,失败不回滚既有 staff/敏感词段);yunke-server/scripts/full_sync_all.sh 在 11/12 步后加 13/14 步(fact 全量 → 宽表+通话全量)+ 终局快照补三表行数。CLI 已有(first-reply-facts / employee-metrics),无新端点。边界:不碰审计文档 P0 的脚本加固/实体补抓(并行会话领地)。

Tech Stack: 同批次A(APScheduler + SQLAlchemy async + bash)。


执行者必读

  1. 上游函数签名(as-built):first_reply_facts.pairs_active_in_days(session, days) / recompute_pairs(session, pairs);storage/employee_metrics.recompute_metric_days(session, days) / recompute_call_days(session, days);staff_summary.rolling_days(today, 3)(返回当日+前2天列表)。全部幂等 delete+insert。
  2. 隔离语义(v2 Task 12:指标失败不中断主流程):tick 内管理段用独立 session+commit——失败只回滚管理段自身,staff/敏感词段已提交不受影响(当前实现是单 try 单事务,改造为两段)。
  3. 锁竞争既有结论(tick docstring 已记录):与夜间 full_sync 重叠时 DELETE+INSERT 可能锁等待,非致命,幂等下轮自愈——管理段沿用同一语义,docstring 注明。
  4. 脚本在 yunke-server/scripts/full_sync_all.sh(注意路径已从根 scripts/ 迁移);测试先例 tests/test_scheduler_analytics_tick.py。
  5. 并行会话活动照旧;中文 conventional 提交。

任务

Task F1: _analytics_tick 管理段 + 测试

Files: Modify yunke-server/app/sync/scheduler.py + tests/test_scheduler_analytics_tick.py

改造 _analytics_tick(:703)为两段:

    async def _analytics_tick(self) -> None:
        """4h 分析链(staff 滚动3天 + 敏感词增量 + 管理域滚动)——异常只记日志不断链。

        两段独立事务:段1 staff+敏感词;段2 管理域(fact 近3天活跃会话 + 宽表/
        通话 滚动3天,批次F 2026-09-10)。段2 失败只回滚自身,不牵连段1 已
        提交结果(v2 Task12:指标失败不中断主分析链)。纯 DB 无浏览器,不抢
        ``_page_lock``;与夜间 full_sync 13/14 步重叠时的锁竞争语义同段1
        (非致命,幂等下轮自愈)。
        """
        # ── 段1:staff 滚动 + 敏感词增量(既有,原样保留) ──
        try:
            async with self._session_maker() as session:
                from app.analytics.sensitive import scanner
                from app.storage.staff_summary import recompute_days, rolling_days
                from app.storage.summary import day_key

                today = day_key(int(time.time() * 1000))
                n = await recompute_days(session, rolling_days(today, 3))
                scan = await scanner.run_incremental(session)
                await session.commit()
            logger.info(
                "[analytics-tick] staff_days=%s scanned=%s hits=%s",
                n, scan.get("scanned"), scan.get("hits"),
            )
        except Exception:  # noqa: BLE001 — scheduler MUST NOT crash
            logger.exception("analytics tick failed")

        # ── 段2:管理域滚动(fact 活跃会话 + 宽表/通话,当日+滚动3天) ──
        try:
            async with self._session_maker() as session:
                from app.storage.employee_metrics import (
                    recompute_call_days,
                    recompute_metric_days,
                )
                from app.storage.first_reply_facts import (
                    pairs_active_in_days,
                    recompute_pairs,
                )
                from app.storage.staff_summary import rolling_days
                from app.storage.summary import day_key

                rd = rolling_days(day_key(int(time.time() * 1000)), 3)
                pairs = await pairs_active_in_days(session, rd)
                f = await recompute_pairs(session, pairs)
                w = await recompute_metric_days(session, rd)
                c = await recompute_call_days(session, rd)
                await session.commit()
            logger.info(
                "[analytics-tick] mgmt fact_pairs=%s wide_rows=%s call_rows=%s",
                f, w, c,
            )
        except Exception:  # noqa: BLE001
            logger.exception("management metrics tick failed")

(导入合并/去重按文件现状微调;today 变量两段各自取或共享只读。注:recompute_metric_days 内部会再查一次 pairs_active_in_days(与本段显式调用重复但廉价)——非冗余待修,fact 段需要显式拿 pairs 计数记日志。)

测试追加(照既有文件 mock 模式):

  1. tick 调用顺序:staff recompute_days → 敏感 scan → pairs_active_in_days(rolling3) → recompute_pairs → recompute_metric_days → recompute_call_days;
  2. 段2 失败隔离:recompute_metric_days 抛异常 → staff recompute_days 与敏感 scan 仍被调用且各自 commit 成功、tick 不抛、异常被记(caplog);
  3. rolling 窗口传参 = rolling_days(today,3)。
  • pytest tests/test_scheduler_analytics_tick.py -v + 全量 -q → commit feat(management): 4h分析链接入管理域滚动段——独立事务隔离/fact活跃会话+宽表+通话滚动3天

Task F2: full_sync_all.sh 13/14 步 + 快照行数

Files: Modify yunke-server/scripts/full_sync_all.sh

第 12 步之后(COMPLETE 横幅之前)加:

# 13:首响事实表全量重建(管理域,跨日精确串;批次A)——会话发现读
#    msg_daily_stats(fact 行直算读 message),故须在 11 步后;与 11/12
#    同为夜间自愈链(绕过钩子的写入由全量重算纠正)
echo "$(date) --- 13/14 First-reply-facts rebuild ---"
$PY -m app.sync.cli first-reply-facts
# 14:管理宽表+通话小表全量重建(驾驶舱/员工页读路径;串类列与 fact 同
#    算法各自直算,放 fact 后保持派生链顺序语义)
echo "$(date) --- 14/14 Employee-metrics rebuild ---"
$PY -m app.sync.cli employee-metrics

终局快照 TABLES 追加三行(放 daily stats rows 后):

    ("first reply facts", "SELECT COUNT(*) FROM first_reply_fact"),
    ("employee metric rows", "SELECT COUNT(*) FROM employee_metric_daily"),
    ("employee call rows", "SELECT COUNT(*) FROM employee_call_daily"),
  • bash -n yunke-server/scripts/full_sync_all.sh 语法过 + commit feat(management): 夜间全量脚本13/14步——fact与宽表全量自愈+终局快照三表行数

Task F3: 本地 scheduler 容器重建 + 真机验证 + 归档

  1. 真机验证滚动段(直接跑 tick 逻辑,不等 4h):临时脚本或 python -c 用 scheduler 的 session_maker 调 _analytics_tick()(或手动等价:按 rolling 3 天调三个 recompute + commit)——记录耗时(预期 fact 活跃会话数十秒级内)+ 三表当日行数变化(recompute 后 updated 行 freshness);
  2. 幂等复跑:连跑两次行数一致;
  3. 本地 scheduler 容器重建(新代码进夜间链):docker ps 确认 scheduler 容器名 → docker compose -f docker-compose.yml up -d --build scheduler——范围仅本地(服务器 scheduler 镜像 2026-09-09 已重建过,服务器侧等部署时随 DEPLOY.md 流程走,本任务不碰);重建前后 docker exec <scheduler> python -c "import app.sync.scheduler" 冒烟;(已知后果不阻塞:phone_call 尚未进 4h/每日页面同步链[审计P0-5,并行会话领地]——通话小表滚动重算在补齐前只是从静态源幂等重生成);
  4. 归档:耗时/行数/容器重建记录 → 核实文档「批次F-调度」小节 + commit。
  • commit docs(management): 批次F调度接入验证——滚动段耗时/幂等/容器重建(清9d3cc8e旧账)

验收清单

  • tick 两段隔离测试过(段2 异常不牵连段1);
  • 夜间脚本 13/14 步 + 快照三表行数;
  • 滚动段真机耗时记录(预期 <60s)+ 幂等;
  • 本地 scheduler 容器已重建(新代码),夜间 02:00 链将含管理域;
  • 全量测试绿;
  • 未触碰审计 P0 领地(脚本加固/实体补抓——并行会话负责)。

后续(不在本子批)

E:辅导+快照;F 余项:dashboard 性能修复(unreplied 索引)、winsorize 决策、服务器部署(届时服务器 scheduler 镜像重建走 DEPLOY.md)。