484725f0b2
fix(deploy): [skip-issue] 共享文档登录脚本迁移至 app/services/ 修复清理清单导致镜像内缺失
...
gitleaks / gitleaks (push) Failing after 11s
backend-test / commit-message-lint (push) Successful in 2s
backend-test / analytics-ods-isolation (push) Failing after 3s
backend-test / backend-lint-test (push) Failing after 1h9m31s
backend-test / frontend-lint-test (push) Successful in 1m35s
根因: 两脚本在 scripts/archive/ 子目录, 但 upgrade.sh 白名单 find 只保留 scripts/ 根目录
同名文件, .dockerignore 放行规则 !scripts/kdocs_login.py 也匹配不到 archive/ 路径,
导致 docker compose build 时脚本被排除, 镜像内根本没有 → 共享文档同步反复报
"未找到金山登录态"
修复:
- kdocs_login.py / tencent_doc_login.py 迁入 app/services/shared_doc_{kdocs,tencent}_cli.py
- 去掉 sys.path 注入, 直接 from app.core.config import settings
- upgrade.sh: 整个 backend/scripts/ 清空(不再需要按文件名白名单保留)
- .dockerignore: 删掉两条 scripts/ 登录脚本放行规则
- package.sh: 删掉追加登录脚本到 tar 的 staging 逻辑
- 服务层/错误提示/测试断言同步更新路径引用
- 旧文件删除
2026-10-20 20:06:35 +08:00
6517f8501e
fix(bms): #336 refund_record_report 导出 GET query ISO 日期改写 — 根治 E 路静默空采 6 天
...
gitleaks / gitleaks (push) Failing after 11s
backend-test / commit-message-lint (push) Successful in 2s
backend-test / analytics-ods-isolation (push) Failing after 3s
backend-test / backend-lint-test (push) Failing after 35m44s
backend-test / frontend-lint-test (push) Successful in 1m4s
根因(生产抓包实证): BMS 前端 10-15 深夜发版后, wxRefundBill/downLoadExcel
GET query 携带 ISO-8601 UTC 日期(refundStartTime=2026-10-11T16:00:00.000Z),
BMS 静默返 137 字节纯表头空 CSV — 与 #335 return_tracking 同族(日期选择器
v-model 被 toISOString 序列化), 但走 GET+query 且无报错, 6 次同步全部
"成功 0 行"。对照: 页面默认查询用正常格式 → total=1948, 数据一直在。
修法: 白名单加 refund_record_report; route 改写扩展支持 GET query string
(parse_qsl 逐值改写 → urlencode 重组, 空格→+ 与 BMS 自身拼参一致)。
验证: ISO 改写 9 项全绿(新增 GET query 3 项), bms 回归 77 项全绿,
ruff 全绿, 全量 pytest 全绿。
Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com
2026-10-20 15:45:22 +08:00
e0612509ec
fix(bms): #335 bms_order 导入分批 commit — 根治 DB memcg OOM/recovery
...
gitleaks / gitleaks (push) Failing after 10s
backend-test / commit-message-lint (push) Successful in 2s
backend-test / analytics-ods-isolation (push) Failing after 2s
backend-test / backend-lint-test (push) Failing after 6m18s
backend-test / frontend-lint-test (push) Successful in 55s
根因(2026-10-19 生产三次实证+内核日志): 10 万行单事务单语句 upsert 使
postgres backend 进程 RSS 膨胀至 ~1.9GiB, 撞破 db 容器 2GiB memcg 限额被
OOM kill → postmaster 全库 recovery → 导入 retry 仍撞 recovery → 「本轮未完成」。
修法: upsert_bms_orders 分批 2000 行/commit(回归 docstring 原始设计),
单批 ~3.8 万绑定参数(<65535), WAL/事务内存随 commit 释放; upsert 幂等,
中途失败重跑安全。
2026-10-19 16:46:04 +08:00
b4e1d56a28
fix(bms): #335 return_tracking route 模式补 download — 导出接口不含 export
...
gitleaks / gitleaks (push) Failing after 10s
backend-test / commit-message-lint (push) Successful in 2s
backend-test / analytics-ods-isolation (push) Failing after 2s
backend-test / backend-lint-test (push) Failing after 5m14s
backend-test / frontend-lint-test (push) Successful in 1m6s
根因(生产实证 2026-10-19 12:57): return_tracking 导出接口 URL 为
.../customer-return-tracking/downLoadExcel, 不含子串 "export", 导致
page.route(r"export") 未命中 → ISO 日期未被改写 → BMS 仍返回 400。
导出响应监听器原本就用 "export|download|record" 三选一判据, route 模式
与之对齐为 export|download(return_tracking 命中 download)。
2026-10-19 12:58:45 +08:00
4cb7d7c2f2
fix(bms): #335 return_tracking 导出请求 ISO 日期 route 改写
...
gitleaks / gitleaks (push) Failing after 10s
backend-test / commit-message-lint (push) Successful in 2s
backend-test / analytics-ods-isolation (push) Failing after 3s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
根因(生产实证 2026-10-19): BMS 后端 ~10-11 起收紧导出接口日期反序列化,
只收 yyyy-MM-dd HH:mm:ss; 而 return_tracking 页面导出把日期选择器
v-model(JS Date 对象)直接 JSON 序列化为 ISO-8601 UTC 串(实锤 payload:
2026-10-19T01:41:07.000Z = 北京 09:41:07 窗口终点) → BMS 400 JSON parse
error, 该源自 10-11 起每次失败。
修法: 导出前对白名单源(仅 return_tracking)注册 page.route 拦截, 把请求
体中完整匹配 ISO 形态的日期串改写为北京时 yyyy-MM-dd HH:mm:ss 后放行。
非 POST / 非 JSON / 无 ISO 日期一律原样放行; 改写异常不阻断导出。
- 改写逻辑 _rewrite_iso_dates_in_json 递归处理嵌套 dict/list, 只改完整
匹配 ISO 的字符串值(防误伤长文本内嵌日期子串), 返回改写处数
- _download_with_retry 拆出重试循环体 _download_retry_loop, 外层套
route 注册/卸载 try/finally; 公共签名不变, 两处调用点零改动
- 新增 6 用例单测锁定: Z/偏移/跨日换算、嵌套计数、非 ISO 形态不改写、
白名单契约
2026-10-19 12:34:12 +08:00
38f8042cf7
fix(bms): #335 低频采集两类失败 —— bms_order OOM 循环 + 导出异常请求诊断
...
gitleaks / gitleaks (push) Failing after 10s
backend-test / commit-message-lint (push) Successful in 3s
backend-test / analytics-ods-isolation (push) Failing after 2s
backend-test / backend-lint-test (push) Failing after 14m3s
backend-test / frontend-lint-test (push) Failing after 36m8s
- _fetch.py 两处全量导出读取(openpyxl→calamine): bms_order 导出被 BMS
硬截断 10 万行, openpyxl DOM 全量加载峰值 RSS>4GiB 触发 memcg OOM
(dmesg 实证 anon-rss 4161640kB), gunicorn 被杀→重启→僵尸清理标 failed,
2026-10-13 起每小时循环; calamine 内存降一个量级(data_parser 同款先例)
- docker-compose.yml backend 内存 4G→8G 双保险(主机 23G 余量充足)
- _browser.py 导出 JSON 异常时记录请求 url/method/post_data(诊断):
定位 return_tracking 页面把日期序列化为 ISO-UTC(BMS 10-11 收紧只认
yyyy-MM-dd HH:mm:ss)的实际端点与 payload, 为下一步 route 改写供事实
- ruff 全绿; bms/fetch/browser 相关测试 EXIT=0
Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com
2026-10-19 10:01:27 +08:00
ad8ed8661c
feat(data): #333 数据查看树五路原始表采纳状态只读展示
...
gitleaks / gitleaks (push) Failing after 10s
backend-test / commit-message-lint (push) Successful in 3s
backend-test / analytics-ods-isolation (push) Failing after 2s
backend-test / backend-lint-test (push) Failing after 4m59s
backend-test / frontend-lint-test (push) Has been cancelled
- refund_adoption 新增 ADOPT_REASON_CN 规则码中文释义(17码, 单一来源紧贴
规则码定义), ADOPT_REASON_ENUM/ADOPT_STATE_ENUM 经 fields API 下发前端
- data_source_registry: 经营五路 A~E 程序化补采纳四列 FIELD_OVERRIDES
(adopt_state 三态枚举+可筛选 / adopt_reason 释义枚举不可筛 /
adopt_rule_ver+adopted_at 仅展示); 启动 lifespan 自动注册生效
- Browser.vue 三层落地: ①设计说明补 #333 段(取舍: 不提供改采纳状态入口,
人工治理一律走 adoption_overrides 留痕); ②页面层 adopt_state 列头 ⓘ
三态口径说明(含"未判定=未跑作业"); ③行级 el-tag 三态徽标 + 规则码
中文 tooltip(支持 manual: 前缀人工覆盖展示) + 采纳状态下拉筛选(待定
治理跟进); 释义前端零硬编码, 规则升级只改后端
- 测试 test_adopt_state_display_333.py 5 用例: 注册四列齐全/三态元数据/
释义与规则码全集一致(防两处漂移)/版本与时间不可筛
- ruff 全绿; 前端 build 通过; 全量 pytest 零失败
Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com
2026-10-19 09:12:35 +08:00
b5b7b8aa75
refactor(refund): #334 退款域业务代码零读 ods —— 全状态场景迁 std_*_all 镜像视图
...
gitleaks / gitleaks (push) Failing after 11s
backend-test / commit-message-lint (push) Successful in 3s
backend-test / analytics-ods-isolation (push) Failing after 2s
backend-test / backend-lint-test (push) Failing after 15m29s
backend-test / frontend-lint-test (push) Has been cancelled
- 新增 5 个全量镜像视图(std_refund_online_all/std_wallet_flow_all/
std_refund_shanmao_all/std_refund_manual_all/std_refund_record_all,
SELECT * 透传 ods 含采纳四列), 单一来源 STD_ALL_VIEWS 供迁移/conftest 共用
- 迁移范围(业务读取→视图, 写入一律不动仍落 ods):
analytics/refund.py 18 处(挽回专项/状态分布/工作量/导出明细/登记水位)
analytics/refund_chain.py 3 处(链路台账, ORM→table()/column()轻量句柄
避免对只读视图 autoflush) today/_staff.py 2 处(退款工作量)
device_profile 5 / audit_timeline 7 / reconcile_check 24 /
matcher 8 / reconcile 7(读; backfill_d_op_time 写路径保留)
- quality_disposition_service 保留 ods 读(质量治理本职, 铁律第三本职)
- ods 回归三本职: 采集写入 / 采纳作业读取 / 质量治理标记
- 口径文档+AGENTS.md 铁律15: 全状态语义一律读 std_*_all, 业务代码禁直读 ods
- ruff app/ tests/ 全绿; 全量 pytest 2513 全绿(首轮 5 个 flake 重跑收敛)
Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com
2026-10-19 08:30:32 +08:00
687a817f6e
docs(analytics): #332 铁律15例外声明补齐 —— 退款挽回专项全状态读点
...
gitleaks / gitleaks (push) Failing after 11s
backend-test / commit-message-lint (push) Successful in 2s
backend-test / analytics-ods-isolation (push) Failing after 3s
backend-test / backend-lint-test (push) Failing after 8m19s
backend-test / frontend-lint-test (push) Successful in 36s
- _refund_recovery_core / refund_recovery_analysis / person_devices /
device_analysis / device_detail docstring 声明: 读 ods 原始层 refunds
为全状态语义例外(挽回漏斗需追申请中/无需退款未完结工单, std 只含
已退款采纳行), 防后续按铁律15误"修"成读 std 导致漏斗数字失真
- 纯注释, 零行为变更; ruff 与挽回相关测试全绿
Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com
2026-10-19 06:16:19 +08:00
9e1e609425
docs(governance): #332 蓄水池口径拍板纯B一本账 —— A路转入提现工单仅作按单台账
...
gitleaks / gitleaks (push) Failing after 11s
backend-test / commit-message-lint (push) Successful in 3s
backend-test / analytics-ods-isolation (push) Failing after 2s
backend-test / backend-lint-test (push) Failing after 4m58s
backend-test / frontend-lint-test (push) Successful in 52s
- 口径文档 §四: 进水/出水一律以 std_wallet_flow 为唯一准绳, 不引入 A 路
工单; 「转入提现账户」工单与「订单退费转入」是同一笔钱两本账, 前者仅
用于按单链路分析, 不参与蓄水池/水位核算, 严禁重复计入; §二说明同步
- refund_chain 注释改口: 链路分析=按单业务台账语义(非蓄水核算), 保留
A 路工单锚点 —— 生产实测 B 路进水 remark 无 RF 单号, 纯 B 无法按单还原
- 代码侧水位/勾稽本已是纯 B(std_wallet_flow), 零行为变更; 链路相关测试全绿
Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com
2026-10-19 01:38:49 +08:00
b76c3a49c5
docs(governance): #332 退款五路标准层 批次E(T9 口径文档定稿+铁律15挂链)
...
gitleaks / gitleaks (push) Failing after 11s
backend-test / commit-message-lint (push) Successful in 2s
backend-test / analytics-ods-isolation (push) Failing after 2s
backend-test / backend-lint-test (push) Failing after 31m57s
backend-test / frontend-lint-test (push) Successful in 56s
- 口径文档去草案标注, 升版 v1.0(实施), 记录 T7 双跑确认与 T8 切换完成
- README §6.1 引用列 ⏳ →✅ 铁律 15
- AGENTS.md 绝对禁止新增第 15 条: 统计一律读 std_*、禁止查询侧散落
复刻采纳条件、ods 只同步不治理(采纳四列 upsert 不可覆盖)、规则变更
必须升版 ADOPT_RULE_VER、企业商户统一走 dim_device_merchant
Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com
2026-10-19 00:46:48 +08:00
00d5845878
feat(analytics): #332 退款五路标准层 批次D(T8 analytics全入口切换std五表)
...
gitleaks / gitleaks (push) Failing after 12s
backend-test / commit-message-lint (push) Successful in 2s
backend-test / analytics-ods-isolation (push) Failing after 2s
backend-test / backend-lint-test (push) Failing after 19m30s
backend-test / frontend-lint-test (push) Successful in 3m54s
- 退款统计全入口 clean 视图取数切换 std 五表(refund/cash/refund_chain/
rr_views/today 系列/category/complaint/reception), 导出仅汇总切 std
- 删除查询侧散落排除 WHERE(失败/冻结关键词、质量状态过滤)——已在
采纳作业 r20261016_v1 表层固化; 全状态/全方式语义例外改读 ods 原始层
- 删 analytics 重导出的 9 个内部符号; 两处悬空注释改写(宽表/投诉侧)
- 测试适配: seed ods 后跑采纳作业固化 std(commit=False 同连接/
commit=True 隔离连接), quality_parity 语义升版为同源 std 断言
- 全量 pytest 2585 通过(exit=0), ruff app/ tests/ 全过; analytics 内
*_clean 取数 grep 零残留(TR-8.2)
Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com
2026-10-18 22:47:57 +08:00
23a041833d
feat(governance): #332 退款五路标准层 批次B(T4人工覆盖API+T5企业商户维表+T6调度接入与并发互斥)
...
gitleaks / gitleaks (push) Failing after 11s
backend-test / commit-message-lint (push) Successful in 3s
backend-test / analytics-ods-isolation (push) Failing after 2s
backend-test / backend-lint-test (push) Failing after 29m42s
backend-test / frontend-lint-test (push) Successful in 56s
T4 人工覆盖 API: admin_refund_adoption(单路/全量提交覆盖 upsert + 审计留痕 +
作业联动重跑), 非法枚举/路由 422, 列表与撤销。
T5 E 路企业商户维表: dim_device_merchant_service 纯 SQL 全量重建(集团映射 +
LAG/LEAD SCD 分段 + overlap 冲突 + 假SN 过滤 + 档案仲裁), 迁移 a332b05d6e7f
新增 arbiter_pick 列; 提供消费侧 as-of JOIN 标准片段。
T6 调度接入: bms_scheduler 同步成功后顺序触发采纳作业→维表重建; jobs.py 以
事务级 pg_try_advisory_xact_lock(随作业 commit 自动释放)实现并发互斥,
admin 手动触发/维表重建返回摘要, 每次运行写 system_event 留痕。
测试: T4 8例 / T5 8例 / T6 5例; T2 用例补事务内清残留, 规避作业内部 commit
落在持久测试库导致的绝对计数假红。
全量 pytest 2519 例通过, ruff(app+tests) 通过。
2026-10-16 16:30:09 +08:00
8bc7e7bca7
feat(governance): #332 退款五路标准层 T2采纳作业+T3同步护栏与回归测试
...
- T2 新增 app/services/governance/refund_adoption.py: 15个机器规则码集中
定义(含普查来源注释)+ADOPT_RULE_VER=r20261016_v1; 五路集合化SQL
(text+绑定参数)keyset分批盖章(UPDATE_BATCH=5000)ods四列; TRUNCATE+
INSERT…SELECT 重建5张std表(B写flow_dir/D写source_grade/E全量);
人工覆盖LEFT JOIN(state以覆盖为准, reason永记机器码); 返回三态计数/
std行数/Decimal金额/耗时; 未知枚举→pending不进std。
- T3 同步护栏: _execute_upsert 双保险(rows入口剔除ADOPT_COLUMNS+update
列清单再排除); manual独立路径与E路 update_cols 同步排除; wallet
do_nothing 天然安全以测试固化。
- 测试: test_refund_adoption_rules.py 9例(全规则码命中/std派生列/pending
不进std/覆盖撤销往返/幂等校验和/小批次游标); test_sync_preserves_
adoption.py 6例(五路径更新不覆盖+插入为NULL+投毒rows护栏);
test_e_route_switch dup夹具加order_suffix兼容测试库唯一索引。
- 全量 pytest EXIT=0 无失败; ruff 全绿。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-10-16 14:45:39 +08:00
9700715be1
feat(models): #332 退款五路标准层 T1 迁移+ORM(治理四列/std五表/dim两表/override表)
...
- 迁移 f332a01b2c3d(单 head, down=c3d4e5f6a7b8): 五路 ods 加
adopt_state/adopt_reason/adopt_rule_ver/adopted_at + 10 个部分索引;
建 std_refund_online/std_wallet_flow(flow_dir)/std_refund_shanmao/
std_refund_manual(source_grade)/std_refund_record + dim_merchant_group(种子
云客猫系3行) + dim_device_merchant + adoption_overrides; downgrade 完整逆序。
- ORM: AdoptionColumnsMixin(含 ADOPT_COLUMNS 常量)混入五路 ods 模型;
新增 refund_standard.py / dim_merchant.py 并注册。
- conftest: 建表前 DROP 五路旧 ods 表(create_all 不 ALTER)+补迁移专属
uq_manual_refunds_dedup 部分唯一索引(同步路径测试依赖)。
- 探针库验证: 空库全链 upgrade/种子恰3行/downgrade 往返无损; ruff 绿。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com >
2026-10-16 14:45:18 +08:00
96d6ef8859
docs( #332 ): 新增退款五路数据标准与采纳规则 META v1.0
...
- 定义原始同步(ods+三态采纳标识)→std_*标准物理表→analytics 的权威分层
- A/B/C/D/E 逐路采纳口径(2026-10-16 业务逐条确认+生产只读普查验证)
- B 路钱包蓄水池恒等式;E 路企业商户维表与云客猫集团归并
- README §6.1 META 索引登记(15 份);AGENTS 铁律 15 待文档批准后挂
2026-10-16 11:57:25 +08:00
7632342c39
docs: #328 #329 #330 #331 技术债批次处置交接记录(20261016 接续点)
gitleaks / gitleaks (push) Failing after 10s
backend-test / commit-message-lint (push) Successful in 2s
backend-test / analytics-ods-isolation (push) Failing after 2s
backend-test / backend-lint-test (push) Failing after 19m32s
backend-test / frontend-lint-test (push) Successful in 2m3s
2026-10-15 23:08:31 +08:00
aa9bf2fd30
docs: #328 #329 #330 #331 台账新增 §十八 技术债分类处置(批次A/B + 实查纠偏)
gitleaks / gitleaks (push) Failing after 10s
backend-test / commit-message-lint (push) Successful in 3s
backend-test / analytics-ods-isolation (push) Failing after 2s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
2026-10-15 22:56:29 +08:00
b2f781492e
fix(tests): #331 删 test_device_dim_filter 未使用 import(F401)
...
main 上 ruff 门禁红灯(F401), 删 from sqlalchemy import select; ruff check app/ tests/ 归零。
2026-10-15 22:56:29 +08:00
564b6e7912
docs(services): #329 manual_refund_sync 去重键 docstring 与实现对齐
...
原 docstring 写 (register_time, order_no, refund_amount), 与 _key_cols 不符;
改为真实键 (北京登记日期, order_no, COALESCE(device_sn,''), COALESCE(registrant,'')).
2026-10-15 22:56:28 +08:00
05e15e385d
fix(alembic): #328 迁移残留裸 print 改 logger(4 处/3 文件)
...
AGENTS 铁律 1 禁裸 print; 改 logging.getLogger("alembic.runtime.migration"),
保留原提示文案与语义。
2026-10-15 22:56:28 +08:00
f87998e9b8
chore(repo): #330 工具目录 .trae/.dsh/.workbuddy 去跟踪 + .gitignore
...
依据《仓库入库规范》C7(工具私有状态不入库):
- git rm -r --cached .trae .dsh .workbuddy (15 文件, 仅去跟踪, 磁盘保留)
- .gitignore 增补 .trae/ .dsh/
2026-10-15 22:56:27 +08:00
8c35a7ae95
docs(spec): #191/#299 收口任务清单勾选进度(T1-T5,T9-T11 完成)
gitleaks / gitleaks (push) Failing after 10s
backend-test / commit-message-lint (push) Successful in 2s
backend-test / analytics-ods-isolation (push) Failing after 2s
backend-test / backend-lint-test (push) Failing after 1m43s
backend-test / frontend-lint-test (push) Successful in 1m9s
2026-10-15 17:20:34 +08:00
a8b68f6ba4
fix(scheduler): #191 低频档 per-source 小时窗口修复 + #299 覆盖率守卫
...
gitleaks / gitleaks (push) Failing after 11s
backend-test / commit-message-lint (push) Successful in 2s
backend-test / analytics-ods-isolation (push) Failing after 3s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
#191 : 调度器低频分支调用 should_run_low_freq 时漏传 deadline_hour, 取默认 7
→ now_hour>=7 恒 False, 使 per-source hour>=7 的低频档(E 路 15:00)**永不触发**
(生产实测近 20 天零 scheduled 记录, E 路数据仅靠人工手动触发维持, 已落后 5 天)。
修复: 显式 deadline_hour=now_cn.hour+1, 窗口 = [该源配置小时, 该源配置小时+1)。
补 4 个单测: 窗口语义 / 根因复现 / 源码漂移守卫 / 覆盖率告警。
#299 顺带项 5: source_freshness_service 新增 check_device_combo_coverage 只读巡检,
无套餐设备占比超 5%(当前 2.62%) → system_events 告警(日节流), 接入每小时巡检入口;
抓"成功但覆盖不完整"(0 行当成功 / 回补不全)。
2026-10-15 17:08:20 +08:00
0ea12267b8
Merge branch 'main' of http://10.147.17.41:3000/tanzk/csctalon
gitleaks / gitleaks (push) Failing after 11s
backend-test / commit-message-lint (push) Successful in 2s
backend-test / analytics-ods-isolation (push) Failing after 3s
backend-test / backend-lint-test (push) Failing after 1m41s
backend-test / frontend-lint-test (push) Successful in 58s
2026-10-15 16:11:01 +08:00
8fd561835c
feat(mcp): #327 MCP 只读数据出口(SSE + external_token 复用)
...
- 新增 app/mcp/: MCP over SSE 最小实现(initialize/tools/list/tools/call)
- 首批 2 工具: device_behavior(设备360) / recharge_traffic(充值流量)
- 认证/限流/审计复用 external_service 体系, scope 沿用 device/recharge
- 只读兜底: 每个 tools/call 前 SET TRANSACTION READ ONLY(PG 会话级硬约束)
- SSE 端点兼容 ?token= query(EventSource 无法带 Header)
- 测试: 11 项单测覆盖握手/鉴权/白名单/只读/正常调用
- 回归: 全量 pytest 2480 通过 0 失败 / ruff 通过 / C2 门禁 0 违规
2026-10-15 16:10:41 +08:00
d6006e9b8b
fix(frp): frpc 模板补登 talon-http 隧道条目 (talon.kimirock.top → 本地 4080)
...
gitleaks / gitleaks (push) Failing after 10s
backend-test / commit-message-lint (push) Failing after 2s
backend-test / analytics-ods-isolation (push) Failing after 2s
backend-test / backend-lint-test (push) Failing after 1m45s
backend-test / frontend-lint-test (push) Successful in 36s
生产迁移 node-2080 (prod-csctalon-talon VM) 后 frpc.toml 只保留 web-http,
talon 隧道条目丢失 → VPS OpenResty (mTLS 通过) → frps 无此 customDomain → 404。
模板补登 talon-http, 重装/迁移时不再丢。
2026-10-15 04:38:28 +08:00
af0572b2f0
refactor(analytics): #326 cs_records 有效接待判据归一为单一谓词(零语义变更)
...
gitleaks / gitleaks (push) Failing after 10s
backend-test / commit-message-lint (push) Successful in 3s
backend-test / analytics-ods-isolation (push) Failing after 2s
backend-test / backend-lint-test (push) Failing after 1h3m31s
backend-test / frontend-lint-test (push) Successful in 2m54s
- _core 新增三件套: _cs_time_cond(ORM 时间窗)/_cs_operator_cond(操作人
弱判据)/_cs_time_sql(裸SQL片段); NULL 三写法(IS NOT NULL+比较/裸比较/
BETWEEN)在 PG WHERE 中同为不通过, 行选择等价
- 替换 25 处等价出现点(today×18/reception×5/complaint×2/refund 裸SQL);
保留 8 处非「有效接待」语义的专用条件(±24h 邻近JOIN/c2工单±2天等)不强行归一
- f-string 仅嵌代码内常量片段, 值一律走绑定参数(参数化铁律不破)
- 回归: 全量 pytest 0 失败 / ruff 通过 / C2 门禁 0 违规
- 生产对账: 发布后同窗口(date=2026-09-15)三口径 KPI 与改前基线 diff=0
2026-10-14 23:34:17 +08:00
d1199b8686
fix(analytics): #325 清理5个存量红测试, 修复投诉根因热力图双bug
...
gitleaks / gitleaks (push) Failing after 11s
backend-test / commit-message-lint (push) Successful in 2s
backend-test / analytics-ods-isolation (push) Failing after 2s
backend-test / backend-lint-test (push) Failing after 7m4s
backend-test / frontend-lint-test (push) Successful in 1m4s
- rr_views 投诉根因热力图(_view_complaint_root_heatmap) SA2.0 双bug:
① CompoundSelect(union) 直接取 .c → AttributeError, 线上调用即500;
② Row 字符串下标 r["投诉子类"] 已移除 → 改 _mapping。空库冒烟原即红,
属存量代码缺陷(冒烟从未跑通过该 preset)
- category_ops_overview 设备类型维测试: #293 起 key 源已从 DimDeviceType
改为 devices.device_type(中文名), 测试对齐
- ai_data_supply 行为画像测试: 补 package_name(#293 质检排除会把 NULL
行过滤掉) + 时间锚定(写死 2026-09-01 已滑出 30 天窗口)
- llm_tiers 测试: expert 首选对齐 #300 值班切换后的 gpt-oss-120b
全量5红→0, smoke 套件全过, ruff 通过
2026-10-14 18:50:29 +08:00
6b80c7b94c
fix(deploy): #148 修复 package.sh 因登录脚本移入archive而静默失败
...
gitleaks / gitleaks (push) Failing after 14s
backend-test / commit-message-lint (push) Successful in 2s
backend-test / analytics-ods-isolation (push) Failing after 3s
backend-test / backend-lint-test (push) Failing after 1m55s
backend-test / frontend-lint-test (push) Successful in 48s
tencent_doc_login.py/kdocs_login.py 移入 backend/scripts/archive/ 后,
原 tar rf ... 2>/dev/null 在 set -e 下静默中止 → 不产出 .tar.gz/latest.tar.gz,
方式B(tar包)发布链路不可用且无报错。改为 canonical 缺失回退 archive/,
经暂存目录以标准路径追加; 均缺失则告警跳过, 绝不静默。
2026-10-14 18:14:36 +08:00
354d84dd61
fix(scripts): #293 设备档案SN补全CLI容错未知参数(parse_known_args)
...
手动长跑入口(最长15-19h)对手动误传的额外参数不再硬崩;
本脚本无程序化调用方(调度器走服务层), 仅影响手动CLI体验。
2026-10-14 18:14:29 +08:00
89d1057b1b
#322 P2 方案A 全线: 10 张 public 干净视图 + analytics 全改读, C2 基线清零
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / analytics-ods-isolation (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
- 新增迁移 c3d4e5f6a7b8: 批量建 bms_order_clean/device_combo_clean/card_complaint_clean/
external_complaint_clean/cs_record_clean/wallet_withdrawal_clean/device_flow_usage_clean/
manual_refund_clean/refund_record_report_clean/shanmao_refund_clean 共 10 张薄视图,
逐列取自 ods 源表(口径逐字不变, 不做任何加工), 仅排除内部溯源列 synced_at/raw_extra/updated_by_raw
- 新增 app/models/ods_clean.py: CLEAN_VIEWS 作为单一列清单来源 + 11 个只读模型
(RefundClean 从 refund.py 迁入); 迁移与 conftest 视图重建共用同一来源, 消除双份定义漂移
- analytics 14 文件: ORM 模型类与裸 SQL 表名全部由 ods 改读 *_clean 视图
- 手动退款质检写路径移出 analytics → app/services/quality/manual_refund_review.py
(读走 ManualRefundClean; 确认/修正写走 ods 基表 ManualRefund, 视图不可写);
analytics 门面继续导出原 4 个函数, 路由零改动
- tests/conftest.py: refund_clean 专用拆表重建 → 按 CLEAN_VIEWS 通用循环
- C2 棘轮基线 64 → 0(scripts/analytics_ods_isolation_baseline.txt 仅剩表头)
2026-10-14 17:30:50 +08:00
cc37a68c37
#322 方案A: refund 域 analytics 改读 public.refund_clean 精简视图
...
gitleaks / gitleaks (push) Failing after 3h11m7s
backend-test / analytics-ods-isolation (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
- 新增迁移 a9b0c1d2e3f4: CREATE VIEW public.refund_clean AS SELECT 21 列
FROM ods.refunds, 口径逐字不变(不暴露 package_price/raw_extra/synced_at/
updated_by_raw/quality_note 5 个内部列)
- 新增只读模型 RefundClean(不继承 Quality/Timestamp mixin); Refund 写入
模型不动, upsert 入库与 actual_refund_time 回填仍直写 ods.refunds
- services/analytics 14 个文件全部由 Refund/ods.refunds 切换到
RefundClean/refund_clean; refund.py GROUP BY 补 refund_amount
(视图无主键致 PG 丢失函数依赖推断, refund_no 唯一故语义不变)
- conftest: 测试库不跑迁移, create_all 后按同口径重建 refund_clean 视图
- C2 棘轮基线 80 -> 64(收敛 16 条)
2026-10-14 12:52:40 +08:00
0cae935765
#321 current_combo_name 投影: dim 当前切片只补空回写(+可选覆盖)
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / analytics-ods-isolation (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
- dim_device_combo_service.refresh_current_combo_from_dim(db, *, overwrite=False):
默认**只补空**(保留 BMS 现值, 零回归); overwrite=True 则以 dim 覆盖(仅改差异行)
- 生产实测 dim 当前切片仅覆盖 86% 有 current 的设备(缺 44.5 万台) → 故不默认覆盖
- scripts/backfill_current_combo_from_dim.py: 回填脚本(--allow-real-db / --overwrite)
- bms_scheduler: 回补重建 dim 后"只补空"投影(current_combo_name 不再由三写入方各写各的)
- CSV 导入仍覆盖 current 的冲突: 经确认**后续仲裁**, 本次不动
2026-10-14 11:39:46 +08:00
95e9ff622c
#322 C2 门禁落地: analytics↔ods 隔离棘轮检查器 + CI job
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / analytics-ods-isolation (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
- scripts/check_analytics_ods_isolation.py: 检 ods_prefix/sql_table/orm_model 三类违规,
与基线棘轮比对(新增违规阻断 CI, 减少则提示收敛)
- scripts/analytics_ods_isolation_baseline.txt: 80 条存量基线(orm_model 64 + sql_table 16)
- .gitea/workflows/test.yml: 新增 analytics-ods-isolation job(纯 python3, 不依赖 docker)
2026-10-14 11:06:10 +08:00
11dffa14c7
#320 C4登记: dim_device_combo 唯一键+口径说明入数据库表结构说明 §四
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
2026-10-14 10:23:54 +08:00
52938da587
#320 前端ⓘ: 补口径拍板说明(钱包退款归SN套餐/赠送包不过滤/#321问题保留)
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
- 退款侧: A路钱包退款归设备时点套餐(钱包=中间态,特征跟SN走); 赠送包命名无规律不过滤
- 投诉侧: 空时回落 current 基准; current 与订单源 37% 不一致问题保留, 排查见 #321
2026-10-14 10:17:57 +08:00
8a3d12ed18
#320 fix: 过滤 evt_time NULL 行(create_date/created_at_raw)
gitleaks / gitleaks (push) Failing after 3h5m28s
backend-test / commit-message-lint (push) Failing after 3h5m28s
backend-test / backend-lint-test (push) Failing after 3h5m28s
backend-test / frontend-lint-test (push) Has been cancelled
2026-10-14 00:58:31 +08:00
f12f5dc510
#320 fix: 去重 (device_sn, evt_time) 撞唯一键(bms_order 优先于 device_combo)
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
2026-10-14 00:52:23 +08:00
9d37acba72
#320 P1 套餐维度样板: dim_device_combo SCD2 干净维度 + 退款/投诉两侧同读
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
- 新表 dim_device_combo(SCD Type 2): bms_orders + device_combos 两路事件合并去工厂质检,
切出每台设备套餐变化区间, valid_to=NULL/is_current=true 标记当前生效
- 构建服务 rebuild_dim_device_combo 纯 SQL(TRUNCATE + INSERT...SELECT 窗口函数),
回填脚本 backfill_dim_device_combo; 调度器在 device_combo 夜间回补完成后自动刷新
- 退款 by_combo / 投诉 by_package 改读 dim_device_combo 时点真身
(事件时点 ∈ [valid_from, valid_to)), 回落退款记录 package_name → 当前套餐;
新增 lookup_point_in_time_combo 共享助手
- 对账脚本 diff_dim_combo 抽样比对新旧口径差异
- 前端 ⓘ 设计说明同步更新套餐真身位置
2026-10-14 00:45:08 +08:00
b3a35e94be
#319 数据分层: 21 张信源表移入 ods schema(M1 建 schema/M2 移表/M3 autogen 适配)
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
背景: 86 张表混居 public, 其中 21 张为 BMS/SCRM/企微等信源浅层拷贝, 与系统
业务表(强逻辑/强约束)边界不清。按数据中台思路分层: public=系统业务库,
ods=信源直连层, 为数据治理与业务分析划清边界。
改动:
1. M1(d6e7f8a9b0c1) CREATE SCHEMA ods; M2(e7f8a9b0c1d2) ALTER TABLE ×21
SET SCHEMA ods(目录级元数据操作, 不搬数据, 索引/约束/序列随表迁移)。
2. 21 个模型 __table_args__ 加 {"schema": "ods"}; 3 处跨 schema FK 显式加
ods. 前缀(scrm_session_transfers/complaint_same_source_members/reconcile_matches)。
3. 裸 SQL 兼容方案调整(替代工单正文的"逐处补 ods. 前缀"): 实查裸 SQL 300+
处(refund.py 单文件 60+), 逐处改写风险高噪音大, 改为连接级
SET search_path TO public,ods(database.py connect 事件, 必须 commit——
连接池 reset_on_return=rollback 会撤销未提交的 SET); public 在前,
系统表解析行为不变, ods 兜底, 42P01 风险从根上消除。
4. information_schema 查询 11 处 table_schema='public' 改 IN ('public','ods')
(采集 _runner ×2 / governance_detector ×7 / governance_execute ×2)。
5. alembic env.py 两处 configure 加 include_schemas=True, 防 autogen 把
ods 表误判为待新建。
6. 补登记 ShanmaoRefund/LlmCloudPolicy 到 models/__init__(存量缺失,
autogen 一直将这两表当孤儿要 drop)。
7. tests/conftest: 独立 engine 同步 search_path(带 commit); create_all 前
CREATE SCHEMA + 清理 public 残留旧表及 2 张带跨 schema FK 的依赖表。
验证(dev 库 csctalon_dev):
- upgrade/downgrade 往返零报错; 21 表迁移前后 count(*)+金额列 sum 零差异
(对账脚本 workdata/issue319_snapshot.py, 一次性现场不入库)。
- autogen 检查: 无因本次移动引入的新 op(剩余 alter_column/索引抖动为
存量基线漂移, public 表同等存在, 与 ods 无关)。
- pytest 全量: 2464 通过 / 5 失败, 5 个均为存量问题(3 个 #293 口径漂移
+ 2 个 llm 模型名配置漂移), 与本改动无关; ruff 本次改动文件全净。
2026-10-13 23:13:01 +08:00
204b9d6399
#318 修正 extended-dims 套餐口径 stale docstring(方案0)
...
gitleaks / gitleaks (push) Failing after 3h13m19s
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
调研实证: by_package(complaint_extended_dims) 与 category_by_package 并非
「同一投诉页面两维度」——category_by_package 在 Category.vue(退款分类页),
by_package 经 /complaints/extended-dims 暴露但前端无任何消费
(ComplaintAnalysis.vue 明确「取舍=不重复展示 extended-dims」)。
两者测不同域(退款金额 vs 投诉单数)不同页, 用户侧无桶名冲突,
故不动统计口径(避免方案A对退款分类页的回归风险), 仅修文档漂移:
- analytics/complaint.py complaint_extended_dims docstring:
套餐口径仍写「combo_name_alias 归一」, 但 #313 已改 _device_dim_attrs
三源回落 + 工厂质检过滤 → 同步为现行口径
- complaints/_extended_dims.py 头部设计说明口径段同步
2026-10-13 18:20:40 +08:00
b8f7487b35
#316 修复 refund_group_comparison 金额计算 float+Decimal 报错
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
预热实测发现: refund:gc (refund_group_comparison) 函数内部
refund_op_amount 初始化 0.0 (float) 与 sd["refund_op_amount"] (Decimal,
line 395 round 对 Decimal 返回 Decimal) 相加报错
「unsupported operand type(s) for +=: 'float' and 'decimal.Decimal'」,
违反铁律 4「金额字段一律使用定点数 Decimal」。
修复:
- refund_op_amount 初始化 0.0 → Decimal("0")
- est_amt (估算挽回金额) 初始化 0.0 → Decimal("0")
- 出参 round(d["refund_op_amount"], 2) / round(est_amt, 2) 保持
(round 对 Decimal 返回 Decimal, FastAPI jsonable_encoder 自动转 float)
此 bug 不只影响预热, 也影响路由层 /api/v1/analytics/refund/group-comparison
接口本身(用户访问会 500)。
2026-10-13 17:47:11 +08:00
0d2edef5c0
#316 缓存预热扩展: 接待/退款深度看板纳入 warm_idle 节流预热
...
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
- 新增 _warm_reception: 预热接待看板 4 接口(overview/catsum/percapita/group)
默认近 90 天, group-detail 用近 7 天(与前端 ReceptionDashboard.vue:710 约定对齐)
- 新增 _warm_refund_deep: 预热退款深度分析 8 接口(do/gc/pa/prm/ptd/tt/da/pt)
覆盖 RefundDashboard 月报场景默认入口(近 30 天)
- warm_idle 调用上述两个新函数, 节流模式 TTL≤60 才重算
- key 与 analytics.py 路由层逐字符对齐(缓存规范 §5.2 铁律)
- 时间语义已确认无 8h 偏移: service 内部统一走 _date_range(_core.py:191),
naive 入参按北京时间解释转 UTC aware, 不存在 SCRM naive 当 UTC 风险
2026-10-13 17:30:12 +08:00
6fa3afcfca
#317 combo_backfill end_date 由前天改为昨天, 兜底 06:00 低频档失败缺口
...
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
原 end_date = n.date() - 2 (前天), 昨天整天归 06:00 低频档; 若低频档失败/熔断
昨天缺口无人兜底. 改为 n.date() - 1 (昨天), 与 06:00 低频档重叠但 upsert 主键
recharge_order_no 幂等不冲突, 不影响 01:00-04:45 夜间窗口硬停止.
2026-10-13 17:23:43 +08:00
499e0252c6
#314 手动上传入口补齐缓存失效 + 缓存规范文档对齐
...
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
- uploads.confirm_import: inserted+updated>0 时按 data_type 查 _CACHE_PATTERNS_BY_TYPE
推断受影响前缀(退款→refund+today; 投诉→complaint+today; 设备→两侧+today; 其他→today),
调 cache_invalidate_recent_days(pat) 与 sync_task_manager finally 同口径; 失败静默
- 缓存规范 §6.1 finally pattern: 补 reception:* (3→4, 与代码逐字符对齐)
- §九 G2 标记已修复; G3 标记部分修复(已失效, 未预热→#316); 覆盖矩阵/缺口表/关联文件同步
2026-10-13 17:22:38 +08:00
e644ccf4f4
#315 docs file:// 路径前缀批量修正: /Users/tanzk/dev/csctalon → /Users/tanzk/dev-tmp/csctalon
...
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
docs/ 下 14 个文档(缓存规范/规划设计/arch 架构决策/安全处置/审计报告)内
file:// 链接路径前缀漂移, 导致导航断链. 纯文档改动, 不影响容器镜像, 无需重建.
2026-10-13 17:16:32 +08:00
2e5ae8d11a
#313 投诉侧 by_package 套餐口径统一到退款侧 _device_dim_attrs 三源回落
...
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
投诉侧 by_package 原走 devices.current_combo_name 单源 + combo_name_alias 归一,
与退款侧 by_combo 的 _device_dim_attrs 三源回落(最新非质检订单→devices→
device_combos, 工厂质检过滤)不一致。统一方向: 投诉侧向退款侧看齐。
改动:
- complaint_extended_dims 的 by_package 改为调用 _device_dim_attrs 获取 combo
(三源回落 + 工厂质检过滤, 与退款侧 a["combo"] 同源)
- 去掉 combo_name_alias → combo_dict 归一(与退款侧对齐)
- category_by_package / package_alias_coverage 不受影响(独立用 _package_canonical_sq)
- 测试 test_dims_package_three_source_and_qc_filter 覆盖三源回落 + 质检过滤 + 无归一
退款侧 #312 已发布, 不改动。
2026-10-13 16:14:55 +08:00
a1e70e4386
#312 by_combo 口径扩展: 最新非质检订单 + device_combos 回落链
...
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
修复1: combo 改取 bms_orders 最新非质检订单(跳过质检单)——
设备退款/提现后回厂质检, 质检单 create_date 最新会把历史正常套餐掩埋,
90天实测 480 SN 因此白归 (未填); type_name 仍取最新订单不变。
修复2: 回落链接 device_combos.package_name(created_at_raw DESC 最新非质检)——
bms_orders 无订单但充值流水有套餐的设备, 90天实测 256 SN 白归 (未填)。
回落链: 最新非质检订单 → devices.current_combo_name → device_combos 最新,
三源统一过滤工厂质检。退款/投诉两侧共用 _device_dim_attrs, 单点修复双受益。
2026-10-13 15:52:29 +08:00
aab1d9ba7e
#311 回填脚本防撞去重键: 撞 uq_manual_refunds_dedup 行分流 dup_residual
...
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
生产首跑发现: 同订单存在"带SN重复登记行", 空SN行回填后与既有行完全重复,
触发 uq_manual_refunds_dedup 唯一约束(事务已回滚, 库无损)。
修复: 命中后先预检撞键, 撞键行打标 dup_residual(STATS_EXCLUDED, 不进统计);
UPDATE 带 NOT EXISTS 双保险防批次内互撞。
生产实测: 安全回填 769 / dup_residual 1 / sn_unresolvable 103, 自检零遗留。
2026-10-13 15:20:51 +08:00
fdccf5d084
#311 决策概览套餐/激活桶口径修正 + C路单列 + SN回填机制
...
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
方案A: by_combo 改用 refunds.package_name(退款时真实套餐, 非最新订单套餐)
方案B: D路 manual_refunds SN 回填机制(_resolve_device_sn_by_order 跨表反查) + 历史回填脚本
方案C: C路闪猫退款独立单列(c_route_summary, 不混入A+B+D汇总)
方案D: sale_days_split 拆出 (无SN) 单独桶(SN缺失不再混入无激活记录)
2026-10-13 14:51:50 +08:00
b46424cde6
#310 退款决策概览90天窗口超时修复: axios 默认 60s → 单独 180s
...
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
90天窗口后端实测 65s > 前端 axios 默认 60s, 前端超时报"加载决策概览失败"。
nginx proxy_read_timeout=900s 不会先断, 卡在前端 axios。
给 decisionOverview 调用单独设 180s timeout(同 refundExport/staffAssignment 既有模式)。
2026-10-13 12:35:13 +08:00
35c432ff2b
#309 今日看板充值卡 + 退款分析窗口充值对照与退款/充值占比
...
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
- today_kpi 新增 recharge 块: device_combos 窗口 created_at_raw 落窗,
剔除工厂质检套餐流水(%质检%, 与 #293 同判据), 金额/笔数/上一等长窗口环比
- refund_decision_overview summary 新增 recharge_amount/count 与
refund_recharge_ratio(四路经营合计÷窗口充值, 分母 0 → null)
- 今日看板 KPI 条加「充值金额」卡(副行笔数+环比, 下钻充值流量页带窗口);
RechargeTraffic 承接 start/end query; 决策概览加「窗口充值」卡
- 新增 test_recharge_kpi_309.py 3 用例(剔质检/环比/除零)
2026-10-13 05:54:45 +08:00
47a726d1ff
#308 区间问句窗口贴合度门槛: 生产实测 Q2 补刀(近30天命中7天周快照回显错位)
...
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
- _SQL_RANGE_FIT: 区间分支加内容窗口贴合度(交集/并集天数)>=0.5 硬过滤, L1/L2 两处注入
- 本月12天vs月快照11天=0.92保留; 近30天30天vs周快照7天=0.23排除交实时链
- 新增2用例: 低贴合排除/高贴合命中; SQL断言补贴合度表达式
2026-10-12 22:49:58 +08:00
05eccf14f2
#308 匹配层候选池硬过滤: 停用模板快照排除 + 单日内容窗口对齐(今日问句命中 rolling_30d 快照事故)
...
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
- _SQL_TMPL_ACTIVE: 五个候选分支排除 is_active=false 模板的存量快照(rolling_7d/30d_compare #306 已停用)
- _SQL_SINGLE_DAY_FIT: 单日分支要求内容窗口恰为该日(start=end=sdate)或 NULL 窗口历史快照, 防窗口回显「今日」而数字是多日口径
- 新增 5 用例: 单日排除多日快照/停用模板 L1+L2 排除/区间回归/NULL 窗口兼容/SQL 注入断言
2026-10-12 22:31:07 +08:00
edcb74866e
#307 v4 铁律 3 强化: 锚点必须 ## 两井号开头, 禁 ### 变体(对比实测发现全用三级标题会令摘取层失配)
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
2026-10-12 21:14:11 +08:00
c066dd1356
#307 gen_system_live v2: 禁两绝对值自算百分比, null 只报绝对值
...
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
生产实测发现 v1 下模型在 per-source 场景绕过引用, 用绝对值自算环比
(62.5%/把基期 0 自算成上升 100%)。v2 铁律 1 明确禁止; 代码常量同步;
迁移补 v1 留痕 + v2 激活逻辑(幂等, 已 stamp 库由发布后 SQL 等价补齐)。
2026-10-12 21:06:47 +08:00
56a2f8f2cb
#307 发布实测补刀: complaint 侧登记/处理 verdict 预计算(处理上升=利好)
...
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
生产实测发现模型对无 verdict 的处理量自判「上升(利空)」——判反且比率系自算。
补 complaint.total 与 by_source 的 registered/handled 前值+prev_pct+verdict
(登记少=好, 处理多=好), 模型只引用不判定; v4 铁律 12a 字段清单同步。
迁移同步修正: ai-incremental-analysis 用完整原文等值定位(片段导致漏更)。
2026-10-12 20:59:14 +08:00
7ce5a63d6d
#307 AI 问答质量治理: 提示词双版拆分 + feed 预计算 + focus 卡修复
...
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
- ai_feed: 实时版 _SYSTEM_PROMPT_LIVE(只答所问/verdict 引用/禁系统话术/≤300字);
build_messages 加 call_mode(live 缺省/pregen), live 裁 routes_prev;
verdict 预计算(退款少=好/接待多=好/涉诉率降=好), complaint_breakdown 补前值,
窗口时间格式化到分钟
- ai_report_worker: 预生成显式 call_mode=pregen(保留 H2 五锚点全景)
- prompt_config_service: PROMPT_CODES 加 gen_system_live
- 迁移 c5d6e7f8a9b0: gen_system v4 seed(不激活, 金丝雀后再激活) +
gen_system_live v1(激活) + focus-refund/focus-global/ai-incremental-analysis 修复
- 测试: 新增 11 用例, 更新 2 个行为变更断言, AI 链路 91 用例全过
2026-10-12 20:47:09 +08:00
bf44b5f784
#299 模型注册表 import 顺序修正: ComboBackfillSlice 移至字母序正确位置
2026-10-12 20:47:08 +08:00
a897d65043
#306 AI 预生成收敛: rolling 停用/5 模板切 120b/周月报触发日漏跑当补
...
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
生产 26 天命中率审计: daily 6.9%/2.2%/1.1% 已随 #236 改实时; rolling_7d
3.7%、rolling_30d 0% 停用; weekly 31%/monthly 25% 保留但旧调度仅 ±1 分钟
容差, 发布重启错过周一/1号 02:00 即缺报(10-09~10-11 连续三天未跑成)。
- worker: weekly/monthly 到点判定放宽为「触发日当天时刻已达」, 新增
_has_public_snapshot_today 当日快照幂等去重, warm_idle/warm_on_startup
同口径 —— 错过 02:00 当天自动补跑, 成功后永不重算; 跨触发日不补
(compute_pregen_window 仅触发日当天才是完整上周/上月窗口)
- data migration a4b5c6d7e8f0(可重入/可回滚): 模板 1/2/3/7/8 provider
冷备 27b → 热驻 120b(消除 ~80s 冷加载及与 coder-30b 抢显存);
rolling_7d/30d is_active=false(历史报告保留, 问句走实时 LLM 兜底)
- settings: AI_PREGEN_SKIP_MODE 默认 dry_run → off(作用对象已消失,
三档代码保留, .env 可回开)
- 测试: 新增 9 例覆盖补跑/未到点/跨日/幂等去重/启动补跑, 138 项相关测试全过
2026-10-12 19:42:13 +08:00
fb323a648b
#300 logrotate 补 su root root(本机 /var/log 组写权限策略下否则跳过轮转)
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
2026-10-12 19:10:14 +08:00
1b6579946a
#300 巡检模块迁至 app/ops: scripts/ 被发布流程清理不随镜像
...
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
上一版放 backend/scripts/ 在生产容器 /app/scripts 为空(--git 模式按
docker-compose.yml 纯净性注释清理 scripts 杂质)。改为 app/ops/llm_drift_patrol.py,
`python -m app.ops.llm_drift_patrol` 随镜像发布; 容器内天然 prod 环境,
只读 SELECT + 免密 GET, 去掉 --allow-real-db。cron/README 同步修正。
2026-10-12 19:04:37 +08:00
08950dc37b
#300 LLM 档位链漂移巡检: 日比对 talon /v1/public/overview × llm_role_members
...
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
2026-10-12 talon 换防暴露三类漂移(122b 下线链未摘/9b structured fail 在
assistant/链成员 provider 被禁), 全靠人工拉 /v1/models 才发现。落每日
10:20 cron 巡检(只读): E1 链成员缺失禁用 · E2 模型不在 talon 清单(下线) ·
E3 档位必需实测能力 fail · E4 MoE coder 进涉数值档(与 model_selection_guard
同锁); W1 冷备/不健康 · W2 非必需能力 fail · W3 无实测 · W4 code/model 名实
不符。退出码 0/1/2, 含 --json; 判断纯函数化并配 7 例单测(含 122b/9b 真实场景)。
cron 与 logrotate 配置版本化于 deploy/, README 补安装小节。
2026-10-12 18:55:37 +08:00
127a0fe2cd
#300 档位常量与 talon 值班实测对齐: 122b 下线 → 14b 补位, assistant 恢复 coder-30b
...
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
依据 talon /v1/public/overview(2026-10-12, 免密聚合值班+14项实测):
- 122b 已从 talon 彻底下线: specialist/expert 常量备选改 qwen2.5:14b
(Dense, 14/14 全过含 en_math/structured_output, 热驻 mac-mini 零冷启动)
- governance 备选 7b→14b: 7b reasoning_multistep=fail 且语义匹配 5/5 全错
- assistant 常量本就是 coder-30b→7b(DB 曾漂移为 7b 首选, 已在 DB 侧改回),
9b 因 structured_output=fail 移出 assistant 链
- 守卫测试锁定目标由 122b 更新为 14b, NUMERIC_UNSAFE 禁 coder 进涉数值档不变
- 能力矩阵表补 14b/9b 实测行并标注 122b 下线
DB 侧 llm_providers/llm_role_members 已先行调整(无需发版), 本提交消除
DB 无角色配置时源码常量回退到已下线模型的隐患。
2026-10-12 18:47:46 +08:00
cf07aaa6b3
#305 时间填值第三级策略: Vue \$emit 直改 v-model(设备档案页键盘输入不认账)
...
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
三策略对抗探针实证: 设备档案页激活时间编辑器对键盘输入(Enter/失焦)完全
回退默认值, Vue 层 \$emit('input',[d1,d2]) 100% 粘住。_fill_daterange_inputs
改为三级: 键盘输入(其它页面已验证路径) → Vue \$emit(本页兜底) → 响亮抛错。
2026-10-12 15:07:12 +08:00
56d6ea4e7b
#305 修 datetimerange end 侧填值不提交: labels 路径改键盘逐字输入 + DOM 回读强校验
...
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
探针实证: 设备档案页 fill() 提交了 start 但 end 失焦被 Element 重置回页面
加载时刻 → bindEndTime 恒为 now, 查询窗口 [start,now] 达 176 万台, 导出
10 万行全是默认窗口。_fill_daterange_inputs 改用 #299 已实证的
click→Ctrl+A→Backspace→键盘逐字输入→真实鼠标点空白失焦机制, 加 DOM
回读校验(按日期部分匹配, 兼容 daterange), 不符重试一次, 仍失败响亮抛错
(宁可切片 failed 重试, 不带错窗口静默查错数据)。
2026-10-12 14:54:48 +08:00
de129c9c0e
#305 device_profile 时间过滤修复: #299「关面板→真点击」序列泛化到设备档案页
...
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
实证: 设备档案切片导出 10 万行全是 2026-08~10 默认窗口(切片窗口本应
2021-09~2022-06) — time_labels 路径填值后面板未关, force-click 落在
面板遮罩上, findPageList 不发请求。将 #299 已验证的
_click_search_device_combo 改名为 _click_search_after_panel_close(加 tag
参数), 分派集合扩为 device_combo + device_profile。
2026-10-12 14:37:59 +08:00
d0ec457b60
#305 _sync_devices 垃圾 SN 建档防御: 多SN拼接/引号壳/字面占位不建空壳设备档
...
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
实证: 人工登记数据一格多 SN(换行/斜杠/全角逗号)、Excel 引号前缀、字面 x
照单建档 → 1.7 万+ 四无空壳档。规则: 去首尾引号壳后判垃圾(空白/斜杠/引号/
全角标点/垃圾字面), 垃圾 SN 跳过建档, 业务行本身导入不受影响; 引号壳内合法
SN 正常落地。拦截量打 warning 留痕。
2026-10-12 14:21:05 +08:00
284b5d3131
Merge branch 'main' of http://10.147.17.41:3000/tanzk/csctalon
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
2026-10-12 14:00:20 +08:00
fdc9fe6d5b
#305 设备建档回填激活时间: device_combo 导入时以批内最早套餐开始时间填 devices.active_time
...
根因: _sync_devices 建档不带 active_time, 生产 68.6 万设备(52%)激活时间为 NULL。
- _core: fill 值非字符串保留原类型(datetime 直入 timestamptz), 字符串路径不变
- _masters: 注入批内该 SN min(start_date) 作激活时间, 仅填 NULL 不覆盖
(BMS 设备档案导入的权威激活时间优先); 注入键不进套餐表
2026-10-12 14:00:04 +08:00
91f62b8f2e
fix( #238 ): D9 段落块规则补充——标题章节子锚点行不截断 ( #305 )
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
2026-10-12 12:22:52 +08:00
86261a08cd
fix( #238 ): D9 段落块规则补充 —— 标题章节的子锚点行不截断块扫描
...
gitleaks / gitleaks (pull_request) Has been cancelled
issue-gate / gate (pull_request) Has been cancelled
backend-test / commit-message-lint (pull_request) Has been cancelled
backend-test / backend-lint-test (pull_request) Has been cancelled
backend-test / frontend-lint-test (pull_request) Has been cancelled
issue-close-on-merge / close-issue (pull_request) Has been cancelled
复测取证(27b t3): `### 趋势对比` 标题锚点后紧跟子锚点行(`**接待量**:每日
趋势如下:`)+表格, 旧规则遇子锚点行即停 → 块=0 行误判趋势缺失。实际子锚点
行属于该章节的展开组织形态。
规则: 锚点本身是标题时, 段落块仅遇下一个标题终止(子 `**标签**:` 行继续扫,
其行体与表格数字归属该章节); 粗体锚点行为不变(仍遇下一锚点即停, 防他维
数据误归属)。8 非空行预算不变。
测试: 新增 3 用例(子锚点续扫取证复刻/子锚点无数字仍缺失/遇下一标题终止),
28/28 全过 + b23 复刻 12/12 + ai_feed 冒烟全过(容器内 runner 实测)。
2026-10-12 12:22:32 +08:00
4876bfa7bc
fix( #238 ): D9 闸门 detail 契约修复 ( #304 )
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
2026-10-12 11:51:32 +08:00
402f625875
fix( #238 ): D9 闸门 detail 契约修复 —— 校验器认标题锚点+段落块数据检查, 预生成 prompt 末尾输出契约
...
issue-gate / gate (pull_request) Successful in 0s
gitleaks / gitleaks (pull_request) Failing after 3h1m39s
backend-test / backend-lint-test (pull_request) Has been cancelled
backend-test / frontend-lint-test (pull_request) Has been cancelled
issue-close-on-merge / close-issue (pull_request) Has been cancelled
backend-test / commit-message-lint (pull_request) Failing after 3h1m39s
根因: system 铁律 3 "H2 锚点结构"把"锚点"指向标题, detail 体裁下双模型
(120b/27b)稳定输出标题式结构(数据在标题行内或紧随其后), 校验器只认
`**标签**:` 粗体锚点 → rolling_7d/30d 断供 5 天, 三轮实验 0/4~0/8 复现。
三处修复:
1. ai_completeness_service: 锚点行扩展两类形态(粗体=标签严格匹配;
标题 #{2,6} 整行匹配), 空锚点行体/缺失标记时做段落块检查(向后最多
8 非空行, 遇标题/下一锚点即停, 任一行含数字→覆盖); 粗体锚点保持
标签严格匹配防旁述误判。fail-open 不变。对 20 份存档输出评估
0/20→1/20 显式通过, 残缺维度收敛为随机遵从缺口。
2. ai_feed.build_messages: 末尾追加【输出契约】(仅预生成链路), 锚点行
纪律收尾吃近因效应(中段注入实测 0/8 被淹没); 示例全部 <数据> 占位
防数字幻觉注入; 同义词数组渲染成"甲(或写作乙)"形态。
3. 测试: 删旧空锚点用例(语义反转), 新增 D9 用例 14 个, 25/25 全过;
容器内 runner 实测 ACS 25/25 + b23 复刻 12/12 + ai_feed 冒烟全过。
2026-10-12 11:50:55 +08:00
27ee142299
Merge origin/main(#302/#303) into prod-local-merge: 拉取档位回退与122b退出值班
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
2026-10-12 11:41:45 +08:00
9f7cfc2d49
Merge prod-local into main: 恢复 127 个被 2026-10-12 06:02 main 发布覆盖的提交(含 #295/#296/#297/#294/#291/#205/#67 等)
2026-10-12 11:40:45 +08:00
c134c0aae5
revert( #300 ): 回退档位参数改动 ( #303 )
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
2026-10-12 08:19:42 +08:00
9447b69248
feat( #300 ): 122b 退出值班 ( #302 )
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
2026-10-12 08:08:49 +08:00
44c0a77092
#299 fix: ensure_slice_rows 误在 to_thread 外提前调用(冒烟 TypeError: int not callable)
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
2026-10-12 07:35:22 +08:00
8729d23e0e
#299 fix: 缺口查询 generate_series 显式 ::date(冒烟实测返回 timestamp 导致切片规划 TypeError)
2026-10-12 07:35:22 +08:00
671260ff4e
#299 device_combo 历史切片自动回补:夜间切片导出 + 断点台账
...
- fetcher 修复(探针实证真根因): device_combo 页 el-picker-panel 拦截
force-click, renewRecord/findPageList 从不带时间参, 每日导出长期是
BMS 默认近7天 → 新增专用序列: 真实 ESC 关面板 → 原生点击搜索,
面板仍拦截时降级 DOM click(实测 2024-01-01~07 导出 679 行 0 越界)
- combo_backfill 服务: 按缺失北京业务日自适应切片(2024 30d/2025上 21d/
2025下 14d/2026 7d), BMS 10 万行硬上限触顶自动折半(split), 片间
65s 限流, 04:45 硬停止, 连续3片失败当夜休眠, 复用 _auto_import
upsert 幂等通道, 无缺口夜 no-op(兼作漏采自愈)
- 新表 combo_backfill_slices(Alembic f299combo): 切片断点台账,
pending/running/success/empty/failed/split 状态机, 分夜续跑
- bms_scheduler: 01:00 后台 task 触发(不阻塞主 tick), 与
sync_task_manager 互斥, 让开 05:00 补跑/06:00 低频/08:00 高频
- admin API: GET/POST /system/combo-backfill/status|run (AdminUser),
白天手动执行需显式 allow_daytime
- tests: 17 个纯函数单测(片宽分段/不重不漏/折半/夜间窗口/触顶阈值)
2026-10-12 07:35:22 +08:00
ee2ede32df
feat( #300 ): expert 档值班切换 gpt-oss:120b ( #301 )
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
2026-10-12 06:02:21 +08:00
518ddac614
#299 fix: ensure_slice_rows 误在 to_thread 外提前调用(冒烟 TypeError: int not callable)
2026-10-12 00:13:14 +08:00
2b47f2966d
#299 fix: 缺口查询 generate_series 显式 ::date(冒烟实测返回 timestamp 导致切片规划 TypeError)
2026-10-12 00:09:36 +08:00
c3a4dd83e9
#299 device_combo 历史切片自动回补:夜间切片导出 + 断点台账
...
- fetcher 修复(探针实证真根因): device_combo 页 el-picker-panel 拦截
force-click, renewRecord/findPageList 从不带时间参, 每日导出长期是
BMS 默认近7天 → 新增专用序列: 真实 ESC 关面板 → 原生点击搜索,
面板仍拦截时降级 DOM click(实测 2024-01-01~07 导出 679 行 0 越界)
- combo_backfill 服务: 按缺失北京业务日自适应切片(2024 30d/2025上 21d/
2025下 14d/2026 7d), BMS 10 万行硬上限触顶自动折半(split), 片间
65s 限流, 04:45 硬停止, 连续3片失败当夜休眠, 复用 _auto_import
upsert 幂等通道, 无缺口夜 no-op(兼作漏采自愈)
- 新表 combo_backfill_slices(Alembic f299combo): 切片断点台账,
pending/running/success/empty/failed/split 状态机, 分夜续跑
- bms_scheduler: 01:00 后台 task 触发(不阻塞主 tick), 与
sync_task_manager 互斥, 让开 05:00 补跑/06:00 低频/08:00 高频
- admin API: GET/POST /system/combo-backfill/status|run (AdminUser),
白天手动执行需显式 allow_daytime
- tests: 17 个纯函数单测(片宽分段/不重不漏/折半/夜间窗口/触顶阈值)
2026-10-12 00:04:55 +08:00
001e59d123
#297 投诉设备维度汇总 tooltip 补单数占比, 对齐退款侧详细度
...
积压页/设备维度tab 共 8 张条形(型号/套餐/店铺/激活时长)原 tooltip
仅 echarts 默认「名称: 值」, 无占比无口径。统一显示
「单数 n 件 + 单数占比 x%(分子/分母)」; Top8 截断时分母仍为该维度
全量件数; 激活时长六桶占比合计 100%。投诉无金额维度(#252 取舍),
显式标「单数占比」避免 #296 式歧义。抽 _ageBarOption/_dimBarTooltip
消除三处激活时长 option 重复; 两处 ⓘ 设计说明同步口径与取舍。
纯展示层, 后端契约不变。
2026-10-11 12:00:18 +08:00
2b0c82f8e7
#296 激活时长条形 tooltip 补双占比口径(单数占比+金额占比)
...
原 tooltip 仅「占比」未标口径且紧邻金额, 被误读为金额占比
(6.1-9.30 实测 0-15桶 单数占比30.7% vs 金额占比34.9%)。
现并列显示单数占比(笔数/总笔数)与金额占比(桶金额/总金额),
副标题与 ⓘ 标明条形按单数; 仅展示层修改, 后端不变。
2026-10-11 11:33:35 +08:00
a127c225d9
#295 激活时长统计桶改五桶(0-15/16-30/31-180/181-365/>365), 退款/投诉统一同源
...
- _core.ACTIVE_AGE_BUCKETS 改五桶+(无激活记录), 切点=上界(30/180/365 归左桶);
active_age 参数改 le15/16-30/31-180/181-365/gt365/none
- 退款 sale_days_split 收口到同一常量/函数(原 #251 六桶), 投诉 extended-dims
SQL CASE 同步; 3 处 API pattern 更新
- 前端退款区块六/投诉积压四维/设备维度tab 图映射与明细下拉改新枚举,
页面ⓘ与设计说明同步(含锚点差异致下钻近似的取舍说明)
- 测试: 新增切点纯函数用例, 更新 sale_days_split/active_age/extended_dims 断言
2026-10-11 11:10:52 +08:00
606f7a73c7
#294 新增采集健康对账服务, 打通两条补数通道的事件自动收口
...
新增 ingest_health_reconcile.recover_arrival_alerts: 把未恢复的
data_arrival_* 告警按源/业务日收口为 recovered, 并精确平息同源采集
Incident(同时尝试告警键 data_arrival_<dt> 与熔断键 <dt>)。
只处理 pending/confirmed 且未 recovered 的系统自动登记行, 人工闭环
不动; 只 flush 不 commit。
接线两个补数成功入口:
- bms SyncTask 增量补跑: 按补跑窗口覆盖的上海业务日收口(补了哪天
关哪天), 独立 session 提交; 替换原仅平息 Incident 的窗口枚举实现,
现在 Alert 也一并恢复。
- shared_doc 全量快照同步: success/partial 后按通道语义收口该源全部
历史业务日滞留告警(快照成功=文档全量已入库), 排在 _record_status
前借其 commit 提交, 覆盖跨天补数落次日到达日志、容器重启孤儿场景。
测试: test_ingest_incident_294.py 扩到 5 项(窗口双键收口/快照全期收口/
confirmed 收口/独立 session 集成/精确匹配防子串误关联), 全过;
shared_doc、bms 熔断/调度、data_arrival 健康等回归 115 项通过。
2026-10-11 10:17:53 +08:00
943ff6d0df
#294 今日值班看板排除采集运维事件 + 补跑成功按窗口自动平息事件
...
修复A: today_queue 非周期/周期两个事件查询均加
category IS DISTINCT FROM '数据采集' 过滤, 与时序轴 markers/timeline
口径对齐。数据采集告警属系统运维事件, 不再进业务看板横幅/事件区块
(台账仍完整可见)。
修复B: 同步任务成功收尾(success/partial)时, 对每个成功源按本次补跑
窗口覆盖的上海自然日(前后各扩1天兼容4点切业务日), 分别以熔断键
<data_type> 与告警键 data_arrival_<data_type> 精确匹配平息同源采集
Incident。覆盖 reset_halt 只认当前 biz_key、_halted_types 容器重启
丢失导致落库事件成为孤儿的场景。close_ingest_incidents 新增
exact_title 参数, 避免 refund 误关联 shanmao_refund 等子串源。
测试: 新增 test_ingest_incident_294.py 4 项(看板过滤/窗口平息/
空操作/精确匹配), 全部通过; #203 等相关回归 24 通过。
2026-10-11 09:59:06 +08:00
7f92bd383b
#205 退款业务看板移除两条E同步滞后文本提示(业务逻辑不承载运维逻辑)
...
- 后端: 管理摘要结论行2 删除「E数据截止X, 截止日后对账仅供参考」后缀, 只留业务读数
- 前端: 资金流出全景删除「当前数据更新至X, 请补充采集/导入」Alert
- 保留: 商户号实际现金流KPI卡副行「数据截止X」作为数字解读标注(看板卡片规范的"空"含义)
- 同步/采集问题引导至数据采集页与对账核查tab(核查页的同类提示保留)
2026-10-10 23:32:16 +08:00
50c900b377
#291 修复点击饼图「其他」扇区导致退款明细清单变空
...
「其他」是 Top5+其他 聚合的长尾合成桶, 无真实筛选值; 点击后把字面量"其他"
透传为 店铺/套餐 筛选 → 精确匹配 0 条, 明细清单呈现"空了"的假象。
修复: 「其他」桶禁止下钻, 消息提示引导; 与既有"反查不到类型码不设筛选"同原则。
2026-10-10 21:25:41 +08:00
47ff995ae6
#67 投诉分析tab导航移至顶部 + 设备维度命名与退款侧统一 + 修复退款饼图点击乱跳
...
- 投诉分析两级导航从页底上移至时间条下方(页底需滚屏才能切tab, 不可用)
- 设备维度四维命名全站统一为 设备型号/套餐/店铺/激活时长(以退款区块六为准,
同为 devices 表属性 #271/#273 同源): 投诉新设备维度tab图题/积压汇总图题/
投诉积压筛选/退款明细筛选 placeholder 同步改名, ⓘ口径文案注明激活时长锚点差异
- 退款设备维度桶点击统一为「只设筛选不自动滚动」+ ElMessage 引导
(此前激活时长桶已改, 饼图仍 scrollIntoView 跳页底, 用户反馈乱跳)
2026-10-10 21:00:22 +08:00
bbbafebe84
#67 投诉分析新增「设备维度」独立tab(按渠道可切)
...
- 后端: 新增 /complaints/device-dims 接口, 复用 _device_dim_groups, 支持 source_type(内部/商户号/外部/卡商)过滤
- 前端: 决策概览移除 A6 设备维度汇总, 新增独立「设备维度」二级tab
- 设计: 趋势(时间维度)与SN维度(结构维度)分离, 避免互相干扰; 渠道切换支持归因差异分析
- 口径: source_type=全部 ≡ 旧概览A6(三源合计剔卡商), 保证同源自洽
2026-10-10 20:10:12 +08:00
5961cc721e
fix(#291/#233): 时序图排除系统故障标记 + 激活时长点击不下滑
...
1. (#233 ) 时序图事件标记排除系统层故障(category='数据采集' 的数据采集/同步异常) —
时序轴只标注业务异常与经营事件, 系统告警不干扰业务趋势判断
(incidents.py list_timeline_markers, is_distinct_from 兼容 NULL)。
2. (#291 ) 退款分析区块六: 激活时长桶点击只设筛选不再 scrollToSection ——
原自动滚动会把图表与 tooltip 推出视口, 用户想看数值时页面跳到底部;
型号/套餐/店铺饼图点击仍下钻到明细(点击语义=查构成)。
2026-10-10 19:30:40 +08:00
7efb33c424
fix( #291 ): 设备维度汇总口径升级为经营四路 A+B+C+D 全量
...
用户拍板: 区块六原仅汇总 A 路线上直退, 漏掉钱包提现/闪猫/手动退款的钱, 结构集中度失真。
- 后端 refund_decision_overview: 区块7/8 合并为四路明细行单次收集, 每路沿用
routes_daily 逐字同款筛选条件与归日锚点(COALESCE 链, 已排除重复/忽略与冻结
类流水), 各桶合计与「资金流出全景」四路合计可对账; device_dims 与
sale_days_split(激活时长, #251 真身)同步切换四路口径, 每桶新增 routes 分路明细
- 前端 DecisionOverview: 设计说明/页面ⓘ口径文案同步, 饼图与激活条形 tooltip
增加分路构成行(「其他」桶聚合长尾 routes)
- 测试: 新增 test_decision_overview_device_dims_four_routes(四路齐全+线下/申请中
排除回归+sale 桶四路), 全部 11 项通过; 退款相关 96 项回归通过
2026-10-10 18:06:46 +08:00
c81a426689
Merge origin/main into prod-local: 统一生产分支与主线(#293等)
...
冲突 197(71UU/121AA/5UD),经逐文件比对确认 origin/main 为 prod-local
功能超集(调度5:00/CATCHUP/熔断/半开/低频、防假告警、workbuddy推送、
缓存TTL等全覆盖且更完善),解决策略:
- AA/UUs: 采用 origin/main
- UD: 接受单文件→包重构删除(config/analytics/bms_fetcher/upsert)
- 删除 prod-local 旧 complaints.py(origin/main 已重构为 complaints/ 包)
- 保留 prod-local 独有纯新增文件(scripts探针/docs,不改现有文件)
验证: pytest 2382 passed/3 failed(3个均为 origin/main 存量失败,
非本次merge引入;涉及设备类型归一/投诉heatmap/AI行为档案边缘视图)。
alembic 单一head(e1_renew_probe_at),app import OK。
2026-10-10 17:32:52 +08:00
fb95c91c78
fix( #293 ): 移除设备档案补全调度任务, 根治 BMS 浏览器挂死阻塞后端
...
gitleaks / gitleaks (push) Failing after 3h5m50s
backend-test / commit-message-lint (push) Failing after 3h5m50s
backend-test / backend-lint-test (push) Failing after 3h5m50s
backend-test / frontend-lint-test (push) Failing after 3h5m50s
根因: 4:00 设备档案 SN 补全(run_enrich)与 post-sync 反填钩子均依赖
BMS 浏览器自动化(Playwright), 浏览器登录态失效/页面挂死时
SyncTaskManager 内存态卡 running → 阻塞全部同步调度 → 后端假死。
移除:
1. bms_scheduler: DEVICE_ENRICH_HOUR=4 调度档(29 行)
2. bms_sync: device_combo 同步后 backfill_from_business 反填钩子(18 行)
保留 device_profile_enrich.py 模块本体供手动 CLI 按需使用。
设备维度数据由 devices 表已有存量 + 日批导出维持, 不影响退款/投诉分析。
2026-10-10 16:27:45 +08:00
348f66acba
设备维度汇总改造(方案B): 前三饼图Top5+其他+笔单价tooltip+位置上移 #291
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
按用户 2026-10-10 拍板的方案B 改造退款分析「六、设备维度汇总」:
1. 位置上移: 从原区块五之后上移到「管理摘要」正下方
- 决策顺序: 总量(KPI)→结构集中度→趋势→原因→明细
- 让用户在看完总额后第一眼看到"退款集中在哪"
2. 前三维度改饼图(占比构成语义):
- 设备类型→设备型号 / 商户号→店铺 / 激活时间段→激活时长 (命名调整)
- 按金额倒序排, Top5+其他聚合 (避免>8扇区不可读)
- 扇区 value=金额 (钱集中度而非笔数集中度)
3. 激活时长保条形(有序分桶保顺序语义):
- 饼图会丢失"新→老"顺序洞察 (新机高退=质量/预期差, 老机高退=老化/续费话术)
- 按桶固定顺序: 0-7→8-30→...→无激活记录 (即时长由短到长)
4. tooltip 全维度统一加笔单价:
- {分项名} · 占比% · 金额¥ · 单数笔 · 笔单价¥(金额/单数)
- 笔单价识别"单笔损失大"维度 (笔数少但金额大=高单价设备出问题)
5. 设计说明三层落地(AGENTS 界面设计纪律):
- 代码层: section 头部 Design Rationale 注释块(目的/逻辑/口径/取舍/工单)
- 页面层: block-title 后 ⓘ tooltip 含来源/口径/时点/下钻/取舍
- 工单: #291(维度汇总) + #293(探针补全驱动覆盖)
2026-10-10 14:15:43 +08:00
71d596962a
fix( #293 ): 续费探针独立 renew_probe_at 标记, 修 9.4 万台 miss墙漏扫
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
旧实现与设备列表探针共用 profile_probe_at: miss 设备刷新标记后在排序
中形成「miss墙」挡住后续设备, 首批零填充即误判收敛(94,570 台漏扫);
且 renew 写标记会把设备推出设备列表30天窗口, 两探针耦合。
- migration e1: devices.renew_probe_at
- renew 扫描只取「未续费探过或超30天」缺套餐设备, miss 打 renew 标记
不再挡道, target=0 才是真收敛; hit 只写 renew_probe_at
- 驱动去掉零填充提前停止; 测试去 commit 改 expire 重读, 防污染
2026-10-10 13:50:41 +08:00
949e2c4302
fix( #293 ): 续费套餐选择跳过质检记录, 真实历史套餐不再被挡
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
11.9 万台设备最新续费记录为质检流水, 旧逻辑先选中质检→过滤置空, 其
历史真实套餐(300G/月、八通道200G等)无法落库。apply_renew_items 改为
先过滤质检再选套餐, 「服务过期」真实套餐照取。
- 驱动终止: 整批零填充即收敛(renew扫描无30天排除, 防无限重查)
- 外壳: python exit0=收敛停止, 非0=升级打断重启
- 新增 apply_renew_items 质检跳过回归用例
2026-10-10 13:21:17 +08:00
563aa5283d
fix( #293 ): renew hook 补 SessionLocal 闭包内导入(与 device hook 一致, 防模块级循环依赖)
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
2026-10-10 12:43:01 +08:00
67dda02054
feat( #293 ): 续费记录套餐兜底探针(enrich renew 模式)
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
设备列表的 deviceComboName 只反映「当前有效套餐」——已退款/过期/作废
设备该字段本就为空(实测 14.8 万台已探针但套餐空), 套餐分析仍有大量
「未填」。设备套餐页续费记录接口(renewRecord/findPageList)按 SN 宽窗
可查全历史套餐(套餐名/店铺名/起止日期), 实测 5/5 命中。
- scan_renew_combo_sns: 扫「已探针但套餐空」SN, 近180天有退款优先
- apply_renew_items: 优先非「套餐已作废」最新套餐, 全作废取最新;
质检套餐不落库; merchant←businessName; active_time←最早 startDate
- run_enrich(mode="renew"): 手动驱动, 不挂调度(增量缺口由
device_combo 同步钩子覆盖, 存量按需重跑)
2026-10-10 12:35:31 +08:00
ab8e9fd249
fix( #293 ): 档案回填套餐排除工厂质检专用套餐
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
步骤3 取 device_combos 最新行未排质检 → 最新充值是质检流水时,
质检包名被写进 devices.current_combo_name(实测污染 33,282 台),
退款分析设备维度按质检过滤后这些设备套餐全落「(未填)」。
修复: 套餐子查询单独取「最新非质检行」(质检行回退更早非质检充值),
商户/激活时间仍取全量最新行。存量污染值清洗后重跑回填。
2026-10-10 12:18:17 +08:00
2c6d8369ef
fix( #293 ): 设备维度取数加 devices 回落, 消除退款分析 68%「未填」
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
#292 口径从 bms_orders 取型号/套餐, 但 90天退款 SN 68% 在 bms_orders
无订单(14位SN), 型号(未填)71.85%/无激活记录 68%; #293 反填后 devices
覆盖 93%+, 该视图未读 devices。
- _device_dim_attrs: 型号/套餐 bms_orders 权威优先, 无订单回落
devices.device_type/current_combo_name(与已有 active_date 兜底同模式)
- refund.sale_days_split: 激活锚点加 devices.active_time 兜底
- _device_dim_matched_sns: 类型/套餐筛选命中集扩为两源并集(NOT EXISTS
反连接), 与图表桶一致; 修复非法日期串退化为"有激活即命中"
- 测试更新到 #292/#293 口径 + 新增权威序回归用例
预估效果(90天退款SN): 型号缺失 68%→6.5%, 激活 68%→4.3%, 套餐 85%→40%
2026-10-10 12:11:23 +08:00
68045f58b7
feat( #293 ): 扩展设备档案反填数据源+分步执行
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
- 新增 bms_orders 作为 device_type + active_time 最权威源(覆盖11.5万SN)
- 新增 service_tickets 作为 device_type + active_time 补充源(覆盖9.5万SN)
- CTE 改为分步 UPDATE 避免多表 JOIN 锁等待
- post-sync 触发条件扩展为 device_combo OR ticket 同步成功
生产一次性反填已执行: device_type 35%→84%, 四字段全完整 35.5%→70.4%
2026-10-10 11:46:33 +08:00
e73cb25064
feat( #293 ): 采集后自动反填设备档案缺字段
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
device_combos 同步成功后, post-sync 钩子自动将商户名(店铺名)、
套餐名、激活时间从业务数据反填到 devices 主表(只填空值, 幂等)。
解决 BMS 设备列表接口查不到的14位SN(约45%)档案字段为空影响分析的问题。
一次性反填已生产执行: 160,584行, 四字段全完整率 35.5%→52.6%。
2026-10-10 10:56:31 +08:00
6343208137
#293 档案补全扫描改倒排: 最近活跃设备优先探针
...
gitleaks / gitleaks (push) Waiting to run
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
活跃口径=充值(device_combos.created_at_raw)或完整月流量
(device_flow_usage.stat_month), 与分品类看板一致; ORDER BY 最近活跃
DESC NULLS LAST, 无活动记录老设备殿后。生产实测排序扫描 ~1.5s/批。
目的: 最近活跃的 SN 档案先补全, 管理分析尽早可用。
2026-10-10 07:47:35 +08:00
48259d02dc
#293 档案补全加探针时间戳: miss/无套餐设备不再每批白查
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
生产实测批1 hit 11018 仅 1073 台退出缺失集——永久 miss(BMS 查无 SN,
占45%)与无套餐设备永远满足不了出队条件且 miss 不写标记, 在扫描集堆积,
3~4 批后回填停滞。修复: devices 加 profile_probe_at, hit/miss 均打标,
扫描只看未探针或超30天(留复探窗口); migration 存量已探设备(active_flag
非空)直接打标避免上线重扫 23 万台。
2026-10-10 07:37:27 +08:00
3a6bb767b6
#293 修复分品类环比假降60-85%: 取数函数只过滤下界从未限制end上界
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
- 根因(比limit截断更本质): _fetch_active_devices/_fetch_recharge_by_dim/
_fetch_refund_by_dim/_fetch_complaint_by_dim 全部只 WHERE >=since,
end 参数被完全忽略 → 「上月窗口」实际查到 09-01至今全量(2985万 vs 真实712万)
- 修复: 四路取数全部加 <=end 上界(活跃流量按月口径同补)
- 修复后: 本月充值651.6万 vs 上月同期712.2万 = -8.5%(与原始SQL逐分对齐)
2026-10-10 05:59:51 +08:00
96c34917ae
#293 修复分品类KPI环比异常下降60%: limit截断致前端求和少算长尾
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
- 根因: device_type 回填一夜后桶数 8→84, 而接口 limit=50 截断(按充值额降序),
前端 sum(items) 丢掉 34 个长尾桶 → 本月显示 649万 实际 651万虽近,
但活跃/退款/投诉等被截断影响更大, 与上月对比全线假降 60-85%
- 修复: 接口返回新增 totals 字段(全桶聚合, 不受 limit 影响);
前端 KPI 改用 totals, items 仅用于展示 top N
- 实测对齐: 本月充值 651.6万 vs 上月同期 712.2万 = -8.5%(真实小幅波动)
2026-10-10 05:53:18 +08:00
844d0931e9
#293 分品类运营KPI卡对齐运营总览tab表现方式
...
backend-test / commit-message-lint (push) Waiting to run
gitleaks / gitleaks (push) Failing after 3h13m22s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
- 复用 KpiCard 组件(图标/单位/万元格式/同比标签)替代自绘色块卡
- 活动指标(活跃/充值/退款/投诉)加环比: 对比上一同期(昨日/上周/上月/上季度同期)
- 退款卡 yoyKey=refund_count(越大越差红), 投诉=complaint, 与 KpiCard BAD_UP_KEYS 对齐
- 总设备数=存量不做环比(取舍已在ⓘ声明)
- SectionHeader 标题栏 + grain 切换移入 extra 区, 与总览 tab 布局一致
2026-10-09 23:40:31 +08:00
c03019a703
#293 充值金额排除工厂质检套餐(占10月流水57.5%,非真实营收)
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
- 根因: device_combos 混入「工厂质检专用套餐」流水(单笔999/9999元,
2026-10 达 7,948笔/881万,占当期 57.5%),CategoryBoard 充值金额虚高~2.4倍;
设备集中入库日(10-01)造成386万假峰值;TOP设备单台月充2万(质检9999x2)
- 修复: category.py 充值笔数/金额+充值活跃 排除 package_name LIKE '%质检%';
device_profile.py 设备360累计充值金额同步排除
- 修复后本月真实充值 ~652万(日均~72万),与运营预期(30-70万/天)吻合
2026-10-09 23:29:32 +08:00
8fc1286bd9
#293 分品类运营看板接入grain标准窗口+退款口径A+设备类型key修复
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
- 后端 category.py: 活跃/充值/退款/投诉全部由 start/end 单一窗口驱动(删90/30硬编码拼盘);
退款加口径A过滤(排除无需退款/驳回/申请中, 修复前夸~40%);
device_type 维度 key 从 device_type_code(全表0非空,704k台挤"未归一"一桶)改 device_type(中文名);
投诉率分母改窗口内活跃设备(旧分母=全量存量被稀释)
- 前端 CategoryBoard: 加今日/本周/本月/本季度 grain 切换(周一起始复用 weekRange.js);
KPI副标题随窗口; ⓘ说明同步; 设计说明更新
2026-10-09 23:14:09 +08:00
1ac89ea1ff
#293 修复退款明细接口全量422回归: active_age空串默认值违反pattern
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
- 现象: /refund-withdrawal/detail 不带active_age也422(退款分析页明细区块全挂)
- 根因: pydantic v2 对 Query("",pattern=X) 的空串默认值也执行pattern校验
- 修复: pattern 改 ^(le7|8-30|31-180|gt180|none)?$ 允许空串
- 实测: 合法值OK/空串422/不传422 → 修后三种情形全通
2026-10-09 22:42:41 +08:00
0f4e70ee78
#293 根治预热直调Query默认值坑: _backlog两端点全参数改Annotated声明
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
- 现象: 预热「今日:complaint:backlog」can't adapt type 'Query'
- 根因: cache_warmer直调__wrapped__, 未传参落 Query(None) 声明对象(truthy)
→ if device_type or combo 误判有筛选 → Query对象进SQL绑定
- 修复: 全参数 Annotated[X, Query(...)] = 真默认值, 直调/HTTP行为不变
- 连带: distinct_on 报错的真正触发源即此(修复后暴露下层)
2026-10-09 22:30:51 +08:00
4442e892fd
#293 退款分析口径收敛为"只管实际退款": 排除申请中(钱未退, 件数虚高17%/件均稀释)
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
- refund.py 10处 NOT IN ('无需退款','驳回') → 加'申请中'
- 例外不动: is_abandon放弃分析(申请中=放弃语义)/cash状态分布(全状态)/A路导出(全量)/对账函数(独立开关)
- 文件头登记口径说明+与宽表对账关系; failed_rule文案同步
2026-10-09 22:04:06 +08:00
23930f5bab
#293 修复设备维度筛选500: distinct_on()非Select方法(#292引入)+类型筛选位置语义修正
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
- analytics/_core._device_dim_matched_sns: .distinct_on(sn)→.distinct(sn)(PG DISTINCT ON 正确写法);
此前投诉积压/退款链条页选设备类型/套餐筛选即 AttributeError 500, 缓存预热今日:complaint:backlog 反复失败同因
- 类型/套餐 where 移到「每台设备最新订单」外层: 原写法 WHERE 先于 DISTINCT ON 执行,
语义为"曾有该类型订单即命中", 与 #292「最新订单类型」口径不符(命中集偏大)
2026-10-09 21:30:21 +08:00
a68aeab553
#293 设备档案SN补全服务+每日4点调度档: SN精确查询绕过导出10万行截断, 只填空值幂等, bms_sync_logs可观测
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
- device_profile_enrich: 扫描devices缺字段SN → BmsFetcher.findPageList?sn= (0.6s/次) → COALESCE只填空 → 缓存失效
- _fetch.fetch_one 增 session_hook 分叉点(登录后不走导出流程, 不传时行为零变化)
- bms_scheduler 4:00档(2万SN/次, 5点缺口补跑前), scripts CLI(manual, --limit 0 存量清零)
- 口径: 设备档案是SN维表特性(类型/套餐/商户/激活时间), 退款/投诉/接待为消费场景
2026-10-09 20:56:05 +08:00
1af03e86f6
#293 回填窗口支持任意日期段(≤31天校验): 整月导出实测300s超时, 建议≤10天切窗
gitleaks / gitleaks (push) Failing after 3h4m3s
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
2026-10-09 17:29:54 +08:00
8b52561a16
#293 bms_order 历史回填 admin 接口: /admin/bms-backfill/{status,month}, 单月窗口复用 sync_all manual 链路, apply 默认 false
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
2026-10-09 16:51:42 +08:00
dd335e442b
#293 bms_order 历史订单回填脚本: 逐月窗口(≤1月)复用 sync_all, 窗间冷却90s避频控, 默认dry-run
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
2026-10-09 16:44:41 +08:00
20261c5907
#292 口径修正: 设备型号/套餐改取bms_orders最新订单+激活并集兜底+排除测试套餐+投诉四维移概览
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
- _device_dim_attrs: 型号/套餐改取 bms_orders 最新订单(create_date DESC)的
device_type_name/combo_name; 激活改并集(bms_orders MIN(active_date) 优先,
devices.active_time 兜底); 工厂质检测试套餐(含"工厂质检"3变体)combo 置 None
- _device_dim_matched_sns: 设备类型/套餐筛选改查 bms_orders 最新订单字段;
激活时间改查 bms_orders MIN(active_date) 或 devices.active_time 兜底
- _device_dim_options: 设备类型/套餐候选项改取 bms_orders; 排除工厂质检3变体
- complaint_overview: 加四维统计(窗口内全量投诉工单剔卡商, 复用 _device_dim_groups)
- 前端 ComplaintAnalysis.vue: 概览 tab 加设备维度汇总区块(4柱状图+ⓘ+下钻)
- 前端 useComplaintAnalysis.ts: 加 ov* computed + drillOvDeviceDim(跳积压页带筛选)
- 测试: _mk_order 加 device_type_name/combo_name/create_date 参数;
造数改用 bms_orders 字段; 新增 test_factory_combo_excluded 验证排除逻辑
2026-10-09 14:31:11 +08:00
a3ac39f29c
#291 ruff 收尾: test_incident_event_extension.py import 排序( 4d67715 ruff --fix 漏提交)
gitleaks / gitleaks (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
2026-10-09 13:49:39 +08:00
4d67715a04
#291 退款/投诉维度汇总分析: 设备类型/套餐/商户号/激活时间段四维
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
后端:
- _core 新增 ACTIVE_AGE_BUCKETS 四桶口径(≤7/8-30/31-180/>180天+无激活记录)
与 _device_dim_attrs 共享层(激活基准收敛 bms_orders 最早激活日, #251 同源)
- 退款 decision-overview 新增 device_dims 三维汇总(类型/套餐/商户号),
激活段复用 sale_days_split 六桶真身不另做
- 投诉 extended-dims 激活分桶 #252 五桶改 #291 四桶(bms_orders 基准)
- 投诉 /backlog 返回四维统计(与明细同源自洽, 桶计数=筛选行数)
- 退款明细 + 投诉积压/导出 新增 active_age 桶下钻参数, naive 时间显式按北京时区解释
前端:
- 退款决策概览新增「六、设备维度汇总」(三图+激活段图, 点击桶带筛选下钻明细),
明细区改「七、退款明细」并新增激活时段筛选
- 投诉积压页新增四维汇总图(点击桶带筛选刷新明细) + 激活时段筛选
测试: test_active_age_dims 新增 5 项; #252 五桶测试改四桶口径; 签名清单同步
2026-10-09 13:34:53 +08:00
8fb966a174
docs(roadmap): 择机项状态回填 —— 11/13 已闭环,仅剩 #280/#283 待用户拍板
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
2026-10-09 12:18:48 +08:00
6daf05c4b9
#196 验收报告更新为同步恢复后终态:差异率 0.60%
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
#289/#290 修复后 12:07 手动触发同步恢复(inserted 24),滞后行全部入库,
真丢行 24→2,终态差异率 0.60%。
2026-10-09 12:10:17 +08:00
5c67f5856a
#290 manual_refund 金额防爆护栏:SN 串列置空+pending,防整批 INSERT 崩溃
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
「9月份登记明细」row 2344 订单金额被误填 14 位设备号(70182638026145),
Numeric(12,2) 硬限 10^10 溢出,整批 INSERT 回滚致同步中断。
_clean_row 增加 10^8 元量级护栏:命中即置空+pending 打标(与人员污染同模式),
行保留不丢真实退款金额。含 2 个新测试(污染行置空留痕/正常金额不误判)。
2026-10-09 12:02:02 +08:00
e5d443f8f9
#196 退款数据治理验收报告:当前数据审计差异率 0.95% 达标
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
基于 2026-10-09 11:22 实时导出文档(5700行)与生产库(10343行)对账:
真丢行24+未打标多余30=54行,差异率0.95%<1%;日期跳变5处全检出;
负数金额0。21/24丢行为 #289 采集中断导致的同步滞后。
2026-10-09 11:46:11 +08:00
7ec513c3b5
#289 金山新UI导出异步化:下载超时 90s→180s
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
金山改版 kd-new-ui 后导出改服务端异步生成(点击→「文件导出中,请稍候」→生成完
才触发下载),实测 5700 行/161MB 文档生成 >90s,旧超时必然失败,导致
2026-10-08 20:32 起 manual_refund 采集中断 22h+。
2026-10-09 11:33:41 +08:00
f21eaff305
#284 全量测试 3 项失败归因修复
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
1. test_b2_2_ai_data_supply: EXTERNAL_API_ENABLED 默认 false 导致
鉴权前先 503, 断言 401/403 恒失败 → monkeypatch 开启后再断言
2. test_cache_warmer_alignment: CAT_BUCKETS 随 today.py 拆分移入
today/_staff.py, glob("*.py") 不递归子目录 → 改 rglob
3. test_gap_queue_service: 共享测试库残留 DataSource(cs_record,
time_field=operation_time) 被 scalar() 先命中, 测试新加的同 code
行因 autoflush=False 不可见 → 改为复用残留行覆盖 time_field
4. setup_package.py 生产代码补齐: #237 新增 4 scope(recharge/
device/device_tag/schedule) 未同步 SCOPE_ENDPOINTS/SCOPE_PROMPTS
2026-10-09 10:56:28 +08:00
34cf8c8f75
#284 修复 test_b2_2_ai_data_supply 鉴权测试 503 断言失败
...
根因:测试环境默认 EXTERNAL_API_ENABLED=false,auth 检查前
先返回 503「外部只读接口未启用」,断言期望 401/403 恒失败。
修复:测试前 monkeypatch.setattr 开启该配置,再断言鉴权拦截。
2026-10-09 10:26:11 +08:00
79c2265d64
#287 消除 cs_records 高频全扫:JOIN 谓词去掉 NULLIF(TRIM()) 表达式
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
- 根因:JOIN/WHERE 使用 NULLIF(TRIM(device_sn),''),device_sn 索引失效被迫 Seq Scan
- 实测 device_sn 三表(cs_records/refunds/service_tickets)0 NULL/0 空串/0 需 trim,
表达式纯属冗余
- 改写:refund.py(failed_sn/host_sns/arr 等 CTE 的 join 与谓词)、
today/_staff.py(tk_sns_linked 两处 DISTINCT ON join 及同块 CTE)、
rr_views.py(投诉根因 outerjoin)
- EXPLAIN 证据:Seq Scan+Hash Join → Nested Loop + Index Only Scan
using uq_cs_record_business(1000 行 u / 30 天窗口均验证)
2026-10-09 08:40:37 +08:00
956b4f12ec
#286 删除 8 个 idx_scan=0 的未用索引(约 72MB)
...
- 迁移 c2_drop_unused_idx_286(down_revision=o5_risk_alert_settings,单一 head)
- 模型同步去除 index=True:ticket_action_logs(action_type/operator/action_at)、
devices.start_iccid、service_tickets.synced_at、cash_flow_wide.biz_no、
merchant_refunds.merchant_order_no(去重走部分唯一复合索引)、
data_match_relations.src_table(100% 行='refund' 零选择性)
- downgrade 可重建全部索引
2026-10-09 08:40:27 +08:00
3138430854
#285 bak 残留表清理脚本(DROP 前安全闸)
...
- 默认 dry-run;须 --apply --allow-real-db --dump-tag 三参齐备
- 经 scripts/_bootstrap.bootstrap 进入,禁止自拼环境变量
- 行数偏差>5% 中止;pg_dump 归档文件存在性强制检查
- 白名单仅含 wallet_withdrawals_bak_20260919 / ai_reports_bak_20260920
2026-10-09 08:40:16 +08:00
2be6c77974
docs(audit): #288 登记空表拍板结论(C4保留豁免/C5启用)
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
2026-10-09 07:54:54 +08:00
8cd397d1b4
feat(combo): 启用套餐归一字典官方种子 #288
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
基于生产三源套餐原始名归一解析(覆盖率98.3%行), 635归一码/1161全局别名:
- combo_dict_seed_data.json 随代码发布, canonical_name 取组内最高频写法
- combo_dict_seed.apply_combo_seed 幂等只新增, 不覆盖人工主字典与别名
- admin /combo-dict/stats|apply 端点显式触发, 未命中1.7%长尾回落原始名
- 7个种子测试(含归并端到端), 既有1334分段测试无新增失败
2026-10-09 07:53:06 +08:00
67b573e859
docs(eval): 采集统一入口中台化评估报告 ( #275 )
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
结论=部分收口:BMS 侧已实质中台化维持不动;SCRM 存废与口径收口
并入 #283 拍板(生产实证:四表全空、末次同步 2026-08-09);
Provider 工厂不形式化(fetch_mode 分流已达成实质);注册表
去双写归 #223。
2026-10-09 06:57:52 +08:00
66e884c09d
docs(audit): 数据表合理性专项审计台账 ( #274 )
...
133 模型表 + 2 生产残留表逐行判定(保留/收敛/豁免),数据源为生产
pg_stat(从未 reset)+ 全仓代码引用扫描。重点结论:refunds 双金额
13.2% 真实分叉保留、devices 双商户列系不同概念保留、refund_wide/E路
双存豁免(多份存储台账已登记);收敛项立单 #285/#286/#287/#288;
SCRM 域空表+两月未同步归入 #283 决策输入。
2026-10-09 06:56:13 +08:00
94c5d1c8c9
feat(deploy): 四服务资源上限 + web healthcheck + nginx 日志轮转 ( #279 )
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
- docker-compose: db/redis/backend/web 补 deploy.resources.limits(cgroup 上限
非预留); backend 4G 覆盖 Playwright+ddddocr 峰值, web 256M, db 2G, redis 512M
- web: 补 healthcheck 打 /healthz; nginx.conf 新增该端点(access_log off,
只验 nginx 监听, 不串联后端避免放大故障面); busybox wget 为 alpine 内置零镜像改动
- deploy/logrotate.d/csctalon-nginx: 宿主 logrotate 日轮转保留 14 份,
copytruncate 免 docker 依赖; README 登记一次性安装方式
验证: compose config 通过; nginx -t 通过; 容器内 wget /healthz 200 实测
2026-10-09 06:46:29 +08:00
d153551229
#282 test: api 层冒烟测试 + TS 渐进约定(O-12 / S-P2-4)
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
- web/src/api/__tests__/smoke.spec.js: import.meta.glob 自动纳入 api/ 全部
模块(新增模块零登记自动被测); mock @/api 客户端, 每模块断言可导入 +
至少 1 个导出函数真实发起 request.get/post/put/delete; 兼容具名函数与
对象聚合(如 reconcile.js)两种导出风格
- review-checklist.md 一致性段: 登记 TS 渐进约定(web/src/ 新增文件一律
.ts, 既有 .js 不追溯, 新增 .js 须在 PR 说明理由)
- 实测: 61 项冒烟全过(60 模块 + glob 生效断言), 前端全量 192 项全过
2026-10-09 06:35:10 +08:00
ac47be2c95
#278 docs: 多份存储台账(O-7 / S-P2-3)
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
- 新增 docs/standards/多份存储台账.md(REF): 6 项登记
M1 refund_wide 投影(增量触发+夜间全量) / M2 cash_flow_wide 四路投影
M3 merchant_refunds↔refund_record_reports 新旧双存(E路 v1.1 已切真身)
M4 E路时间回填D路 op_time(可信匹配+溯源字段)
M5 投诉三源跨表(引用渠道口径规范, 不重复登记)
M6 注册表三处定义(登记, 去双写归 #223 )
- 明确排除: SCD 历史表/persons 身份映射/quality_status/Redis 缓存
- docs/README.md §6.3 REF 区登记一行
2026-10-09 06:31:32 +08:00
4cbc286986
#276 feat: 夜间投诉风险预警参数配置化(O-5 / S-P2-5)
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
- 新增 risk_alert_settings 单行配置表(复用 scheduler_settings 模式):
4 权重 + 4 回看窗口 + 2 阈值 + 3 等级线, 默认值=#250 v1 拍板值,
CheckConstraint 兜底(权重 0~100 / 窗口 1~365 / 等级线严格递减)
- 新增 risk_alert_settings_service: get(缺行补默认)/update(400 校验)/
get_cached_config(60s 缓存, 改库即生效)/reset_cache_for_tests
- complaint_risk_service: compute_* 参数默认 None → 读配置表,
显式传参优先; 默认常量单一事实来源移至模型层
- 取舍: 评分阶梯(40/70/100)保持代码常量(改阶梯=改算法);
same_source SIM_THRESHOLD/DAY_WINDOW 保持硬编码(#206 拍板口径,
铁律#10 须人工审核, 不做自助可改)
- 测试: test_risk_alert_settings 8 项(默认值兜底/缓存语义/校验/端到端
配置生效/显式传参覆盖) + test_complaint_risk 14 项回归全过
2026-10-09 06:27:18 +08:00
4aff092533
#230 fix(test): 守护测试滞后修复——签名守护跟上 #273 + rr_views UNION 误用 CTE
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
- test_analytics_core_smoke: refund_withdrawal_detail 期望签名补
#273 新增的 5 个设备维度参数;detail 空库断言改为分页 dict
(该函数早已返回 {total,page,...,list},断言 list 系滞后)
- rr_views.preset_complaint_subtype_rootcause_heatmap: SQLAlchemy 2.0
下 union() 元素必须是 SELECT,去掉误加的 .cte()(修复既有失败)
2026-10-09 05:35:38 +08:00
78e4e32df1
#277 refactor: md5 指纹组装单源化(O-6 / S-P2-1)
...
- 新增 app/services/fingerprint.py:全仓业务 md5 指纹唯一组装入口
(complaint_dedup_hash / staff_unique_key / md5_hex 原语)
- upsert/_complaints.py、complaint_sync.py 的 byte 级重复实现改为委托
- scrm_staff_service.build_staff_unique_key 改为委托(不 strip / None→"0"
的历史语义 byte 级保留,避免既有键值漂移)
- tests/test_fingerprint_single_source.py:旧实现内联为等价基准,
逐样本 byte 级对比新旧输出 + 两个调用方口径一致性守护(铁律#9)
2026-10-09 05:35:29 +08:00
3b342631c3
#281 ci: issue-close-on-merge 补子工单判定(A-20260903-001 §2.3)
...
- 新增 scripts/ci_open_referenced_issues.py:解析工单正文 #N 引用,
查询各引用状态,输出仍为 open 的编号(独立脚本,避开 YAML block
内嵌多行 Python 的缩进陷阱——本次实测踩到)
- workflow 在关闭工单前调用该脚本:仍有 open 引用 → 仅评论不关闭,
评论中列出未关闭关联工单号
- 实测:#273(引用已关 #272)不阻塞;#199 检出 open 引用 #193 正确阻塞
2026-10-09 05:20:00 +08:00
fecdb6b0eb
docs(plan): 回填 B1/B1.5/B2/B3 批次完成状态与工单号 [skip-issue]
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
五个主线批次(B1/B1.5/B2/B3/B4)实际均已闭环,文档状态滞后,本次仅回填:
B1 #226~#231、B1.5 #232~#235、B2 #236~#245、B3 #246~#256 全部关闭。
2026-10-09 04:52:57 +08:00
58031ecc1b
#273 feat: 退款/投诉明细加设备维度筛选(设备类型/套餐/商户号/激活时间段)
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
- _core 新增 _device_dim_matched_sns/_device_dim_options 共享口径(走 devices 表,同 #271)
- 退款明细 refund-withdrawal/detail + facets 加四维筛选与候选项
- 投诉积压 /backlog + /backlog/export 加同套筛选,新增 /backlog/device-dim-options
- 前端 DecisionOverview 区块六与 ComplaintAnalysis 积压 tab 筛选条加设备维度控件
- 测试 test_device_dim_filter.py 4 例全过;前端 vitest 131 例全过
2026-10-09 02:02:48 +08:00
923371943a
#272 refactor: 分品类运营看板收敛为总览KPI
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
用户拍板: 运营看板只回答「整体是否健康」,维度归因下沉到
退款/投诉分析(作为筛选/分组维度)。
改动: 去掉维度切换tab+TOP10图+明细表,保留5张总览KPI卡
(总设备/活跃/充值/退款/投诉),数据说明同步更新。
后端 /category/ops-overview 接口保留(其他调用方可用)。
2026-10-09 00:49:44 +08:00
b2d31cfccf
#271 fix: 分品类看板 merchant 维度改走 devices.merchant_name
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
问题: category_ops_overview merchant 维度 JOIN device_merchant_links
+ merchants,但生产两表均为 0 行,商户号 tab 全空。实际商户号数据在
devices.merchant_name(588 个商户) / refunds.merchant_name(537) /
device_combos.merchant_name(601)。
修复: merchant 维度所有 5 处 SQL 改走 devices.merchant_name 直接
分组,与 device_type 同理。_resolve_dim_name merchant 分支不变
(key 即显示名)。注释说明暂用原因,后续 links/merchants 建链完成
后可切回。
测试: test_ops_overview_merchant_from_devices 替换原 linked 测试,
验证 devices.merchant_name 聚合正确;全量 6 例过。
2026-10-09 00:31:59 +08:00
e9413d10ed
#270 fix: 分品类看板 combo 维度退款/投诉重复计数修复
...
gitleaks / gitleaks (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
问题: _fetch_refund_by_dim/_fetch_complaint_by_dim combo 分支
JOIN device_combos(充值流水一对多)导致放大 —— 生产实测退款 2.55x、
投诉 2.04x。
修复: 改按 Device.current_combo_name 分组,与设备数/活跃同口径,
同时消除 key 空间不一致(current_combo_name vs package_name)。
测试: 新增 test_ops_overview_combo_no_amplification 回归保护
(3 笔充值 + 各 1 笔退款/投诉 → 计数恒为 1);既有 5 例全过。
2026-10-09 00:20:00 +08:00
e6817b73a5
#266 fix: _resolve_person_name 支持 bigint person_id 直接 join persons.id
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
问题: SCRM 分析视图 #42/44/46/49/50 的 role=person + field=person_id
因 scrm_sessions.person_id 是 bigint,而旧实现生成
person_aliases.alias = scrm_sessions.person_id(varchar=bigint)导致报错。
修复:
- _resolve_person_name 增加 bigint 类型检测,直接 join persons.id
- 字符串别名列仍走 person_aliases.alias 解析
- 新增 2 例测试覆盖 bigint/varchar 两条路径
2026-10-09 00:00:30 +08:00
69fb58b173
#265 O-8 env 模板键漂移清理核验: R-P2-14 已于 #214 完成,全仓复核确认
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
- TALON_API_KEY: app/alembic/ 零引用(模型层走 LLM_API_KEY/LLM_BASE_URL)
- assert_db_matches_profile 死函数: 已从 guard.py 移除
- 4 份 env 模板 vs settings 78 字段: 无残留死键(已 #214 程序化比对)
2026-10-08 23:33:34 +08:00
0d3b6a7bf6
#264 O-3 裸 except:pass 收窄: _fetch.py 2 处改为 FileNotFoundError + 注释
...
- 语义不变: 旧 .xls 删除失败不阻塞重命名为 .xlsx
- 裸 except 会吞掉 KeyboardInterrupt/SystemExit, 收窄为具体异常类型
- 迁移 print 2 处保留(条件守卫+注释说明, 是 alembic 标准输出可观测点)
2026-10-08 23:30:22 +08:00
1f63254c51
#263 O-4 scripts/ 清理归档: 21 个一次性脚本移入 archive/,顶层仅剩 5 个
...
- 归档: 9 个一次性治理脚本 + 4 个 SCRM 配置修复 + 8 个验证/调试/评估脚本
- 保留顶层: _bootstrap.py / check_ai_issue_gates.py / cleanup_test_residue.py /
mark_manual_future_year_209.py / run_complaint_same_source_groups.py
- 测试适配: test_org_no_hardcoded_names.py SCRIPTS_DIR 改为 scripts/archive/
- archive/README.md 更新为 52 个脚本完整索引
2026-10-08 23:27:24 +08:00
081dde2cd2
#262 B4-6 cash_flow_wide 增量重建评估: 维持全量(量级+两硬约束), 重评触发条件落档
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
- 评估报告: docs/reports/20261008-cash_flow_wide增量重建-评估报告.md
- 生产实测: 宽表 6.9 万行/源表最大 19.8 万行, 全量重建预估 <2min(夜批无感)
- 增量不采用: ①四路均有移除条件, 删除对账复杂度远超收益 ②e_matched/EC标记依赖全量集合预载, 扫描省不掉
- 分区不采用: 6.9 万行远未达千万级收益线
- 重评触发: 夜批实测 >10min / 宽表 >500万行 / 出现日间实时需求
- §六 为 O-7(S-P2-3) 多份存储台账预留登记
- 排期文档: B4 批次 6 项状态回填(#257~#262), B4 批次标记已完成
2026-10-08 22:42:10 +08:00
e190c92b19
#261 B4-5 统一变更审计: audit_changes 表 + record_change 服务 + persons 接入 + 保留期夜批
...
- 模型/迁移: audit_changes(字段级 before/after JSONB + actor 冗余 + source), 迁移 b4_3_audit_changes(单 head, 测试库往返验证)
- 服务: record_change 独立事务(失败不影响业务, logger 留痕) + diff_model_changes(setattr 前算差)
- 接入: PUT /persons/{id} 字段级审计(source=api)
- 保留期: settings 180/730 天 + 夜批 purge_expired_logs(02:00, db_guard 显式 allow_real_db, id 分批 5000)
- 设计: docs/arch/A-20261008-003(分区先行, 三取舍: 不归并 13 张存量 Log 表/不执行分区/不做审计查询页)
2026-10-08 22:38:18 +08:00
00e828c37f
#260 B4-4 导航/权限单源: router 死路由清理 + 权限档位单源化 + 孤儿页清理
...
- router/index.js: 删 5 个不可达组件路由 (data/records, data/browser,
system/data-sources, system/data-quality, system/sync-settings 的组件版;
redirect 保留深链行为不变); 守卫三级判断统一收敛走 roleAllowed (roles > admin > manager)
- 新建 config/access.js: ROLE_LADDER 档位唯一来源 + roleAllowed()
(未知档位别名 fail-closed); auth.js isAdmin/isManager 改派生;
MenuTree.roleVisible 走同一判据
- 删除 5 个孤儿页 (Records/FetchRefund/FetchScrm/Match/ManualRefundQuality,
功能已收编 Ingest tab/SyncDashboard/QualityAudit; git 历史可恢复)
- 测试: access.spec.js (档位/数组/fail-closed/auth 派生一致性) +
routes.spec.js (同 path 唯一注册/redirect 不挂 component/组件须有 name)
- 取舍: Dashboard.vue 快捷入口不收敛 (角色工作台聚合非导航树);
menu.js 本体不改 (LOCKED)
2026-10-08 21:22:35 +08:00
f60db4c426
#259 B4-3 看板双轨定主序: 新增看板默认配置驱动 + 标准封装登记
...
- 看板卡片规范升版 v1.1: 新增 §八 技术路线主序 (配置驱动为默认,
手写大页两类例外 + components/dashboard/ 封装底线 + 存量不迁移)
- review-checklist.md: 新增「看板技术路线主序」审查段 (2 检查项)
- Designer.vue: 配置页顶部加主序选型提示 (页面层可见说明 + 代码注释)
- docs/README.md §六: 登记行同步 v1.1 描述
2026-10-08 21:12:17 +08:00
c0cbfde656
feat(batch): B4-2 夜间批处理基建 - 调度表/幂等锁/限额熔断 #258
...
- batch_jobs 调度表(每日 HH:MM 北京时间/enabled/timeout/熔断计数) +
batch_job_runs 执行日志; 两道部分唯一索引幂等锁(防重叠/防当日重跑)
- batch_runner: 注册表驱动(BATCH_JOBS 代码注册, DB 存调度参数),
run_due_jobs 同步入口 + asyncio 轮询(挂 lifespan, 独立于采集开关)
- 熔断: 连续失败 ≥3 自动停用; 超时回收释放锁; 拦截也留 skipped 痕
- 管理端点 /batch/jobs(列表/runs/改参/手动触发, 幂等锁仍生效)
- 种子任务: rebuild_cash_flow_wide(02:30) / rebuild_refund_wide(03:10)
- tests/test_batch_runner.py 6 例(执行/日锁/熔断/超时回收/注册补插/时区)
2026-10-08 21:04:33 +08:00
5784587e7d
feat(metrics): B4-1 指标字典表 - 口径单点登记/CRUD/种子19条 #257
...
- metric_dictionary 表: code(域.名)PK / name_cn 全站唯一 / definition 必填 /
formula(JSONB) / unit / category / source_tables(JSONB) / owner / is_active
- /metrics/dict CRUD: 列表(category/keyword/include_inactive 过滤) + 管理员登记/更新
- 种子 19 条高频核心指标, 口径逐条对照代码现状(v4 四路退款/#78 接待分层/#81 三源投诉)
- tests/test_metric_dict.py 5 例(增删改查/校验/种子一致性)
2026-10-08 20:53:06 +08:00
9ff72a3515
docs(plan): 回填 B3 批次完成状态 #256 [skip-issue]
gitleaks / gitleaks (push) Waiting to run
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
2026-10-08 19:41:38 +08:00
9d987bed84
feat(schedule): B3-8 排班镜像落地 - 企微排班导入/来源标注/事件联动 #256
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
决策(用户拍板 2026-10-08): 方案A镜像起步 - 企微源为主, 系统编排保留为补充,
schedules 为系统内唯一真身, 单向流入不做双向回写。
- 后端 POST /schedules/import: 企微导出 xlsx 导入(姓名+日期必填, 班次或
HH:MM-HH:MM 时间列), 冲突按 uq_person_date skip/overwrite, source=wecom_export,
upload_records 留痕(对齐 B3-9); 事件联动复用 /incidents/markers/timeline(零改动)
- 前端 Schedule.vue: 导入入口(冲突策略选择+结果面板)/列表来源列
(企微/系统/企微API)/日历视图关联经营事件+排班调整建议(只读投影)
- 三层设计说明: script 头注释 + 2处ⓘ(来源列/事件块)含取舍段
- 测试: test_schedule_import.py 4 例(正常+留痕/skip与overwrite/行级错误/缺列400)
2026-10-08 19:38:06 +08:00
d653ddadf1
feat(persons): B3-7 人员技能矩阵 - 技能标签/矩阵编辑/绩效排班联动 #255
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
- 新表 person_skill_tags(人员×技能×熟练度1-5, 唯一约束防重), Alembic 单 head
- 4 个技能 API: 标签字典/技能矩阵/单人全量替换/批量打标(add|remove)
- list/detail 响应挂 skill_tags, 列表支持 skill_tag 过滤
- 前端 Persons.vue: 技能列+筛选+矩阵弹窗(点击轮换)+详情技能编辑
+绩效/排班联动摘要(近7天接待+本周排班, 真身在分析页, 此处投影已声明)
+批量编辑支持技能打标; 三层设计说明落地(头注释+3处ⓘ含取舍)
- 测试: test_person_skills.py 4 例全过; build/vitest 121 例过
2026-10-08 19:09:34 +08:00
29ff0e32f2
feat(analytics): B3-10 分品类运营看板 - 设备类型/套餐/商户号聚合运营KPI #254
...
后端: services/analytics/category.py 三维聚合(规模→活跃→营收→风险四级),
GET /analytics/category/ops-overview; 活跃=90天充值∪流量, 风险窗口30天,
投诉走 complaint_scope 唯一出口, 金额 Decimal 计算出参 float(#222 C′);
套餐走 combo_dict 归一, 商户号走 DeviceMerchantLink is_current 建链。
前端: CategoryBoard.vue(维度切换+KPI汇总卡+TOP10双轴图+明细表,
投诉率>5%红色高亮), 挂运营分析看板新 tab「分品类运营」(lazy 渲染);
三层设计说明落地(代码头注释五要素 + 页面ⓘ数据说明含"空"含义与
不下钻理由声明)。TOP10图为品类轴, 事件时间轴标注不适用(已在
运营总览趋势图由 B1.5 落地)。
测试: 后端 5 例全过(归一聚合/归一名回落/建链/limit截断/端点冒烟),
ruff clean; 前端 build 通过 + vitest 121 例全绿。
2026-10-08 18:25:03 +08:00
548854bb7a
feat(analytics): B3-6 接待/绩效/专项三页产品化 #253
...
- menu.js: 接待分析/绩效分析/专项跟进 buildMode 由 debug_only 改为 always
(专项分析整组本就是 always, 三叶子项出 debug 后 manager 角色 release 也可见)
- Reception.vue: 顶部加「排班对照」(跳 /management/schedule?tab=comparison&date=今天)
与「创建专项任务」(跳 /tasks?create=1&title/context 预填), 把看数→派任务串成闭环
- Schedule.vue: 支持 query.tab=comparison 直接落排班对照 tab 并自动加载
- tasks/List.vue: 支持 query.create=1 自动打开新建对话框并预填 title/description
- DashboardView.vue: 预设看板(performance)补页面层「数据说明」tooltip
(配置化看板的 description 字段缺口径, 用 code 级硬说明补 给谁/回答什么/口径/取舍)
- 测试: MenuTree.release-visibility.spec.js +1 例
(supervisor 在 release 下可见 接待分析/绩效分析/专项跟进)
- 验证: 前端 build 通过; MenuTree 两 spec 共 12 例全绿
2026-10-08 17:59:14 +08:00
628a656782
feat(complaints): B3-11 投诉分析扩展维度 - 店铺/商户号/型号/套餐/激活时长 #252
...
- complaint_extended_dims: 五维 TopN 分布 + (其他)归并
· 店铺/商户号: SN → device_shop_links/device_merchant_links is_current 建链,
未建链显式分桶暴露覆盖缺口
· 设备型号: device_type_code → dim_device_type 归一名, 回落原始文本
· 套餐: current_combo_name → combo_name_alias(is_current) 归一, 回落原始名
· 激活后时长: register_time − devices.active_time 天数分桶(上海时区)
- GET /complaints/extended-dims 端点(带缓存, complaint: 前缀自动失效)
- 投诉判据走 complaint_scope 唯一出口; 仅计数维度(工单金额稀疏)
- 8 例测试: 五维命中/回落/未建链缺口/窗口过滤/非投诉排除/端点冒烟
2026-10-08 17:51:43 +08:00
6baff54c62
feat(analytics): B3-5 退款按设备销售天数核算 - 售出后N天分桶入决策概览 #251
...
- refund_decision_overview 新增 sale_days_split 块(分离展示, 与既有块并列)
- 退款关联 bms_orders.active_date(每设备 MIN = 首购激活日),
7 桶: 0-7/8-30/31-90/91-180/181-365/>365天/无激活记录(空桶补零)
- 口径与 A 路线上直退一致(线上+已退款); 天数带 AT TIME ZONE Asia/Shanghai
- 口径纪律: 单独核算, 不纳入售后客服指标(用户拍板)——仅经营分析视角
- 金额 Decimal 计算, 出参边界 float 转换(AGENTS 铁律4 C′)
- 5 例测试: 桶边界/无激活桶/首购MIN/线上线下口径排除/金额类型
2026-10-08 17:45:31 +08:00
fb801346d4
feat(complaints): B3-4 夜间投诉风险预警 v1 - 确定性四维评分+专员优先清单 #250
...
- complaint_risk_service: 充值多/流量少/愿沟通/同题重复四维确定性评分
(不依赖 LLM), 权重阈值参数化, 评分明细可下钻到原始事实
- GET /complaints/risk-priority-list: 按总分降序输出优先清单,
含 level(critical/high/medium/low) + 各维得分 + 关键事实
- 口径: 投诉判据走 complaint_scope 唯一出口; 流量取最近完整月;
同源组取 auto/confirmed; 孤儿 SN 过滤
- 13 例测试: 评分函数边界 + 全量管线 + 窗口排除 + min_score + 端点鉴权
2026-10-08 17:42:07 +08:00
7b888e8941
feat(tags): B3-3 标签事实化: 打标来源字段+下钻API+投诉按标签维度 #249
...
架构依据: A-20261007-002 §3.6 标签事实化 — 让标签能反查"谁触发、何时、
因为哪条业务事实", 并让投诉看板能按标签组合切片定位高风险人群。
改动摘要:
- device_tag_rel 新增 source_table/source_id/reason 三列(migration
b3_3_tag_rel_source), 唯一性由既有复合主键(device_sn, tag_id)天然满足,
不新增部分唯一约束(避免过度工程, 详见 migration docstring)。
- 新增 GET /api/v1/devices/tags/{tag_id}/devices 下钻端点: 按 tag 查设备
明细, 返回 sn/device_type/merchant_name/tag_status/tagged_at/tagged_by/
batch_id/source_table/source_id/reason, 支持 days 时间窗 + source_table
+ status + keyword 过滤。每条带 sn 供前端跳设备 360 时间线。
- 新增 GET /api/v1/complaints/by-device-tag 端点 + analytics.complaint.
complaint_by_device_tag(): 投诉按设备标签组合聚合。组合口径非互斥分桶
(一台设备多标签时每标签各计一次), 未打标 SN 归入 "(未打标)" 桶暴露
覆盖缺口。投诉判据复用 complaint_scope.is_complaint_ticket_cond (#87
唯一出口), 缓存 key 前缀 complaint: 自动纳入 cache_warmer 失效 pattern。
测试:
- tests/test_tag_fact.py 13 例(打标来源写入/复合主键拦截/下钻返回字段与
状态过滤/404/投诉分桶/组合计数/inactive 不计入/窗口过滤/端点冒烟/
tag_stats 口径回归), 全过。
- B3 累计: B3-1 10 + B3-2 6 + B3-9 3 + B3-3 13 = 32 例, 全过。
2026-10-08 17:31:51 +08:00
ed1654aa77
feat(ingest): B3-9 手工/共享表格通道治理 - source 枚举扩展 + 批次台账 #248
...
- upload_records.source 16→32 字符, 枚举扩展 manual/bms/shared_doc
- shared_doc_sync_service: 投诉/手动退款两条路径入库后写 upload_records
(批次号/文件名/来源/总行数/成功/失败/错误明细), 与手工通道可观测性对等
- 3 例测试: 批次写入/失败状态/按来源查询
2026-10-08 16:53:02 +08:00
f24490af96
feat(analytics): B3-2 套餐字典归一 - combo_dict + combo_name_alias + 口径切换 #247
...
- combo_dict 归一套餐字典 + combo_name_alias 原始名→归一码映射(SCD, 含商户+生效期)
- 部分唯一索引 (raw_package_name, merchant_pk) WHERE is_current + NULLS NOT DISTINCT
- complaint.py category_by_package: 原始名 group by → SCD 时点关联归一码, 未命中回落原始名
- 新增 GET /analytics/category/package-coverage 暴露归一命中率
- 6 例测试: schema 存在性 + SCD 唯一约束 + 归一命中/回落 + 覆盖率空表
2026-10-08 16:46:34 +08:00
aa9bf917dd
feat(domain): B3-1 客户域建表 - 四层主数据 + SCD 建链 + 设备类型维度 #246
...
- dim_device_type + devices.device_type_code 可空外键(原始文本保留)
- merchants 演进: merchant_id 业务键部分唯一索引(空值允许)
- device_merchant_links: 设备→商户号 SCD Type2 建链表 + 部分唯一索引
- shops / enterprises / shop_enterprise_history / device_shop_links
- 新增 pytest 10 例: schema 存在性 + SCD 当前行唯一 + 部分唯一 + FK
2026-10-08 16:22:10 +08:00
b9946d8b27
docs(web): 重点看板设计说明补齐——接待分析 + 纯工信部投诉 #245
...
按界面设计说明规范三层落地:
- 两页文件顶部补五要素(目的/逻辑/口径/取舍/工单)注释块
- 标题旁补可见 ⓘ 气泡, 含「取舍」段; 纯工信部页判据长文本
真身仍是页面 alert, ⓘ 只做摘要指引(投影不双真身)
- review-checklist 既有对应项核对通过
纯说明性补齐, 功能与数据展示零改动。
lint 0 error(警告数不变), build 通过, 120 例全通过。
R-P1-5 / B2-10, 关联 #199 , 纯工信部页源自 #63
2026-10-08 15:05:38 +08:00
45d5bd1567
perf(web): ECharts 按需注册 + Vite vendor manualChunks #244
...
- 新增 web/src/utils/echarts.js 作为全站唯一入口: echarts/core
按需 use() bar/line/pie/heatmap + title/tooltip/grid/legend/
visualMap/markPoint/markLine/markArea + CanvasRenderer
- BaseChart.vue 删整包 import * as echarts, 改引 @/utils/echarts
- vite.config.js manualChunks: echarts/zrender、element-plus、
vue 生态、dayjs、其余 vendor 独立成块
分包效果:
- BaseChart chunk 1128KB → 0.87KB
- echarts 按需块 627KB(整包约 1MB)
- index 主包 1275KB → 28.7KB
- element-plus 954KB / vue-vendor 111KB / vendor 228KB 独立缓存
lint 0 error, build 通过, 120 例全通过;
另跑运行时冒烟确认四类图表+mark*+visualMap 无缺失注册错误。
S-P1-3 / B2-9, 关联 #199
2026-10-08 14:59:50 +08:00
e9c33dc4ea
feat(web): 前端轮询收口——统一轮询层 usePolling #243
...
新增 web/src/composables/usePolling.js:
- POLL_INTERVALS 间隔常量全站唯一来源(2s/3s/15s/30s/60s 六档)
- 同 key 全局单例去重 + async fn 防重入
- visibilitychange: hidden 暂停, visible 立即补跑再恢复
- onScopeDispose: 组件卸载自动停止本组件任务
迁移 10 个文件的自建 setInterval(MainLayout/AccessCenter/
SharedDocSources/FetchScrm/FetchRefund/SyncDashboard/
TencentDocSync/AiDataGovernance/AiOnboarding/useToday),
各页熔断语义保留, 仅 clearInterval → stopPolling。
新增 5 例单测; lint 0 error, build 通过, 全量 120 例通过。
S-P1-2 / B2-8, 关联 #199
2026-10-08 14:52:23 +08:00
0b59069eba
fix(gap): #242 缺口台账闭环: 低频档放弃直接记open + 手动导入按业务日回写filled (B2-7/R-P1-17)
2026-10-08 14:32:08 +08:00
e03c286982
fix(sync): #241 文档采集拆分尝试心跳与成功水位, 失败30min快速重试 (B2-6/R-P1-16)
...
根因: doc_sync_sources / doc_sync_configs 的 last_sync_at 成败都刷新, 一个字段
同时当调度节流基准(最近尝试)与看板数据水位(最近成功)。失败被当成功:
- due 判定 now>=last_sync_at+interval, 默认 interval 60min > 高频槽距 30min
→ 失败源静默等满整个间隔才重试(bms_scheduler 注释却称下槽位重试, 与实现相反);
- 看板把失败误读为"数据刚更新"。
修复(审查选项二: 分离 heartbeat 字段), 两路径同一模式:
- 新增 last_attempt_at(最近尝试, 无论成败=调度节流心跳); last_sync_at 收窄为
最近成功水位(success/partial/skipped 才刷, failed 保留上次成功值)。
- 新增 app/services/sync_watermark.py 单一来源: stamp_attempt / due_wait_minutes
/ is_due(鸭子类型, 两模型共用); due 基准 last_attempt_at 回退 last_sync_at。
- failed 等待 = min(interval, settings.DOC_FAILED_RETRY_CAP_MINUTES=30):
60min 源封到 30min(约下一高频槽位重试), 20min 源不被拖慢。
- shared_doc(doc_sync_sources) 与腾讯开放文档(doc_sync_configs)的记录点、
due/should_sync 判定、状态序列化(row_to_dict/get_doc_config/status 补
last_attempt_at)全部接入; doc_sync 抽 _persist_status 收敛三处重复写状态。
- bms_scheduler:1165 相反注释改为准确口径。
- 迁移 b2_6_sync_watermark_split: 两表 ADD COLUMN last_attempt_at + 回填
=last_sync_at(避免部署瞬间全源到期并发 playwright; NULL 行保持首次到期);
downgrade DROP COLUMN(纯新增, 已验证往返)。
- 测试 test_b2_6_sync_watermark.py(纯函数各分支 + 失败不污染水位 + DB 集成
failed 30min 重试/成功仍按 interval); 更新 test_shared_doc_sync 失败断言。
2026-10-08 14:16:00 +08:00
388e775bf6
feat(ai): #240 预生成 max_tokens 按 period 对齐 R3 长报告口径 (B2-5/R-P1-4)
...
R3《算力深度评估》要求详尽长报告 max_tokens ≥4000。内置模板数据层已对齐
(#134/rolling seed), 本工单补齐代码层默认、实时口径声明与存量兜底, 消除
"手动新建周/月模板仍落 2000 复发截断" 的真缺口:
- AiReportTemplate: 新增单一来源 DEFAULT_MAX_TOKENS_BY_PERIOD
(daily 2500/weekly 4000/monthly 5000/rolling_7d 4000/rolling_30d 5000)
+ default_max_tokens_for_period / resolve_max_tokens; 列 ORM 默认
2000→4000(纯 Python default, 无 server_default, 无表结构变更)。
- admin_ai 创建模板: 未显式给 max_tokens 时按 period 取分档默认,
显式正整数仍优先(resolve_max_tokens)。
- settings.AI_MAX_TOKENS=1500 / ai.py 实时路径: 补 R-P1-4 口径声明
(实时问答=简洁档1500, 非 R3 详尽长报告; 数值不动), 长报告仅走预生成。
- ai_report_worker 注释更新为按 period 分档。
- 新迁移 b2_5_long_report_tokens: 幂等兜底把存量周/月/滚动模板
GREATEST(max_tokens,4000); monthly 5000 等更高值不压低, 日报不动;
downgrade 有意 no-op(无法还原原值且回退会重新引入截断)。
- 测试 test_b2_5_max_tokens_align.py 11 例(分档/兜底/R3 下限/显式优先/
列默认), 迁移在独立空库全链 upgrade 验证单 head + 数据提升语义。
2026-10-08 13:53:46 +08:00
9a6418c6d1
fix(llm): B2-4 超时口径统一(单次 score vs 端到端 wall-clock) + 降级计账修正 #239
...
R-P1-3: 调用方声明 timeout_s 被 _ensure_timeout_fits_tokens 悄悄上调
(time_parser 15→50、extract 60→110、实时 60→130), 且 timeout_s 只是单次
attempt 的 read timeout, 多 provider × 多重试端到端无硬墙; 多 provider 审计
duration_ms 复用全局 t0, 第 N 个 provider 被前序失败累计污染。
- llm_client: chat_completion 新增可选 deadline_s(端到端 wall-clock 预算,
默认 None 维持现状), 换算绝对截止时刻透传 multi/env/single;
_call_single_provider 每次 attempt 前按剩余预算裁剪 read timeout, 预算耗尽
立即终止(DeadlineExceeded), 退避不超预算;
_call_multi_provider 进入 provider 前预算耗尽即停止降级, 各 provider 审计
duration_ms 改为自身 wall-clock(p_t0), 全局 t0 仅用于端到端失败日志。
- ai_time_parser: 实时前置兜底(失败安全)加 deadline_s=20, 落实其「快速」
本意, 不再为 300 token 干等自洽上调的 50s。
- ai_extract / ai.py: 补口径注释(60s 为云端/单次下限, 本地按 token 自洽
上调, 单一真相在 _ensure_timeout_fits_tokens), 不擅缩实时 SLO(归 B2-5)。
- 测试 6 例(deadline 零请求/退避裁剪/成功不受影响/停止降级/per-provider
独立计账); timeout_headroom 与全量 llm、time_parser/extract/semantic
共 230+ 例回归通过; ruff 通过。
2026-10-08 13:37:04 +08:00
d69e2b0688
feat(ai): B2-3 预生成报告写库前 schema/数值校验闸门 #238
...
R-P1-1: 预生成 LLM 输出原 str() 后直接 save_report, 空回答/截断/占位符/
模板维度缺失会静默落库并 is_public。对齐 governance_judge/onboarding_draft
成熟模式, 写库前加 validate→重试1次→仍非法不落库 闸门。
- 新增 ai_report_validator.validate_pregen_report(answer_md, template)
返回 (ok, errors[]): 非空/最小长度、代码围栏成对、NaN/null/None 等
程序占位符拦截; 维度覆盖复用 #152 check_dimensions 锚点判定(单一真身)。
报告为 markdown 非 JSON, 不做逐数字对账(避免环比负百分比等误杀)。
- ai_report_worker: 抽出 _invoke_pregen_once(含抢占/已在loop/失败事件);
generate_for_template 写库前过闸, 非法重试一次, 两次非法记
pregen_validation_failed(error) 不落库; CancelledError 首次即跳过不重试。
- 测试 19 例: validator 各分支 + worker 成功/重试/放弃/抢占 四路径;
worker schedule 与 completeness 既有 23 例回归通过。
2026-10-08 13:24:08 +08:00
8a1ae9f40c
#237 B2-2 AI 数据供给层: 统一聚合 + 设备/充值/标签/排班
...
新增 app/services/ai_data_supply.py 作为设备/充值流量/设备标签/排班四类
实体数据的单一聚合真身, 两侧复用同口径:
- 对外: external.py 新增 /external/recharge|device-tags|device/{sn}|schedule
四个 token+scope 只读端点; SCOPES 增 device/recharge/device_tag/schedule。
- 对内: ai_feed.build_messages 按问句条件注入实体段(设备 SN 经 DB 精确核对
命中→档案/动态/充值; 排班关键词→区间排班考勤), 普通经营问答零影响。
- 排班区间聚合为新写 service(人天/班次/状态/每日序列/出勤异常名单)。
- 设备档案 _device_subject 从 devices.py 下沉为 services/device_profile.
build_device_profile; 下沉时修正 _sum 把"求和列"误当"SN 匹配列"的潜伏 bug
(原 WHERE actual_amount='SN' 非法条件), 匹配列与求和列分离。
- 客户标签依赖未建的客户域 B3-1, 明确推迟 B3-3, 不在本批实现。
测试: tests/test_b2_2_ai_data_supply.py 16 例(聚合/区间护栏/条件注入/鉴权);
device_profile/external/ai_feed 等相关回归 178 例通过; ruff 通过; web build 通过。
2026-10-08 13:12:52 +08:00
afeeb6ef88
#236 B2-1 AI 报告策略调整: daily 改实时生成
...
- warm_on_sync_complete/warm_on_startup/schedule_due_templates 跳过
period=daily 模板(20min 节流代码保留), daily 不再随同步/启动/定时预生成
- ai_report_matcher 全部候选 SQL 增加 snapshot_period != 'daily' 过滤,
历史 daily 预生成快照不再被命中, 强制 daily 问句走实时 LLM(L2, 用户等几秒)
- weekly/monthly/rolling_7d/rolling_30d 预生成与命中路径不变
- 测试: 新增 test_b2_1_daily_realtime_skip(6 例, Mock 不依赖 DB);
worker_schedule/matcher_gate/pregen_freshness_204 改用非 daily 模板覆盖
worker 与门槛路径(行为翻转 + FK 种子补齐)
2026-10-08 12:37:20 +08:00
809c3e8a6f
#235 B1.5-4 排班联动字段登记(经营事件排班调整建议)
...
gitleaks / gitleaks (push) Failing after 3h13m55s
backend-test / commit-message-lint (push) Failing after 1h35m30s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
本工单仅做字段登记, 排班页关联事件展示后置于 B3-8 排班目标形态决策。
后端:
- migration b1_5_4_scheduling_suggestion: incidents 新增 scheduling_suggestion(Text, nullable)
- Incident model 新增 scheduling_suggestion Mapped[str|None]
- incidents API: _serialize / get_incident_impact_comparison / create / update 全部贯通
(create 仅经营事件写入; update 加入 scheduling_suggestion 到可更新字段列表)
前端:
- Incidents.vue: 经营事件表单新增「排班调整建议」textarea;
详情对话框新增条件展示(经营事件 + 非空时显示)
- Today.vue + OperationDashboard.vue: 事件详情对话框同步新增排班调整建议条目
依赖:
- B1.5-1 事件模型(#232 已完成)
- B3-8 排班目标形态决策(后置, 业务决策, 未决)
验证: npm run build ✓ (9.63s) | alembic head=b1_5_4_scheduling_suggestion ✓ | 模型+API 导入 ✓
2026-10-08 12:07:44 +08:00
d0f165acec
#234 B1.5-3 事件与数据对照(事件前后窗口接待/投诉/退款量变化)
...
后端: GET /incidents/{id}/impact-comparison 新端点
- pre/during/post 三窗口各统计 reception/complaint/refund_count/refund_amount
- 点事件 anchor=occurred_at(无 during); 区间事件 anchor=start_at, end=end_at(有 during)
- 退款时间用 SQL func.coalesce(actual_refund_time, created_at_raw) 单次扫描回退
- 金额保持 Decimal, 出参 float(...) 一次性转换(#217 C′)
- 路由声明在 /{incident_id} 之前避免 422 路径匹配冲突
前端: useIncidentMarkers 新增 impactComparison + impactComparisonRows computed
- markArea(区间事件) translucent green 背景 z:1 不遮挡数据线
- markArea silent:false 允许点击触发 onChartClick
- 点击 markPoint/markLine/markArea 弹详情对话框 + 异步加载影响对照
- impactComparisonRows 把 pre/during/post + delta 转成 4 行表格
(接待量/投诉量/退款笔数/退款金额, delta 红↑/绿↓/灰→)
- Today.vue + OperationDashboard.vue 同步落地对照表 + .impact-hint
验证: npm run build ✓ (8.38s) | 后端路由导入 ✓
2026-10-08 12:04:02 +08:00
f17624194f
#233 B1.5-2 看板时间轴标注层(markPoint/markLine + 点击弹详情)
...
后端: 新增 GET /api/v1/incidents/markers/timeline 端点, 返回时间窗口内的事件
列表(id/title/event_type/occurred_at/start_at/end_at/severity/impact/target_audience
/impact_metrics 等), 用 OR 兜底 occurred_at/start_at/end_at/created_at 防漏。
路由顺序前置避免被 {incident_id} 参数路由抢先匹配。
前端:
- BaseChart 加 chartClick 事件 emit, 透传 markPoint/markLine 点击
- 新增 composables/useIncidentMarkers.js: 拉取事件 + 把事件转 ECharts
markPoint(点事件, 经营事件绿/异常事故红, pin/triangle 区分) +
markLine(区间事件, 经营事件绿竖线); resolveClick 按 id 查详情
- Today.vue / OperationDashboard.vue 趋势图注入 markPoint/markLine 到
首个 series; @chartClick 触发事件详情对话框(显示类型/标题/主体/发生/
生效起止/严重/影响/明细/受众/影响指标)
- 视图自适应: 日视图 kind=hourly(labels 'HH:mm'), 周/月视图 kind=daily
(labels 'MM-DD')
- 事件标记加载与主数据 Promise.all 并行, 失败不阻塞主流程
2026-10-08 11:55:34 +08:00
09acc51747
#232 B1.5-1 扩展 incidents 事件模型支持经营事件
...
incidents 表新增 5 字段(走 Alembic 迁移 b1_5_1_incident_event_extension):
- event_type(默认'异常事故', 取值'经营事件'/'异常事故') - 用 String+业务枚举,
与同表 status/severity/category 一致避免 ENUM 修改成本
- target_audience(Text, 推文/活动目标受众)
- start_at/end_at(timestamptz, 经营事件区间表达; 点事件 end_at=null)
- impact_metrics(JSONB, 量化影响指标, 用于 B1.5-3 趋势曲线叠加)
模型 IncidentEventType 枚举类 + Incident 5 字段 Mapped 列声明;
API 序列化/创建/更新/过滤全部纳入新字段; 事件类型非法值返回 400;
前端 Incidents.vue 加事件类型选择/过滤器/类型列/详情展示, 含设计说明
注释块(替谁答何问题/为何这样组织/不做什么及原因/取舍说明).
8 项冒烟测试覆盖字段声明/枚举值/默认值/None 安全/经营事件完整字段.
2026-10-08 11:47:15 +08:00
60e0cf1a1b
chore(scripts): 归档 31 个调试/探针/测试脚本到 scripts/archive/ ( #231 )
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
R-P1-12: 顶层 scripts/ bootstrap 收口
归档清单(31 个, 超计划 23 个):
- _debug_* (7): ticket_export_500/full/simple, ticket_upload_records,
ticket_upsert_bug, cap3, aggressive
- _test_* (2): ticket_upsert_fix, workbuddy_push
- _fix_captcha_* (2): type_math, type_sql
- _offline_test_* (1): math_captcha
- debug_* (5): baiyin_manual, scrm_captcha_detail, scrm_cols, scrm_login, scrm_opts
- probe_* (6): baiyin_menus/submenus/user_urls, baiyinqifu,
scrm_after_login, scrm_login
- test_* (4): baiyinqifu_captcha, scrm_dual_mode, scrm_routes, scrm_text_captcha
- sniff_* (1): shanmao_export_request
- find_* (1): scrm_admin_domains
- 登录辅助 (2): kdocs_login, tencent_doc_login
归档依据:
- 文件名前缀 (_debug_/_test_/debug_/probe_/test_/sniff_/find_) 表明一次性调试
- 绕过 scripts/_bootstrap.py 运行模式守卫与 db_guard
- 无任何代码引用 (grep 验证)
- 用 git mv 保留 git history
scripts/archive/README.md:
- 性质 REF(参考), 创建 #231/B1-6
- 说明归档范围 / 为什么不直接删 / 如何参考使用
- 显式声明: 默认不运行, 复制到 workdata/ 改造后再用
顶层 scripts/ 剩余 25 个运营脚本 (init/cleanup/fix/apply/import/mark/verify/
export/run/check) 仍绕过 bootstrap, 迁移工作量大, 作为后续技术债
(建议归入 B2 或择机项 O-4)。本工单仅做归档收口。
测试: 96 非DB 测试通过 (含 B1-5 新增 22 纯Python 冒烟)
2026-10-08 11:34:24 +08:00
516ebde558
test(analytics): refund_chain + rr_views 冒烟测试 ( #230 )
...
R-P1-15: 核心引擎关键路径补 happy-path 冒烟覆盖
新增 tests/test_analytics_core_smoke.py (44 测试):
- 22 纯Python (无DB, 始终生效):
- TestRefundChainSignatures: 7 公开函数签名稳定(防拆分/重命名回归)
- TestRrViewsHandlers: HANDLERS 15 键完整 + 全可调用
- TestRrParseParams: 5 测试 (None/空字典/start/end/优先级)
- TestShParseParams: 3 测试 (None/date/start_end 单日窗口)
- TestBuildReasonBarData: 3 测试 (空输入/数据/'未知'兜底)
- 22 DB冒烟 (用 db fixture, CI生效):
- 7 refund_withdrawal_* 公开函数对空库应返回正确类型
- 15 HANDLERS 对空库应返回 dict (不崩)
守护:
- 签名变化(如 refund_withdrawal_detail 11 参数)由测试锁定
- HANDLERS 字典缺键/多键都被守护(前端路由硬编码, 缺一即页面空白)
- _rr_parse_params 兼容 start/end 与 date_start/date_end 别名
- _build_reason_bar_data 空 reason 走'未知'兜底 (防 None 混入 ECharts)
测试统计: 22 纯Python 通过, 22 DB冒烟待 CI/部署验证
2026-10-08 11:31:36 +08:00
fcf790345a
fix(analytics): 接待退费统计系统变体排除 2→7 变体 ( #229 )
...
R-P1-19: 系统操作排除变体补齐
reception.operator_refund_stats 原仅排除 ["sys", "system"] 2 变体,
PG NOT IN 大小写敏感, SYS/System 等大小写变体与用户自助/客户自助/
企微用户自助 会漏入人员统计。
修复: 复用 _core.SYS_OP_BLACKLIST_RR (7 变体, 退款挽回路径已用同口径),
不再 inline 硬编码, 避免双份定义漂移(AGENTS 禁止第13条)。
变体清单: sys/system/SYS/System/用户自助/客户自助/企微用户自助
2026-10-08 11:28:09 +08:00
0e13adfa24
fix(sync): 缓存失效补 reception:* ( #228 )
...
R-P1-18: SCRM 同步后接待看板缓存失效遗漏
sync_task_manager._execute 内缓存失效 pattern 列表:
- 原: ("today:*", "refund:*", "complaint:*")
- 补: + "reception:*"
SCRM 同步会更新接待数据(CsRecord), 但接待分析页缓存
(reception:overview/catsum/percapita/group:*) 不会失效,
导致同步后接待看板仍读旧聚合。
reception:* 已在 cache_admin.py 缓存组登记("接待分析"),
本补齐让同步后失效与 today/refund/complaint 对齐。
2026-10-08 11:26:11 +08:00
ee804fa3d7
fix(governance): 补 db_guard 守卫到 3 处 L2 破坏性操作入口 ( #227 )
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
R-P1-11: 补齐 db_guard 运行时缺口
破坏性操作守卫补齐(AGENTS.md 绝对禁止第3条):
1. governance_execute_service._backup_rows 入口
DROP TABLE IF EXISTS gov_bak_* (line 441)
2. governance_execute_service._exec_delete_dups 入口
DELETE FROM {tbl} WHERE ctid IN (...) (line 477)
3. governance_execute_service._exec_fix_time 入口
批量 UPDATE "{table}" SET "{col}" (line 667)
4. log_retention_service.purge_table 入口
DELETE FROM {table} WHERE ctid IN (line 56)
实现:
- from app.core.db_guard import assert_safe_to_destroy
- 各函数入口(任何 execute 前)调 assert_safe_to_destroy(db, reason=...)
- reason 字符串含表名/操作类型便于追踪
- 测试库(_test 后缀)放行; 真实库无 allow_real_db 拦截
测试:
- test_test_db_guard.py 7 测试通过(守卫本身行为)
- governance 非DB测试 35 通过(无回归)
- L2 真实流程需DB, 待部署后生产验证
2026-10-08 11:19:21 +08:00
699c7bda08
refactor(analytics): 拆分 today.py 主题域到 today/ 包 ( #226 )
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
S-P0-3: today.py 3574 行 → 7 文件包(薄 __init__.py + 6 子模块)
- _kpi.py: today_kpi (380 行)
- _trend.py: today_hourly_trend (457 行)
- _reception.py: today_reception_breakdown (954 行)
- _refund.py: _ensure_service_tickets_for_unmatched_refunds + today_refund_breakdown (367 行)
- _queue.py: _recurring_matches_today + today_queue (158 行)
- _staff.py: today_staff_efficiency (1180 行, 单函数不再拆)
- __init__.py: 薄 facade re-export 8 个顶层名字 (38 行)
纯物理切片, 不改实现:
- 头部 72 行 import 块复制到各子模块, 加 # ruff: noqa: F401, I001 抑制
- 函数体内惰性 import (from app.api.v1.complaints / SimpleNamespace / re 等)
随函数体原样保留
- analytics/__init__.py 第 193-202 行 from today import 零改动
测试:
- test_complaint_specialty_merge.py: _pkg_src() 改 rglob 递归读子包
- 67 非DB 测试通过; DB 测试待部署后验证
最大子模块 _staff.py 1180 行仍超 800 行, 但单函数不易再拆,
取舍说明见 #226 评论。
2026-10-08 11:10:36 +08:00
20a0442e29
docs[skip-issue]: 开发计划v2——新增5项用户需求+事件时间轴基础设施
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
v2 变更:
- 新增 B1.5 经营事件时间轴标注(跨看板基础设施,incidents表扩展+ECharts markPoint)
- B2 新增 AI报告策略调整(daily改实时) + AI数据供给层
- B3 新增 退款按设备销售天数 + 分品类运营看板 + 投诉扩展维度 + 人员管理重设计
- 择机升级 数据表合理性专项评估
- 关键路径: today.py→事件时间轴→客户域建表→夜间预警→架构演进
- 总数 40→45 项,批次 4→5
2026-10-08 10:31:55 +08:00
68dc1ec61c
docs[skip-issue]: 下一步开发计划与排期(剩余40项·4批次+12择机)
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
基于 R/S 两份报告消化现状(P0 8/9, P1 ~44%, P2 ~24%)排期:
B1 风险闭环剩余(today.py拆分/db_guard/缓存/测试)
B2 AI与采集健壮性 B3 功能深化(客户域建表/夜间预警)
B4 架构演进(指标字典/夜间批处理/导航单源)
关键路径: today.py拆分→客户域建表→夜间预警→架构演进
2026-10-08 10:02:29 +08:00
8a69d3bf94
docs[skip-issue]: 阶段性升级总结报告(2026-09-01~10-09,570提交/195工单)
...
gitleaks / gitleaks (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / commit-message-lint (push) Has been cancelled
功能/性能/合理性三维度盘点;fix(205)>feat(193),治理优先于堆功能。
REF 性质,登记 docs/README §六。
2026-10-08 09:56:21 +08:00
08f5ad894d
#225 窄屏适配: 全局 flex-between 换行 + 筛选器换行
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
768px 下 .flex-between 自动换行(标题+按钮行/共N条+分页行),
.page-container .el-card .flex 筛选器允许换行.
一次改动覆盖 72 个使用 .flex-between 的页面,
列表页(tools/management)无需逐页改 DOM.
2026-10-08 09:21:30 +08:00
6cd5f455ec
fix(analytics): 修复 #222 金额 Decimal 化遗留的 float/int += Decimal 500
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
#222 把 16 模型 37 列金额改成 Decimal(Numeric) 后,部分累加器仍初始化为
float(0.0)/int(0),与 D(...) 返回的 Decimal 做 += 时抛 TypeError,导致
运营看板/退款看板/今日看板多个接口 500。
根因: 计算路径金额累加器初始化类型未同步 Decimal 化。
修复范围(金额累加器统一 D(0),出参边界 float(round(...))):
refund.py:
- refund_recovery_analysis: abandon_amount/fail_amount/recharge_amount/
success_amount: 0.0 → D(0)
- refund_decision_overview 内嵌 _comp_window_totals: comp_amt/online_amt:
0.0 → D(0)(前周期回零分支同步)
- routes_daily/cash_flow_wide: total_amt = 0 → D(0)(2 处)
- 挽回金额估算 est_amt: 0.0 → D(0)(2 处)+ reason_fail_avg else 分支
- amount_buckets 金额分桶: amount: 0.0 → D(0)
today.py (today_hourly_trend):
- A/B/E 路线金额: float(r.amt) → D(r.amt)(C/D 路线已返回 Decimal,
此前混用导致 _fill_list 里 float += Decimal 炸)
- _fill_list: 金额路径 total=D(0)、默认值 D(0)、出参 float(round(...))
- e_amt_list 出参: float(round(D(0),2))
- 补 import D / Decimal
非金额累加器(work_score/eff_score/时间加权等)保持 float,不改动。
验证: py_compile 通过;所有 += D(...) 左侧均确认初始化为 Decimal。
2026-10-08 09:04:50 +08:00
94eee39e7c
feat(web): 全局移动端框架 + 今日看板窄屏试点 ( #225 )
...
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
替不在工位的值班主管保住「登录 → 找菜单 → 看核心数字 → 下钻」最短路径。
不为移动端重写 UI,仅保证桌面信息架构在 ≤768px 视口下可用、可读、可点。
落地范围(工单 #225 第一层):
- 全局(main.css):统一 768px 断点(与 Element xs/MainLayout/useToday 同源),
跨页窄屏规则——page-container 收窄、el-dialog/message-box/popover/
select-dropdown 限宽 92vw、el-table 表体惯性滚动
- MainLayout:汉堡按钮触控目标 44px(iOS HIG),面包屑限宽省略号,
quick-tools 允许收缩,页脚窄屏换行(抽屉菜单 #46 已就绪,本次只补强)
- Login:卡片 400px→min(400px, 92vw),标题字号下探
- Today(试点做透):KPI 5列→2列网格、趋势图勾选组独占一行、值班条/影响
事件 chip 换行、表格字号下探+横向滚动、筛选栏允许换行、双列卡片窄屏堆叠
设计说明已按规范三层落地:main.css/MainLayout/Today 文件头注释块 +
页面 ⓘ 气泡沿用现有(Today 各卡已有)+ 桌面端零改动(纯 media query 覆盖)。
取舍(按方案登记):
- 不做全量 75+ 页响应式重写(仅全局框架 + Today 试点)
- 不新建 useViewport composable(仅 2 处 JS 判断,语义不同,避免过度抽象)
- 不做手势开关抽屉 / 不为窄屏删 KPI 卡 / 不为效率表做卡片式重排
- 不改后端 API 契约
验证:npm run build 通过;4 文件各仅 1 处 768px media 查询,无断点漂移;
桌面 DOM/JS 零改动。
2026-10-08 08:18:13 +08:00
8c141ad44d
docs(#210,#222): 台账回填 S-P0-2 阶段A / R-P1-8 完成
backend-test / commit-message-lint (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
2026-10-08 07:16:44 +08:00
f4ab9bb144
feat( #222 ): 金额定点化(方案 C′:元 Decimal,禁二进制浮点入计算)
...
- 16 模型 37 列 Mapped[float]→Mapped[Decimal](排除 merchant_refund.rate
费率与 device_flow_usage.flow_gb 流量两个非金额例外)
- upsert._core 新增 _to_decimal(float 经 str() 取最短十进制,字符串经
归一化解析);金额列调用点 30 处 _to_float→_to_decimal
- analytics._core 新增共享 helper D():计算路径定点化唯一入口
- refund/cash/reconcile/reconcile_check/quality_check/quality_disposition
计算路径 Decimal 化;出参边界(JSON/Excel/JSONB)float(...) 保持对外
JSON number 契约不变
- diff_preview Numeric 列转换 _to_float→_to_decimal(消除精确比较误报)
- AGENTS.md 铁律第 4 条修订:分/整数 → 定点数(元 Decimal)+ 例外登记
- 全量 pytest 2128 全绿,ruff 全绿
2026-10-08 07:15:56 +08:00
21f16dd83a
feat( #210 ): 注册表引用完整性主表 + 启动 fail-fast 校验(方案A 阶段A)
...
- 新增 app/services/registry_index.py:能力位主表 RegistryEntry 从
DATA_TYPE_REGISTRY/UPSERT_DISPATCH/DATA_SOURCE_SEEDS/_MODEL_REGISTRY
四处动态汇聚,CAPABILITY_NOTES 单一事实登记 14 条例外
- validate_registry_integrity() 启动自检:缺映射/分析表漂移/name_cn
重名/失配无登记/冗余登记 五类违例汇总抛 RegistryIntegrityError
- main.py create_app() 顶部接入(纯内存校验,不依赖 DB)
- tests/test_registry_index_210.py 9 例:现状零误报 + 六类构造违例 fail-fast
2026-10-08 07:15:44 +08:00
4256e1caef
docs( #219 ): 客户域主数据与设备360 架构 LOCKED v1.0(人工批准,送审稿转正)
...
据用户 2026-10-07「执行吧」人工批准,送审稿由 _pending 移入 docs/arch/ 正式编号
A-20261007-002 并标 LOCKED v1.0(git 识别 rename,内容仅头部状态变更)。
_pending 恢复 4 份草案;arch 正式文档 50→51(README 只维护模板/SOP 与 _pending
计数,无正式清单,无需改)。
台账 S-P0-4/S-P1-12/S-P1-13 标「🔶 设计已过审,实施待 B2/B3/B5」——本 LOCKED
只锁定设计,不含建表/迁移/代码;实施按 v0.7 批次另开工单。tag 留发布流程。
2026-10-07 23:41:22 +08:00
15c64e4443
docs( #218 ): 台账 R-P1-7 回填已完成(LOCKED v1.1,9bda284)
2026-10-07 23:37:43 +08:00
9bda284a56
docs( #218 ): A-20260910-004 导航 LOCKED 升版 v1.1(人工批准,回写已落地现状)
...
据 #218 人工审核批准(用户 2026-10-07「执行吧」)将修订草案并入正式 LOCKED:
- 一级 7 组现状(看板/专项分析/运营工具/管理闭环/数据分析/AI与分析/系统管理);
删工作台组(/dashboard /agenda 路由保留)、#80 命名、新增 AI 与分析扁平6项;
- 系统管理 3→4 子分组(新增数据质量);
- 消解 v1.0 两处自相矛盾:设备流量审计、分析视图/视图构建器唯一归「数据分析」;
- manager=supervisor∪specialty_lead 术语;新增 §4.2 发布期菜单可见性(#221 );
- 测试基线更新(menu 12 + MenuTree 6 + release 矩阵 5)。
纯文档回写,零代码/path 变更,不回滚代码。_pending 草案并入后删除(单一真身,
草案见 020423f/43a86fb 历史)。LOCKED git tag 留给发布流程统一打,本次不打。
2026-10-07 23:37:09 +08:00
75c3e9139f
docs( #219 ): 客户域主数据与设备360 架构送审稿(设计门,不建表不写迁移)
...
S-P0-4/S-P1-12/S-P1-13,v0.7 专题 A / 总规 #199。代码实查后纠偏"从零新建"
口径,三块资产已具雏形:merchants 表已存在但名称即键且设备未建链;标签三表
已完备(含批次/状态/失效期)仅缺 source_table/source_id 且 analytics 零消费;
设备360 已有 /devices/{sn} KPI + /timeline 跨表时序雏形(21 表带 device_sn)。
设计:客户=设备SN(ADR G5),四层上卷 + 商户号/店铺两条独立通路;设备类型维度→
merchants 演进+device_merchant_links(SCD)→套餐字典(别名键含商户+生效期)→
360只读MVP→标签事实化(补来源列+部分唯一约束+可下钻)→B5 shops/enterprises。
建链门控:匹配率报告+人工校对+低置信不建链+原文快照;只增不改不删、可回滚。
含 ER 概要/86表影响面/回填策略/B2 第一批切片/界面五要素/6 点待裁决。REQ,_pending。
2026-10-07 23:27:35 +08:00
43a86fbea5
docs( #218 ): v1.1 草案补 R7 发布期菜单可见性条款(承接用户拍板,实现见 #221)
2026-10-07 23:22:54 +08:00
7c619c1a0e
feat( #221 ): release 构建菜单「管理员见全部、其他角色维持原权限」
...
用户 2026-10-07 拍板的临时发布策略。原判据 debug_only 仅由构建档位决定:
release 下对所有人隐藏(连 admin 也看不到运营工具/管理闭环/数据分析)。
改为 buildVisible(): debug_only = isDebugMode || role==='admin'。
- release+admin:可见全部 7 组及 debug_only 子项(仍受各项 roles 约束);
- release+supervisor/specialty_lead/agent:可见集与改动前逐字节一致,不新增入口;
- debug 构建行为不变。
仅菜单入口层(桌面/移动共用 MenuTree,一处两端生效),不动 MENU_GROUPS/path、
路由守卫与后端权限,深链可达性维持原状。
新增 MenuTree.release-visibility.spec.js(vi.mock env 固定 release,admin/
supervisor/specialty_lead/agent 矩阵 5 例);菜单相关 23 例 + 前端全量 115 例全绿,
eslint 干净。
2026-10-07 23:22:53 +08:00
020423f8ee
docs( #218 ): A-20260910-004 导航 LOCKED v1.1 修订草案(人工审核门,不改 LOCKED)
...
R-P1-7 / R 报告 §3.1 W1。盘点 menu.js 单一源 + router(80 path) + menu.spec.js
(12 测试全绿) vs LOCKED v1.0(2026-09-10),出 15 条差异表 + v1.1 条款式修订草案。
定性:代码/测试演进均有已批准工单背书(#44/#63/#67/#80/#137/#140/#157/#165/#208
等),非乱改;真问题是 LOCKED 未随决策升版,且 v1.0 内部两处自相矛盾
(设备流量审计、视图构建器/清单归属在 §2.1/§2.2/§四 三处说法不一)。
主漂移:删「工作台」组(/dashboard /agenda 留路由不可见)、#80 改名今日值班/运营
分析看板、新增「AI 与分析」一级组(扁平6项)、系统管理 3→4 子分组(新增数据质量)、
设备流量审计与视图系统一归数据分析。历史 path 只增不改不删,深链无断裂。
建议以现状回写升 v1.1、不回滚代码;列 5 点待人工终审。未改 LOCKED 原文件。
2026-10-07 23:00:14 +08:00
dd5beafa7c
docs( #217 ): 金额单位「元→分」迁移评估(决策门送审,只评估不实施)
...
R-P1-8 / R 报告 §3.3 W4。AST 实盘盘点:全链路自洽用「元」——外部源给元 →
_to_float → 37 个 Numeric 金额列/16 模型(36×(12,2)+fee(12,4)),ORM 全
Mapped[float],服务层 float() 聚合/比较并靠 ≥10 处 round(,2) 兜底,API/对外
external.py 透传,前端 formatMoney + 就地 toFixed 按元展示,无任何元分换算。
定性:DB 定点无损、量级有限,生产未爆精度事故;属合规/脆弱性债而非止损项。
给三方案:A 全量迁分(整数,8~15人日,字面合规,但 fee(12,4) 是硬障碍);
B 双字段过渡(不推荐);C′ 维持元但 Python 全 Decimal 化 + 登记铁律豁免
(3~6人日,直击 float 风险源,不动前端/对外契约/历史数据)。
列待拍板 4 项;拍板前不改任何金额字段/计算。
2026-10-07 22:51:50 +08:00
64086c5518
chore( #216 ): 提交信息工单号门禁(commit-msg hook + CI)+ 台账补录 #191~#197
...
R-P2-9 治理无号提交(不追溯历史,只管新增):
1. 判据单一来源 scripts/check-commit-issue.sh:
- --message-file(本地 hook)/ --range(CI)/ --head-only(CI 回退)
- 放行:信息含 #数字、显式 [skip-issue]、merge 提交;违规逐条列出后退出 1。
- 临时 git 仓正/负/区间/空区间/skip/merge/--no-verify 全部实测。
2. scripts/install-commit-msg-hook.sh:安装 .git/hooks/commit-msg 调用判据脚本
(与 gitleaks pre-commit 独立;判据随仓库更新无需重装)。
3. .gitea/workflows/test.yml 新增 commit-message-lint 轻量 job(纯 git 不依赖 docker):
push 用 before..after、PR 用 base..head;两端不可达/新分支/force-push 回退仅查 HEAD,
保证不误阻断主干。
4. docs/standards/issue-workflow.md §1.5 + README 补提交规范与钩子安装说明。
5. 台账新增 §十七,据 Gitea API + git log 考据补录 #191~#197:
#192/#194/#195/#197 closed 且均有关闭凭证评论;#191/#193/#196 open(附 commit/文档依据)。
R-P2-9 回填已完成。
2026-10-07 22:39:03 +08:00
5fef31e90c
fix( #213 ): 治理 async 路由内同步阻塞事件循环(R-P1-10)
...
AST 盘点 app/api:9 个「假 async(函数体无 await)」+ 22 个真 async。
1) 纯同步阻塞且不依赖事件循环 → 改同步 def(FastAPI 自动 run_in_threadpool):
- deps.get_current_user / require_role._check:全站每请求最高频,db.get() 同步 ORM
- uploads.confirm_import / preview_diff:pandas + ingest_table 批量 upsert
- staff_assignments.import_snapshot:pandas xlsx 导入
2) 虽无 await 但内部 asyncio.get_running_loop().create_task 挂后台任务,必须留在
事件循环线程 → 保留 async def,仅把唯一同步阻塞点包 run_in_threadpool:
- ai_onboarding.create_draft(占位行 DB 插入)
- governance.create_scan / create_judge(占位行 DB 插入,await 提交后再入队保证可见)
- fetch.start_schedule(仅内存态判断+调度,无阻塞 IO)
3) uploads.parse_upload 保留 async(需 await file.read),save_upload_file /
parse_file(pandas) 改为 await run_in_threadpool。
验证:ruff app+tests 全绿;onboarding/governance 定向 44 项通过;全量 pytest exit 0。
不做全仓 async 化(守工单不做项)。台账 R-P1-10 回填。
2026-10-07 22:21:32 +08:00
ea9f936df5
docs( #215 ): 文档索引计数实盘校正 + 仓库入库规范挂入 AGENTS 铁律14
...
- AGENTS.md 新增铁律14「文件入库分类纪律」,显式引用《仓库入库规范》,
补齐 META 三层强制链(R-P1-6「标而不管」闭环)
- docs/README.md §六(升 v2.2):
- §6.1 13 份 → standards 14 份 + 根 AGENTS = 15 约束源;补登仓库入库规范;
4 处 ⚠️ 待挂核实已挂 → ✅ (铁律 8-11),另校正 alembic=铁律6、文档性质=文末段
- §6.2 arch 48 → 50 份锁定(_pending 4 份待审)
- §6.3 API ~374 → 449 路径 / 547 操作(app.openapi 实盘)
- §6.4 删除已解决的「4 份 META 无强制入口」并重编号
- arch/README.md:_pending「20 份」改为「初始 20 / 当前实盘 4」(R-P2-2)
- API接口文档.md 升 v1.2:头部计数按 openapi 校正 + 附可复算命令(R-P2-8)
- 台账 R-P1-6/R-P2-1/R-P2-2/R-P2-8 回填已完成及实盘证据
实盘 2026-10-07:standards META14/REQ3/REF5=22;arch 锁定 50、_pending 草案 4;
FastAPI 449 路径、547 操作(448 /api/v1 + /health)。
2026-10-07 21:52:45 +08:00
105955e94a
ci( #211 ): ruff 门禁扩 app/ + 新增空库 alembic upgrade head + gitleaks 收窄
...
- ruff: tests/ -> app/ tests/;清零 app/ 存量 6 处(isort×4 + unused import×2),
并修复合并扫描才暴露的 tests/test_llm_outage_event 多余空行
- CI(test.yml): 新增独立空库 ci-pg-mig(POSTGRES_DB=csctalon_dev) 上的
alembic upgrade head + heads 门禁;连接参数用命令级 env 前缀,不污染
conftest 以 setdefault 从 TEST_POSTGRES_* 回填的 pytest 连接
- 修复真实迁移链断裂: a1c2e3f4d5b6 无条件 DROP CONSTRAINT uq_cs_record_business
在全新空库报 UndefinedObject(该约束建表迁移从未创建,仅由 create_all 带出)。
改为先查 pg_constraint 存在才 DROP,空库/存量两路径最终 schema 一致
- gitleaks 收窄: workflows 由 .* 通配收窄为仅精确放行 test.yml;backend/tests
由"整目录全规则免疫"收窄为仅 hardcoded-phone-number/hardcoded-strong-password
两条夹具启发式的规则级豁免,内置密钥规则与 scrm/shanmao 业务凭证规则对 tests
仍全量生效(正/负向实测验证)
- 文档: alembic-migration 规范升 v1.2,增空库可重放门禁(§八-6)与"修复可重放性
缺陷可改已提交迁移"例外(§八-7);README 记录本地门禁命令
Refs #211
2026-10-07 21:38:07 +08:00
727d327e51
fix(config): compose 库名默认安全侧化 + 清理死配置/死代码 ( #214 )
...
R-P2-13(compose 默认指向真实库名):
- docker-compose.yml db 服务 POSTGRES_DB 默认 ${POSTGRES_DB:-csctalon}
→ :-csctalon_dev,未显式注入时绝不兜底真实库名 csctalon(AGENTS 铁律)。
backend settings 的 POSTGRES_DB 本就是占位符(未配即拒启);
生产按 env.production.example 显式 POSTGRES_DB=csctalon + APP_MODE=prod,
启动期「模式↔库名」硬校验,不受默认影响。
R-P2-14(env 死配置 / 死代码 / 键漂移):
- 删 settings.TALON_API_KEY 死字段:全仓 grep 无 app/alembic 引用
(接 talon 实际走 LLM_API_KEY/LLM_BASE_URL),model_config extra=ignore,
存量 .env 残留该键无害;
- 删 config/guard.py 无调用方的 assert_db_matches_profile 死函数及其在
config/__init__.py 的导入与 __all__ 导出(启动期实际调用 check_db_matches_profile
在 settings 内收集问题后统一报错);
- 程序化比对 4 份 env 模板与 settings 78 个字段:无残留死键
(APP_MODE 为模式入口、*_DATA_DIR/ENV_FILE/WEB_BUILD_MODE 为 compose 插值/构建
变量,均非 settings 字段,属正常)。
验证:ruff 全绿;ENV=test 导入 app.core.config 正常(死函数不再导出、死字段已删、
test settings 实例化通过);pytest --collect-only 全量测试模块导入无错;
test_runtime_mode_framework 31 例通过(dev/test/prod 库名启动守卫)。
回填台账 R-P2-13/R-P2-14。
2026-10-07 20:46:26 +08:00
a984cb99e3
fix(deploy): 消除 Gunicorn -w 4/-w 1 矛盾,部署三处统一单 worker ( #212 )
...
Dockerfile.patch(应急补丁镜像,FROM csctalon-backend:latest 仅覆盖代码)
内嵌 CMD 为 -w 4,与主 Dockerfile / docker-compose.yml 的 -w 1 冲突。
核实部署路径:compose `build: ./backend` 且用 command 显式覆盖为 -w 1,
patch 不被 compose 引用;但 patch 被直接 `docker run` 时其内嵌 CMD 生效,
会破坏单 worker 前提(R-P1-9)。
- Dockerfile.patch: -w 4 → -w 1;
- Dockerfile / docker-compose.yml / Dockerfile.patch 三处补注释,写明单 worker
与进程内状态的关系:llm_scheduler 的进程级 threading.Condition 门闸(_gates,
任一时刻仅 1 个 LLM 调用,多 worker 会并发打爆单 ollama 端点)、
SyncTaskManager 同步互斥、core.rate_limit 登录限速进程内计数;多 worker
水平扩容须先迁 Redis,v0.7 明确「立此存照、不做」;
- 顺手纠正 app/core/rate_limit.py 陈旧「Dockerfile -w 4」注释。
验证:全仓 grep 三个真实启动点唯一 -w 1(其余为说明性注释/第三方 reference/);
docker-compose.yml YAML 合法(backend command -w=1);ruff 全绿。
回填台账 R-P1-9。
2026-10-07 20:36:28 +08:00
314b29e9ea
docs(台账): 回填 R-P2-6 L-3 打标脚本已落地 ( #209 , #220 )
...
#209 打标脚本 + #220 bootstrap 切库缺陷修复均已完成关单;生产 3 行随发布
执行后回贴。
2026-10-07 20:30:54 +08:00
7a7bc250d9
feat(scripts): L-3 录入错年份(2031)手工退款打标治理脚本 ( #209 )
...
新增 scripts/mark_manual_future_year_209.py(经 scripts/_bootstrap 进入):
- 扫描 manual_refunds 中 register_time >= 2030-01-01(+08:00) 的异常年份行;
- 默认 dry-run 只读,打印 id/单号/金额/register_time/当前质量状态;
- --apply 时先过行数安全门(--expected,默认 3,不符即 SystemExit 不落库)
与 db_guard 真实库护栏(生产须显式 --allow-real-db);
- 写 quality_status='dup_residual' + quality_note(注明 #207/治理日期、
register_time 原值不改、行未删可回滚,截断 512);
- 幂等:已是 dup_residual 的命中行不重复更新;register_time 与金额保持不动。
可测纯函数 future_year_rows/split_pending/enforce_count/mark_rows,5 例单测。
test 库端到端实测(#220 修复 bootstrap 切库后):造 3 行 2031 数据(¥203)
→ dry-run total=3 → --apply 打标 3 行 → SQL 核验 status/note/原值保留 →
--expected 5 正确拒绝 → 再 --apply 幂等 0 更新 → raw SQL 清理造数,全绿。
全量 pytest exit 0、ruff 全绿。
生产随发布执行:--mode prod --apply --allow-real-db 后回贴 #209/#207。
2026-10-07 20:27:49 +08:00
ad7ddf5604
fix(scripts): bootstrap 首次 import app 前先设模式环境变量,修复切库失效 ( #220 )
...
bootstrap() 旧顺序先 `import app.core.config.profiles`,而导入任一 config
子模块都会触发 app/core/config/__init__.py 在导入期实例化 settings
(settings.py:91 _PROFILE=resolve_profile(...) 与 :462 模块级 settings,
经 lru_cache 固化);之后才在 :109 设置 APP_MODE/POSTGRES_DB,已来不及。
后果:bootstrap(mode="test"/"prod") 返回的 Session 实际仍连默认 dev 库
(backend/.env),使所有靠 bootstrap 切到非默认模式的脚本(含生产治理)
可能打错库。gunicorn 服务与 pytest 因环境由外部先注入而不受影响,故长期未暴露。
将 APP_MODE/POSTGRES_DB 与 sys.path 设置前移到任何 `import app.*` 之前;
真实库护栏语义不变。新增 2 个全新子进程回归测试锁定(settings 进程内单例,
同进程无法复现首次导入):test→csctalon_test、dev→csctalon_dev,且剔除
继承的模式变量以真实模拟脚本仅靠 --mode 切库。
全量 pytest exit 0、ruff 全绿。
2026-10-07 20:27:34 +08:00
917b069a01
docs( #208 ): 回填台账 R-P2-5 已完成(88afb30,生产3对人名随发布核)
2026-10-07 19:50:03 +08:00
88afb306cb
feat( #208 ): 外部投诉同源判重 v5 落地(机判成组+人工复核闭环)
...
③A 渠道簇同簇仅作旁证 / ④C 登记日取最早、处理人分类取最新有效行 /
⑤A 全自动打标+置信度,疑似内嵌前文(再次来电)降 needs_review 人工复核。
- 新表 complaint_same_source_groups/members(打标不删源数据,一行投诉至多一有效组)
- 迁移 t0u1v2w3x4y5(接真 head l3m4n5o6p7q8,修单一 head)
- 服务:同SN + 文本相似≥0.7(Dice/SequenceMatcher) + 14天窗,并查集连通分量;
质量排除复用 stats_qualityinclude(NULL 安全);rebuild 幂等(人工组锁定)
- admin 路由 /admin/complaint-same-source(groups 列表/详情/PATCH 终审/run 重算)
- 离线脚本 scripts/run_complaint_same_source_groups.py(bootstrap,默认 dry-run)
- 管理端「同源案件复核」页(数据质量组,含设计说明五要素+取舍)
- 测试 17 例全绿;后端全量回归 exit 0;dev 库 dry-run:1660行→21组(19 auto/2待人工)
2026-10-07 19:47:18 +08:00
4893aea4cf
docs(ledger): 台账登记 #205 执行中发现的 4 项既有问题(待编号入批) #205
2026-10-07 19:03:26 +08:00
dfa48c76ae
docs(ledger): 台账回填 #205 关闭状态(R-P0-1 9/9页, R-P1-5 部分完成) #205
2026-10-07 19:02:09 +08:00
bcfba58b34
feat(dashboards): ReconcileCheck.vue 对账复核页看板双强制补齐 #205
...
- 文件头设计说明五要素 + 页面可见ⓘ(含取舍 3 条) + 6 卡卡级ⓘ(来源/口径/时点/空含义)
- 口径追至 reconcile_check.per_route_alignment(复用 _cash_routes_total/_classify_report,
D 路 #201 canonical 排除、匹配 E 不限窗口 e_data_until 警示)
- 下钻声明: 差异/断链/空悬带窗口下钻(既有); 总笔数/已对齐/A∩D 不下钻理由显式声明
2026-10-07 19:00:52 +08:00
f704c01249
feat(dashboards): refund 组 8 页看板双强制落地(卡片ⓘ+下钻+设计说明三层) #205
...
- 8 页文件头「设计说明」五要素注释块 + 页面可见ⓘ(含取舍段) + 卡级ⓘ(来源/口径/时点/空含义)
- 可下钻卡片带时间窗口与筛选条件经 URL 透传(RefundDashboard 壳页承接 tab/start/end)
- 无独立明细接口的卡片在注释+ⓘ 显式声明不下钻理由(§3.3)
- 口径全部追至 analytics/refund.py、refund_chain.py、cash.py、reconcile_check.py 真实实现
- 发现既有问题(未扩范围): ReconcileLines 未被引用; DeviceAnalysis 接待次数分桶语义;
PersonnelAnalysis 汇总行字段后端不返回; RefundRecovery KPI 副标题与后端口径不一致
2026-10-07 18:57:30 +08:00
cd4d2debc5
docs(b0): B0 批次规划产物入库 + 台账回填工单映射 (#199,#200~#207)
...
- 规划设计-v0.7.md / 两份审查报告(来源 R、来源 S)入库,docs/README.md 索引登记
- 台账 §十五/§十六 回填 B0 工单映射与状态:
#200/#202/#203 已关闭;#201(S-P0-1) 已关闭 c410c15
#204 进行中(3630c30 , 影子期);#205 进行中(含 R-P1-5);#206/#207 决策门 open
2026-10-07 18:50:33 +08:00
3630c30537
feat(ai): #193/#204 预生成「无新事件跳过」P1+P2 落地(dry_run 影子期)
...
- 新增 ai_pregen_freshness: scope→事件源水位判定(fail-open)+五类保底+三档开关
- warm_on_sync_complete 接入: 节流后判定, pregen_skipped 留痕(含 throttled_20min)
- settings 三参数: AI_PREGEN_SKIP_MODE(dry_run)/MAX_STALE_MINUTES(120)/灰度 scope
- 管理端: /admin/ai/templates +4 字段(_refresh_stats 口径单源); AiTemplates.vue
4 列 + 文件头设计说明 + 最近判定ⓘ(含取舍段, 三层落地)
- 测试 13 例: 有/无新事件、五保底、fail-open、worker 三档端到端、管理端对账
- P3(enforce 定参)延迟: 需影子期 ≥3 天数据, 凭证见工单
2026-10-07 18:38:18 +08:00
6e9602ecee
feat(ingest-incident): 熔断事件 Incident 自动登记 + 恢复自动平息 #203
...
- 新增 ingest_incident_service: 采集类 Incident 登记/平息下沉 service 层,
与 data_arrival 告警共用同一幂等键(同源同业务日只登记一次)
- alerts.py _auto_register_ingest_incident/_close_ingest_incident 委托 service(签名不变)
- bms_scheduler: 熔断触发登记 Incident(站内信+Incident 同事务 commit,
修复原站内信未提交即 close 丢失); 半开探测恢复/手动 reset_halt 自动平息
- 测试: service 幂等/平息留痕 + 调度器接线全链路(熔断→Incident→恢复→平息)
2026-10-07 18:05:33 +08:00
eb040dd646
fix(llm-scheduler): 5 处同步直调 LLM 收口统一调度队列 #202
...
- extract/semantic/time_parse 收口 L2(实时问答), canary_smoke L1(admin 触发),
governance D7 L0(凌晨体检); 门闸 key 取档位链首, 路由与降级纪律不变
- 修正 llm_scheduler.py 文件头失真注释, 登记全部调用点与门闸约定
- 新增 test_llm_scheduler_convergence_202: 五链路过调度器+级别断言、
同门闸并发单飞(峰值=1)、门闸释放无泄漏
- 修存量测试遗漏: A路口径断言补 #201 新增 quality 排除条件;
db_guard 接线锁改 AST 检查(py3.14 fixture 不可 getsource);
llm_tiers 断言补 governance 档(c721c8e 遗漏);
注册 LlmRoleConfig/LlmRoleMember 到 models/__init__(修复测试库缺表)
2026-10-07 18:05:15 +08:00
c410c15025
fix(quality): quality_status 排除口径单点化并对齐 A路/refund_wide #201
...
S-P0-1: 经营统计排除 dup_residual/ignored 原有 5+ 份并行字面量实现,
A 路(线上直退)/C 路(闪猫) 与 refund_wide 行范围存在敞口(打标后仍计入
经营数字)。本次:
- quality_check 新增 canonical: STATS_EXCLUDED_QUALITY_STATUSES +
stats_quality_excluded_sql() + stats_quality_include(),全仓禁止第二份
- analytics/_core: A/B 路 ORM 过滤补排除(today/metric_registry/对账同源)
- analytics/cash: C 路 agg/trend 补排除;D 路旧字面量改 canonical 别名
- analytics/refund: 决策概览 routes_daily/_window_totals/导出汇总 A/B/C
共 9 段裸 SQL 补排除(明细 sheet 按设计保持全量)
- wide_table_builder(refund_wide): 行范围三路 + 残留子查询 + 批次组装
三处排除治理行(RefundWide 无 quality_status 列,只能 builder 侧过滤)
- cash_flow_wide_builder/quality_disposition_service/reconcile_check:
字面量全部改引用 canonical
- quality_disposition ignore/restore 新增后置钩子:复用治理链
_post_execute 重建 refund_wide/cash_flow_wide + 失效缓存(质量页直接
处置此前会造成宽表陈旧)
- 新增 tests/test_quality_exclusion_parity_201.py: 单源锁定 + ORM 聚合器
vs 裸 SQL 概览四路 count/amount 等价性 + refund_wide 行范围 + 钩子;
相关 87+50 既有测试全绿,ruff 通过
2026-10-07 17:11:13 +08:00
6d7813c2d5
fix(llm-providers): 清除硬编码内网 IP 默认值与占位符 #200
...
R-P0-2:LlmProviders.vue 三处 192.168.10.21(测试连接 placeholder、
testForm 默认 base_url、新建 provider 默认 base_url)改为中性占位/置空,
由使用者按实际节点填写;模型名不含内网信息,保留为便捷默认。
遵守铁律 #3(禁止内网 IP 进代码)。
2026-10-07 16:39:59 +08:00
d12e00e1a3
refactor(ai-menu): 接入中心 Tab4 迁入「AI 与分析」+ 菜单按依赖层次重组
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Failing after 33m30s
backend-test / frontend-lint-test (push) Successful in 3m1s
问题: ① 接入中心 Tab4「AI 与模型」与「AI 与分析」组割裂, 两处模型配置分散;
② 组内 6 项命名不一(H "LLM 模型接入"=页面"LLM 多 Provider"=Tab4"模型接入"
一物三名; "提示词关注点"≠页面"AI 助手提示词"), 顺序按想到的排而非依赖。
重组为扁平 6 项, 顺序即依赖层次(模型→提示词内容→提示词治理→产物):
1. 模型接入与调度 (原 LLM 模型接入; 底部并入 Tab4 的 .env 兜底配置只读区块)
2. 报告模板 (原 AI 报告模板)
3. AI 助手关注点 (原 提示词关注点; 顶部并入 Tab4 的提示词四层总览)
4. 提示词评估 (原 提示词评估中心)
5. 提示词版本 (原 提示词版本管理)
6. 报告历史 (原 AI 报告历史)
- 命名口径统一: 菜单名 = 路由 meta.title = 页面标题(三处对齐)
- 接入中心删除 Tab4, 回归纯数据对接(①登录态/②外部Token/③推送配置包)
- /system/ai-admin 路由保留可达(不破坏书签), 不再挂菜单
- 新增 api 复用: LlmProviders 引 getAiModelConfig; AiFocusConfigs 引 prompt-layers/preview
- 更新 menu.spec 断言(扁平结构 + 依赖顺序 + 命名口径), 110 前端测试全过
2026-09-29 11:36:50 +08:00
f54a98ce3e
refactor(menu): AI 与分析按依赖层次重组, 取消 Tab 容器消除重复
...
gitleaks / gitleaks (push) Successful in 8s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
原 #137/#141 做 3-Tab 整合入口(AI 管理), 后各 Tab 陆续补独立菜单,
导致 AiTemplates/LlmProviders 同一组件既在 Tab 内又挂在侧栏(100% 重复)。
重组为三层结构:
接入层: LLM 模型接入(Provider/角色降级/健康/云端策略)
提示词层: 报告模板 → 关注点 → 评估中心 → 版本管理
查看侧: AI 报告历史
- 删除"AI 管理"菜单项(Tab 容器废弃, /system/ai-admin 路由保留可达)
- 新增"提示词关注点"菜单项(/system/ai-focus-configs, 原 Tab③ 补独立入口)
- 其余各项不动, 无组件代码改动
2026-09-29 10:59:21 +08:00
df20a9c0df
fix(notify): WebhookChannel 补飞书 payload + 前端机器人类型选择
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Failing after 4m34s
backend-test / frontend-lint-test (push) Successful in 2m20s
- 后端: WebhookChannel 按 config.provider 分流 payload 格式
(飞书 msg_type/content.text vs 企微/钉钉 msgtype/text.content),
飞书 200 响应体 code/StatusCode 非 0 也判失败
- 前端: 通道表单加"机器人"下拉(飞书/企微/钉钉), code 提示
system_alert 用于系统告警外发
2026-09-29 10:40:49 +08:00
c91c4a4568
feat(observability): 事件降噪/error 特权角标/系统告警外发/日志保留期
...
gitleaks / gitleaks (push) Successful in 8s
backend-test / backend-lint-test (push) Failing after 4m39s
backend-test / frontend-lint-test (push) Successful in 2m8s
承接 9-29 事故排查: 机制骨架完整但"最后一公里"失效(1168 条未读淹没 95 条
error, 系统故障只写库不外发, 日志表无保留期)。
降噪(1):
- record_system_event: info 级默认不进未读(仍入库留痕), 仅 warning/error 亮
角标; 新增 badge 显式覆盖、dedup_minutes 通用去重(DB 判定, 跨进程/重启有效)
- ai_report_worker 三类 pregen 失败加 30min 去重(昨晚 3 模板×半小时刷 64 条)
- event_summary 增加 unread_error/unread_warning 分级计数
error 特权(1):
- 顶栏铃铛与侧栏角标: 有未读 error 时红色 danger、显示 error 数、图标变红、
tooltip 提示"X 条严重故障待处理"
系统告警外发(2):
- 新增 notification_channel.dispatch_system_alert_async: error 事件后台线程
推群机器人(优先 code=system_alert 通道, 回退任意启用 webhook), 无通道静默;
不做静默时段(系统故障夜间也必须送达), 不阻塞业务提交
日志保留期(3):
- 新增 log_retention_service: events/llm_logs 90 天、操作审计 180 天、
工单动作与同步日志 365 天, ctid 分批 DELETE(5000/批)
- bms_scheduler 每天 03:15(北京)执行, 按北京日幂等
测试 test_llm_outage_event 扩到 11 例(降噪/去重/外发/保留期), 全过。
2026-09-29 10:30:29 +08:00
b7213893fc
fix(llm): 网关 key 失配致全量 403 的治理补漏(2026-09-29 事故)
...
gitleaks / gitleaks (push) Successful in 8s
backend-test / backend-lint-test (push) Failing after 4m42s
backend-test / frontend-lint-test (push) Successful in 2m2s
事故: talon 库重建后 csctalon 持有 key 失配, 401/403 被当 permanent
立即抛出 —— 不计数/不摘链路/不发事件, 14h 100 次失败管理面零感知。
- llm_client: 401/403 计入连续失败(达阈值 unhealthy, 成功自愈)并发
error 级系统事件(auth_failed); 全链不可用发 chain_unavailable;
同事件 30min 去重; .env 兜底通道同样覆盖
- 菜单: 侧栏补「LLM 模型接入」直达入口(原仅 AI 管理 Tab 内嵌可达)
- 接入页: 警示条说明 SN 在 talon 侧创建 key 时指定、重建后需重新授权
- 事务通知: category=llm 分类与红色标签
- 测试: test_llm_outage_event 9 例(鉴权事件/去重/全链失败/400 不误报)
2026-09-29 09:34:35 +08:00
c721c8e7f4
feat: AI 数据治理扩充(午间轻量扫描 + D7 语义探测 + 采集后 AI 分类 + governance 角色)
...
gitleaks / gitleaks (push) Successful in 8s
backend-test / backend-lint-test (push) Failing after 36m41s
backend-test / frontend-lint-test (push) Successful in 9m18s
- 午间轻量扫描:每日 12:00-13:00 自动跑 D1+D2(秒级,不调 LLM)
- D7 语义探测器:AI 辅助检测退款原因与内容矛盾,经 talon qwen3.8:27b
- 采集后 AI 分类:同步完成后自动调 D7 检查误分类,30min 节流
- governance 角色:治理 LLM 调用走角色配置(allow_cloud=false,永不出域)
- resolve_tier("governance") 替代 provider_code=None
- 管理界面可调治理用哪个模型,无需改代码
2026-09-28 14:38:49 +08:00
f1203e643f
docs: 对比报告补充R2压缩输入下完整节点x模型性能与质量矩阵
gitleaks / gitleaks (push) Successful in 8s
backend-test / backend-lint-test (push) Successful in 9m52s
backend-test / frontend-lint-test (push) Successful in 2m5s
2026-09-28 08:17:34 +08:00
7b6353ebc2
docs: R1 vs R2 输入压缩对比报告(速度/显存/质量三维度)
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 8m17s
backend-test / frontend-lint-test (push) Has been cancelled
2026-09-28 08:07:15 +08:00
15c4064b88
docs: 生产日志审计报告追加R1vsR2输入压缩对比实验(先压缩再推理方法论验证)
gitleaks / gitleaks (push) Successful in 7s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
2026-09-28 08:01:17 +08:00
358102a52f
docs: 更新生产日志审计(补跑3060-7b + KV参数根因)
...
gitleaks / gitleaks (push) Successful in 8s
backend-test / backend-lint-test (push) Failing after 10m9s
backend-test / frontend-lint-test (push) Successful in 50s
- 3060从官方源拉取qwen2.5:7b, 发现q4_0 KV在长上下文下触发repeat-limit+复读乱码
- 改回q8_0后正常(20.2s/620tok/30.7t/s), 结论: q4_0仅用于14b场景, 需按模型粒度配置
2026-09-28 07:37:13 +08:00
4078707958
docs: 生产日志审计 + 节点/模型评估 (395+, 72h)
...
gitleaks / gitleaks (push) Successful in 8s
backend-test / backend-lint-test (push) Failing after 10m26s
backend-test / frontend-lint-test (push) Successful in 42s
- coder30b 设计分析脚本, 27b/30b 审核修复统计解析 bug, 9 节点并行分析, coder30b 终审
- P0: talon-gmk-qwen3.8-27b provider 持续不可用回退 deepseek 出域42次; mac-mini/3060 节点失联
- P1: /admin/models/deepseek 500+JSONDecodeError; /v1 404 (3060缺7b/3b)
- 合规缺口: csctalon provider 回退直连 deepseek 绕过 talon 出域闸口
- 节点评估: 4090-coder30b 95t/s 最佳; gmk 27b/122b 长文汇总系统性提前收尾(约450tok)
2026-09-28 07:23:32 +08:00
e58839dece
docs: R3 算力深度评估报告 —— 模型×节点×题型全覆盖
...
gitleaks / gitleaks (push) Successful in 8s
backend-test / backend-lint-test (push) Failing after 10m3s
backend-test / frontend-lint-test (push) Successful in 2m1s
- 20 (节点,模型) 单元 × 7 gen_system 题型 = 140 单元 + 260 次能力微基准, 全部经 talon
- gen_system 73/140、微基准 235/260(90.4%)
- 甄别"无据数字"口径: 区分模型自算比率与真幻觉; 给出按任务类型的分流建议
- 同模型跨节点对比验证节点效应(27b 在 gmk 优于 4090; coder30b 反之)
- 截断根因为输出预算不足(1200→4000 三轮收敛), 非显存
2026-09-28 00:36:34 +08:00
99841af0a3
docs(reports): 提示词 v2.1 迭代 4 份报告入库 ( #197 )
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Successful in 9m41s
backend-test / frontend-lint-test (push) Successful in 2m16s
- 终版报告 / 跨节点模型对比
- 节点性能与模型能力评估 / 3060 节点降级执行方案
(两份成稿于 GPU 修复前, 顶部已加 20:30 状态更新横幅;
3060 实际可跑 14b+8K, 结论以新一轮深度评估为准)
2026-09-27 20:46:53 +08:00
354c18f40d
fix(tests): 修两处存量 CI 兼容问题 ( #197 )
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Successful in 25m59s
backend-test / frontend-lint-test (push) Successful in 3m35s
- ai_report.matched_count 加 server_default="0", 与迁移 a0b1c2d3e4f5
口径对齐: 裸 SQL INSERT 在 create_all 建表(CI 全新库)上不再 NotNull 违反
- test_model_selection_guard: 硬编码 parents[3] 改为向上查找仓库根,
CI 容器 /app 布局下无兄弟仓库时 skip 而非 IndexError
2026-09-27 20:01:23 +08:00
e175646661
style(tests): ruff import 排序修复 4 处存量 I001 ( #197 )
...
gitleaks / gitleaks (push) Successful in 8s
backend-test / backend-lint-test (push) Failing after 9m58s
backend-test / frontend-lint-test (push) Successful in 2m38s
CI backend job 只跑 ruff check tests/, 存量 import 未排序导致红灯;
本次 --fix 仅调整 import 顺序, 105 相关测试通过
2026-09-27 19:44:37 +08:00
a67bb76e05
feat(ai): gen_system v2.1 落地——派生字段系统侧注入 + 122b 生成降级修正 ( #197 )
...
gitleaks / gitleaks (push) Successful in 8s
backend-test / backend-lint-test (push) Failing after 4m45s
backend-test / frontend-lint-test (push) Successful in 2m14s
- external _build_brief: 注入 recon_gap_amount/recon_gap_pct(E 对账差,
非环比) 与四路 routes.*.amount_pct/count_pct(占退款总额/总笔数);
凡工具能算的不写成对模型的要求, 消除模型自算派生数字出错(铁律 12a/12d)
- llm_tiers: 122b 补充降级注释——gen_system 报告生成考场仅 3/7
(伪造逐日明细+13 处无据数字), 不如 mini-14b 5/7
2026-09-27 19:32:46 +08:00
fa3dc5eb34
docs(standards): 新增仓库入库规范 v1.0-rc + 退款日期缺陷治理报告
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Failing after 29m14s
backend-test / frontend-lint-test (push) Successful in 4m12s
- 六类分类(C1~C7)+决策树+8条补充规则, 经 3 轮 14b 模型扫描验证 + 1 轮工具确定性检测 + 30b 终审 approved
- .gitignore 补 workdata/work-data/certs/.workbuddy 四条规则
- .workbuddy/memory/ 移除跟踪(工具私有状态, 属 C7)
- workdata/complaint_date_judge_prompt.py 归位 scripts/(C2 耐用工具)
- #196 治理报告落 docs/reports/(C3 结论报告)
Refs: #196
2026-09-26 11:39:56 +08:00
5f5a67acd2
fix(sync): 退款登记日期两类缺陷修复 —— YY/MMDD 解析 + 空日期邻近填充 ( #196 )
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Failing after 37m23s
backend-test / frontend-lint-test (push) Successful in 2m38s
29 行/轮固定 failed 清零:
- 28 行 '26/0920' 紧凑形态: _to_datetime 与审计侧 parse_doc_datetime
新增 ^\d{2}/\d{4}$ 严格分支(非法月日由 datetime 构造器拒判),
冻结语料重算 date_unparseable 29→1
- 1 行空日期(登记明细 row 2413): sync 入口前 _fill_empty_register_time,
仅当上下最近可解析日期为同一北京自然日才取上一行原值填充并打标记,
证据不足保持空走既有拒收流程(30b 终审认可的加严规则)
验收: pytest 79 点全绿; 30b 终审 A1-A4 全 pass, issues=0
2026-09-26 01:08:43 +08:00
63498ef426
feat(audit): 退款数据审计服务 —— 采集对账+日期跳跃+负数检测 ( #196 )
...
冻结语料(2026-09-25 金山文档导出+manual_refunds快照)上钉死计数:
- 采集偏差: 文档5031行 vs 库9589行, matched=4213
doc_only 800 = 文档内重复768(757为两sheet重叠,库正确去重) + 29日期无法解析等
db_only 5376 = 5281已打标治理残留 + 95其他; 真丢行仅5, 库侧无法解释25(含TEST行)
- 日期跳跃: 5处>7天, 4处成对行序颠倒(info), 1处表头首行真跳变(warn)
- 负数金额: 文档与库两侧均为0, pass
方法论: 凡工具能算的全确定性(本模块), 14b审阅轮的8条反对意见经
pytest冻结计数验收(9/9)全部证伪——模型审阅幻觉, 以测试为准。
2026-09-25 23:13:08 +08:00
90b48c17b5
feat(sync): 315投诉日期治理 —— 确定性恢复+批次传播+提示词沉淀 ( #195 )
...
- complaint_date_recovery.py: 自由文本日期恢复服务(候选打分+语义信号+年份推断)
- complaint_date_pipeline.py: 入库管线集成(单行恢复+批次年份传播)
- complaint_sync.py: 日期列为空时自动调用恢复服务(external/card 均覆盖)
- test_complaint_date_recovery.py: 24条 pytest 全通过(含金标准回归)
- complaint_date_judge_prompt.py: 小模型提示词沉淀(qwen2.5:3b/7b 可用)
冻结语料验证:3563 行恢复 3465 行(97.2%),硬缺陷 98 行标记 pending。
金标准 12/12 通过,0 误报。
2026-09-25 22:02:37 +08:00
8dda34a528
chore(deploy): .dockerignore 注明核验脚本的发布口径与生产配方(防两处白名单漂移) #193
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Failing after 4m34s
backend-test / frontend-lint-test (push) Successful in 34s
实测: upgrade.sh/package.sh 的发布白名单(L121-123 只放行两个登录脚本)会删掉
backend/scripts/*, 故 .dockerignore 的 !例外 只在"直接 docker build"时生效。
已在注释里写明生产配方(两文件 docker cp 进容器后再执行)与"两处白名单会漂移"的风险。
2026-09-24 19:00:11 +08:00
4e0a31a631
chore(ai): 交付 #191/#193 到期核验脚本(只读, 一条命令出凭证依据) #193
...
gitleaks / gitleaks (push) Successful in 8s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
#191/#193 的关闭条件需要时间窗证据(连续 2 日自动采集 / 7 天观测表), 到点核验。把核验做成
可复现命令, 避免"到点手敲 SQL"与"凭证不可复现"。
- backend/scripts/check_ai_issue_gates.py: `--issue 191|193 --allow-real-db`
191: ①连续 2 自然日 scheduled total_rows>0 ②check_freshness lag≤1 且 stale=false
193: 按模板生成/hit_mode 命中/matched_count 交叉校验 + 源表 30min 零新增桶占比 +
是否存在 pregen_skipped(判断 P1 是否实施 → 决定分支 A/B 验收)
退出码 0=通过 1=未通过(打印差在哪); 全程只读(SELECT)
- ⚠️ 不写死 mode(实测踩坑): 写死 prod 时本地运行会把库名默认成真实库名 csctalon,
输出"模式=prod 库=csctalon"却连的是本地同名库 → 凭证自欺。现 mode 读 APP_MODE,
并打印实际连接目标(host:port/db/mode)
- backend/.dockerignore: 放行该脚本与 scripts/_bootstrap.py(核验须在生产容器内跑,
只读且不含凭证; 与登录脚本例外同性质)
2026-09-24 18:57:14 +08:00
ee2aae9b79
docs(ai): #193 实现方案 —— AI 报告"无新事件则跳过"刷新策略 + 文档索引登记 #193
...
gitleaks / gitleaks (push) Successful in 8s
backend-test / backend-lint-test (push) Failing after 4m27s
backend-test / frontend-lint-test (push) Successful in 41s
- docs/design/20260924-#193-AI报告刷新策略-无新事件跳过-实现方案.md(性质 REQ):
基于 09-18~09-24 观测(512 份预生成 vs 命中 90 次; 30min 零新增桶 39.2%/38.7%)设计
"自上次快照以来无新事件则跳过"机制: 水位判定(按 scope 选事件源) + 五类保底
(当日首份/超 120min/手动触发/判定异常 fail-open/上份失败) + off/dry_run/enforce 三档
+ system_events 留痕 + 管理端 4 列(含界面五要素与三层落地要求)
- 明确取舍: v1 用全局 settings 不加列(免迁移); 不做刷新成本看板; 不做逐模板频率编辑器
- 列前置核实项(时间列口径一致性/索引缺失/判定耗时), 避免编码前猜字段
- docs/README.md §6.2 需求(REQ)索引登记该文档
2026-09-24 18:49:04 +08:00
3d1258ef1b
fix(ai): 命中记账补 L2 语义命中(原先只记 L1, matched_count 偏低 32%) #193
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Failing after 4m25s
backend-test / frontend-lint-test (push) Successful in 41s
验证逻辑审计发现: matched_count 只在 L1(_wrap_row)自增, L2 语义命中不计 →
生产实测 7 天"L1 口径 61 份 vs 用户实际拿到命中 90 次", 偏差 32%; 而 #193 的
"刷新频率 vs 命中量"决策与验收判据(matched_count 不下降)都建立在该指标上。
- ai_report_matcher 新增记账唯一入口 bump_hit_accounting(_wrap_row 与 ai.py L2 分支共用)
- ai.py: L2 命中选定后补记一次; 被 #192 门槛拦下的空壳仍不计(未交付给用户)
- 测试补 2 例: L1/L2 同入口各 +1; 空壳被拦不计入
2026-09-24 18:42:01 +08:00
1cd8f19367
fix(sync): 时效巡检改"基准日随采集小时"判定, 消除每天上午的误报 #191
...
验证逻辑审计发现的自身缺陷: E 路上游 T+1, 正常在 D 日 15:00 采到 D-1; 而巡检原来一律以
"今天"为基准 → D+1 06:05 时最新 D-1 会被算成落后 2 天 → **每个上午都误报一次 stale**。
- FRESHNESS_TARGETS 增加 expected_by_hour(与该源 options.low_freq_hour 一致, E 路=15)
- check_freshness: 当天未到该小时 → 基准日=昨天(采集窗口还没到, 不判超期); 已过 → 基准日=今天
(该跑的没跑就报超期, 真漏采仍能抓住); 返回体补 baseline/pending_today/expected_by_hour
- now 形参供单测注入固定时刻
- 测试补 4 例: 早晨不误报 / 16:05 抓真漏采 / 阈值边界 / **漂移守卫**(巡检小时必须等于
采集种子的 low_freq_hour, 防两处口径分叉)
2026-09-24 18:42:00 +08:00
f42fcb1cc1
fix(sync): 低频档回填改看低频源自身采集记录, 不再用高频槽位推断(防 E 路 15:00 档漏采) #191
...
gitleaks / gitleaks (push) Successful in 8s
backend-test / backend-lint-test (push) Failing after 6m32s
backend-test / frontend-lint-test (push) Successful in 33s
自查发现的缺陷: E 路档位小时改为 15:00 后落在高频时段(8:00~24:00)内, 而低频档回填原用
"今天该小时有高频槽位成功" 推断"低频档已跑" → 容器在该小时内重启时会把当天 E 路档误判
为已执行而整天漏采(部署日志实测出现「低频档回填: 今日 15:00 已执行过」属误判)。
- 抽出纯函数 low_freq_done_from_logs(rows, sched_map, ...): 判据 = 低频源自身的
scheduled 成功记录 ∩ 今天 ∩ finished 小时 == 该源配置小时
- 回填时查 bms_sync_logs(data_type ∈ low 源) 而非复用高频 slots_done
- 测试补 2 例: 高频源/昨日的记录不算; 失败或缺 finished_at 不记 done
2026-09-24 18:19:30 +08:00
d1f1162c23
fix(ai): 命中门槛收紧为"问什么就必须有什么" + 占比/字数双判据 #192
...
gitleaks / gitleaks (push) Successful in 8s
backend-test / backend-lint-test (push) Failing after 4m56s
backend-test / frontend-lint-test (push) Has been cancelled
生产验证发现: 仅用"缺失占比>=50%"仍会放行 ai_reports#609 这类快照 —— 它只把「四路」写成
"暂无"(缺失 1/4=25%), 而用户问的正是"四路分布和涉诉怎么样", 仍会拿到无四路数字的回答。
- _asked_dims: 从问句中提取"明确问到的维度"(模板 required_dimensions 中关键词出现在问句里的)
- _snapshot_unusable 增加"问到维度缺失即拦"判据(置前), 保留占比>=50% 与正文<150字的兜底
泛问(未问到任何维度)不触发该规则 → 周/月报快照不会因锚点结构差异被全部拦下(防命中率塌方)
- list_candidates 增加 question 形参(L2 候选同判据); ai.py L2 调用点透传问句
- 测试补 3 例(问到缺失必拦/泛问不拦/占比边界)
2026-09-24 18:14:02 +08:00
6c369a7162
fix(sync): E 路低频档按源可配小时/窗口 + 0 行与时效告警(补 5bf4325 漏提文件) #191
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 5m52s
backend-test / frontend-lint-test (push) Has been cancelled
生产实测(bms_sync_logs + 数据入库日): E 路(refund_record_report)上游 T+1 且次日白天才发布,
而低频档固定 06:00 + 窗口"昨天整天" → 每次 scheduled 都"成功但取 0 行"(total_rows 恒 0),
表内数据全靠人工手动触发, 连断两天无人知晓(事故 #190 )。故本源配置:
low_freq_hour=15(挪到上游发布之后, 仍每天 1 次) + window_days=3(吸收 T+N 抖动与失败重试)。
- bms_fetcher/_fetch.py: E 路种子 options {low_freq_hour:15, window_days:3} + 实测依据注释
- sync_source_config_service.list_low_freq_schedules: 读低频源 options 档位参数(非法值忽略)
- bms_scheduler: 低频档改按源小时分组触发(done 键=日期+小时; 回填与忙顺延语义保留);
_do_scheduled_sync 新增 data_type_filter(只跑到期源) / window_days_map(按源覆盖为近 N 天)
- source_freshness_service(新): ①scheduled 档 0 行告警(仅"应有数据"白名单源, 按源+当日节流)
②关键业务时间列"最新值距今天数"巡检(超阈值告警); 调度器每小时 05 分巡检一次
- bms_sync: 空结果分支接入 notify_empty_result("成功但 0 行"不再静默)
- ai_glossary: 退款四路口径卡补 E 时效(T+1, 按数据截止日表述, 禁止写"E 未提供")
- tests/test_source_freshness.py: 10 例
2026-09-24 18:07:31 +08:00
a579138f67
fix(ai): 命中链路加"空壳快照"门槛, 不再返回 74~88 字的"暂无"回答 #192
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
生产实测 ai_reports#731/#678/#632: hit_mode='section' 且正文仅 74~88 字, 全文只有
"**四路分布**:暂无/未提供 / **涉诉**:暂无/未提供" —— 命中了正确模板但内容是空壳,
且照常计入 matched_count, 掩盖真实命中质量。
- ai_report_matcher: 新增 _snapshot_unusable(缺失占比 >= 50% 或正文 < 150 字 → 判定空壳)
+ _light_report(抽取复用); L1 命中返回前过门槛(拦下则继续找下一份), L2 候选列表同样过门槛
- 拦下时不再 bump matched_count, 并把缺失清单写入 ai_reports.latest_missing(可统计被拦次数)
- 用"占比+字数"而非"全维度齐全": 避免 detail 体裁(周/月报 9 维度)锚点结构差异导致全部拦下
→ 命中率塌方、每次问答走 30~50s 实时 LLM(#192 与 #191 同批上线的依赖原因)
- tests/test_ai_matcher_gate.py: 9 例(判据边界/端到端跳过空壳/命中完整快照/候选剔除)
2026-09-24 18:07:10 +08:00
5bf4325bb4
fix(sync+ai): E 路时效闭环 —— 采集档位按源可配小时/窗口 + 0行与时效告警 + AI 侧 T+1 口径 #191
...
生产实测(synced_logs + 入库日): E 路(refund_record_report)上游 T+1 且**次日白天才发布**,
而低频档固定 06:00 + 窗口"昨天整天" → 每次 scheduled 都"成功但取 0 行"(total_rows 恒 0),
表内数据全靠人工手动触发, 连断两天无人知晓(事故 #190 )。
- bms_fetcher/_fetch.py: E 路种子 options {low_freq_hour:15, window_days:3}(挪到上游发布后, 仍每天1次)
- sync_source_config_service.list_low_freq_schedules: 低频源 options 读档位小时/窗口天数(非法值忽略)
- bms_scheduler: 低频档改按源小时分组触发(done 键=日期+小时, 回填与忙顺延语义保留);
_do_scheduled_sync 新增 data_type_filter/window_days_map, 支持按源覆盖为"近 N 天 0 点~now"
- source_freshness_service(新): ①scheduled 档 0 行告警(仅"应有数据"源) ②关键业务时间列
"最新值距今天数"巡检(超阈值告警, 按源+当日节流); 调度器每小时 05 分巡检
- AI 侧 E 时效口径: brief.refund 新增 actual_cashflow_e_until(数据截止日) + _spec/glossary
声明"E 上游 T+1, 当日为 0 属采集时序", 禁止再写"E 未提供"
- tests/test_source_freshness.py: 10 例(白名单/巡检 lag 与 stale/档位参数/口径声明)
2026-09-24 18:07:10 +08:00
08ee98844c
fix(ai): feed 改按段预算裁剪, 业务段(四路/涉诉/三源/E)不再被前缀截断 #189
...
gitleaks / gitleaks (push) Successful in 8s
backend-test / backend-lint-test (push) Failing after 4m31s
backend-test / frontend-lint-test (push) Successful in 44s
原 _trim_feed 是 raw[:limit] 字符前缀截断(生产 AI_ANALYZE_MAX_CHARS=3500), 而业务段被
排在恒量段之后 → refund 段内 a_route(涉诉)/actual_cashflow_e(E 对账)恒被切掉、reception/
complaint 整段随机丢失, 日报大量"数据未提供"; 段顺序还随 PYTHONHASHSEED 变化。
- _plan_feed_sections/_dump_feed: 整段取舍(始终合法 JSON) + 段优先级(业务段/窗口元信息必保)
+ 被省略段登记到 _trimmed_out_sections 并写进 prompt, 区分"超预算未下发"与"系统无此数据"
- _select_feed_sections: 段顺序确定化(不再依赖 set 迭代) + 业务段前置
- _build_feed: refund 段内 a_route/actual_cashflow_e 提前; queue 纳入恒量段(原先被永久丢弃)
- build_messages 新增 feed_limit; worker 按窗口放宽(daily 7000 / monthly 9500 等,
按实测全量 + 余量, 当前数据量下无段被省略)
- 新增 backend/tests/test_ai_feed_trim.py(12 例, 含跨进程 hash seed 一致性)
2026-09-24 17:33:52 +08:00
c57c5941db
docs(arch): A-20260901-001 升 v1.1 —— 补记 _merge_trend 形状容错与卡片趋势形状统一
...
gitleaks / gitleaks (push) Successful in 8s
backend-test / backend-lint-test (push) Failing after 3m35s
backend-test / frontend-lint-test (push) Successful in 36s
前次提交(e6ce77d )已把变更理由写入 commit message; 本次补上文档同步
(上次因该文件被 IDE 外部改动覆盖未能落盘, 重新加载后写入成功)。
- 状态升 LOCKED(v1.1); 被测函数契约补记「首参同时接受元组/Row 与 {day,count} 字典」
- 测试设计表补 T5(字典形态) / T6(旧事故嵌套写法) 两例
- 新增「五、v1.1 变更」章节: 动因(形状不对称复发面 + _trend 同形诱饵)、
变更内容、测试护栏有效性证据
- 后续跟进: 已删 _trend 死代码; 已统一 card.trend 元素形状为 {day,count}
(新增 test_complaint_card_trend_shape.py 锁定), 并说明 _overview 的
card_trend 为另一字段(前端在读, 不属本次范围)
- 明确留待产品决策项: card.trend 两接口**统计口径**仍未统一
(analysis=补充表净增 / summary=工单侧卡商+净增), 测试只锁元素键不锁数值
2026-09-24 10:55:29 +08:00
e6ce77d752
refactor(complaint): 删 _trend 死代码 + 统一 card.trend 元素形状为 {day,count}
...
gitleaks / gitleaks (push) Successful in 8s
backend-test / backend-lint-test (push) Failing after 3m6s
backend-test / frontend-lint-test (push) Successful in 36s
承接上一步「_merge_trend 形状容错」后的两项收尾(2026-09-24):
1) 删除 `_trend()` 死代码(_helpers.py 定义 + complaints/__init__.py 导入与 __all__)。
它返回 [{day,count}],与被 _merge_trend 接受的首参形状冲突,是本次事故的"同形诱饵";
已核实全仓无调用方。删除后诱饵消除(且 `_merge_trend` 已容错字典, 双重保险)。
2) 统一 `card.trend` 的元素形状为 {day,count}:
- 原 `_analysis` 直接透出 `_suppl_trend_rows` 的 {day,total,overlap},而 `_summary`
为 {day,count} —— 同一字段路径两处元素键不同,消费端按 count 取会**静默拿到 None**。
- 现 `_analysis` 改为 `_merge_trend([], _card_suppl_trend)` 生成, 与 /summary 同形。
- 本例说明: `_overview` 用的是**另一字段名** `card_trend`({bucket,label,registered},
前端 useComplaintAnalysis.ts 读 label/registered), 不属本次范围。
- ⚠️ 仅统一**元素形状**, 未改**统计口径**: `_analysis`=补充表净增(不含工单侧);
`_summary`=工单侧卡商+补充表净增(#65 )。两处代码自述亦互相矛盾
(_analysis 称"工单表含卡商 0 行", _summary 称"28 条/近3月"), 统一口径属产品决策。
测试: 新增 tests/test_complaint_card_trend_shape.py(3 例)锁定两接口元素键一致;
投诉域 256 测试全过; ruff 干净(仅 2 处既有无关文件的 import 排序告警, 未动)。
注: 同步更新 docs/arch/A-20260901-001(LOCKED) 的尝试未能落盘(该文件疑似被 IDE
外部改动覆盖, 磁盘校验哈希与 HEAD 相同), 故本次将变更理由记录于此提交信息。
2026-09-24 10:49:27 +08:00
f4f41da2d3
fix(complaint): _merge_trend 首参形状容错, 关闭"输入输出不对称"复发面
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Failing after 3m33s
backend-test / frontend-lint-test (push) Successful in 37s
背景: 2026-09-01 生产 500(21 次)根因是 _merge_trend 被嵌套调用 ——
内层返回 [{day,count}] 字典列表, 外层按 [(day,count)] 元组解包,
拿到字典键名 → int('count') 抛 ValueError。当时已改调用点为「补充表相加后
一次合并」, 但函数本身"吃元组、吐字典"的形状不对称仍在, 只靠注释与单测守护。
加固: 首参同时接受元组/Row 与 {day,count} 字典, 把误嵌套/误复用返回值
从"崩溃"降级为"结果仍正确"(向后兼容, 现有调用点行为不变)。
测试: 新增 T5(字典形态) / T6(旧事故嵌套写法); 已实测 T6 在旧实现下复现出
与生产完全一致的 ValueError: invalid literal for int() with base 10: 'count',
加固实现下返回正确结果 —— 证明是真实护栏。投诉域 253 测试全过, ruff 干净。
文档: 该函数契约由 docs/arch/A-20260901-001(LOCKED) 锁定, 按其风险清单
「签名变更须同步」要求升到 v1.1, 并记录两项留待决策的同款隐患
(_trend 死代码、card.trend 三接口元素结构不一致)。
2026-09-24 10:34:23 +08:00
77519755ea
fix(audit): 操作日志记录真实客户端 IP + 拦截扫描器探测路径
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Failing after 8m35s
backend-test / frontend-lint-test (push) Successful in 32s
问题(生产安全排查 2026-09-24): operation_logs.ip 对公网请求一律记成 docker
网关 172.21.0.1, 审计无法按来源 IP 追溯/排查爆破。链路:
公网 → 云端 openresty → frps 隧道 → frpc → 本机 127.0.0.1:80 → 网关 → 容器
云端 openresty 用 $proxy_add_x_forwarded_for(追加), 内网 nginx 亦追加,
后端取 XFF 末段 = 网关; 且客户端自带的假 XFF 会被保留在链首(可伪造来源 IP)。
修复(信任边界收敛到边缘 + 内网覆写):
- 云端 openresty(proxy/root.conf): XFF 改「覆写」$remote_addr(丢弃客户端伪造值),
新增自定义头 X-Real-Client-IP=$remote_addr(隧道不改写)
- 内网 csctalon-web(web/nginx.conf): 新增 map 净化客户端 IP ——
· 对端落在 docker 私有网段(隧道流量) → 取边缘注入的 X-Real-Client-IP;
边缘未注入记 "unknown"(明确标示链路未就绪, 不静默记网关误导排障)
· 对端为局域网/ZT 真实地址(直连) → 用对端地址, 忽略客户端自带的任何 XFF
/api/ 与 /uploads/ 均改为下发净化后的单值(后端取末段即该值, 逻辑无需改动)
- 后端注释对齐新不变量(auth._client_ip / operation_log 中间件)
附带: csctalon-web 拦截扫描器常见探测路径(.env/.git/wp-*/xmlrpc.php/phpinfo/
laravel.log/_vti_ 等) 直接 404 —— 原先落到 SPA 回退返回 200 index.html,
虽无泄露但污染日志并给扫描器"路径存在"假信号。
验证(生产, 临时容器实测): 隧道分支伪造 XFF 被丢弃/边缘值优先; 直连分支忽略
伪造 XFF; 扫描路径 9 例全 404, 正常路径(SPA/静态资源/API/favicon/uploads)无回归。
2026-09-24 10:08:37 +08:00
140fb52ee7
feat(web): 新增「提示词版本管理」页面(治理研判 / 接入起草)
...
gitleaks / gitleaks (push) Successful in 8s
backend-test / backend-lint-test (push) Failing after 54m29s
backend-test / frontend-lint-test (push) Successful in 1m0s
配合后端 governance/onboarding 提示词全量版本化:
- 新增可复用组件 PromptVersionManager:版本表格(版本/名称/生效状态/
激活人时/解决什么·参考)+ 新建版本草稿(change_reason 必填 +
references 影响内容)+ 激活/回滚确认 + 内容查看
- 新增页面 /system/prompt-versions:两个 tab
· 治理研判(judge_system / judge_user)
· 接入助手起草(draft_system / draft_user)
AI 报告 gen/extract 两类含金丝雀冒烟,仍在评估中心区块⑤管理,
页面内交叉链接,避免重复入口
- 路由 + 菜单注册(AI 与分析组,admin-only);构建通过,lint 0 error
2026-09-24 00:00:38 +08:00
ce05ff68f4
fix(auth): 反向 SSO 未配置时不再外跳 panel 404,JWT 过期回本站登录页
...
事故(2026-09-23 生产): 用户 JWT 过期后被自动跳到
https://panel.kimirock.top/sso/csctalon 看到 Express 404。
根因: 跨仓库集成只做了一半 —— csctalon 侧实现了「面板→本系统」反向 SSO
消费方(/api/v1/auth/sso + netmgr_sso.py), 但 netmgr 面板侧签发端
GET /sso/csctalon 从未开发, 生产也未配置 NETMGR_SSO_SECRET;
而前端 axios 401 拦截器无条件跳 /api/v1/auth/sso, 后端无票无条件 302
到默认 PANEL_URL → 必然 404。
止血(开关默认安全侧):
- 后端 sso_enter 无票分支: NETMGR_SSO_SECRET 未配置 → 307 回
/login?expired=1, 不外跳
- 新增公开(免鉴权) GET /auth/sso-config: 回 netmgr_sso_sso_enabled 开关
- 前端启动预取开关; 401 拦截仅在开关启用时走面板续期, 否则清本地态
回 /login?expired=1&redirect=原路径
- 登录页 expired=1 时显示「登录已过期」提示
测试: +4 用例(开关两端点/有票无票重定向), test_sso_ticket 14 全过
2026-09-23 23:54:02 +08:00
214bfe4a11
feat(ai): governance/onboarding 提示词全量版本化 + 周月报去除小时粒度
...
gitleaks / gitleaks (push) Successful in 8s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
需求1 - 提示词全量版本化管理(与 prompt_configs #178 同模式):
- governance_prompts / onboarding_prompts 从「单行 UPDATE 覆盖」升级为
「多版本行 + is_active 部分唯一索引」,旧版本保留可回滚
- 新增 change_reason / references / activated_at / activated_by 四列,
每版迭代附注「解决什么问题 / 影响什么内容 / 参考依据 / 激活人时」
- 新增 governance_prompt_service / onboarding_prompt_service
(load/create/activate/rollback/list),load 逻辑零改造(仍取 is_active=true)
- 新增 admin 路由 /admin/governance/prompts、/admin/onboarding/prompts
- 迁移 l3m4n5o6p7q8:加列 + 删 code 旧 unique + 建部分唯一索引,存量行保持生效
- 至此三类提示词(extract/gen、judge、draft)全部纳入版本账本
需求2 - 周/月报不关注小时粒度:
- ai_feed _enrich_with_daily_trend 月报路径(span>14)不再嵌 hourly_summary
(用户明确不关注按小时数据;小时粒度对月报是噪音且占 token)
- 周报/月报保留按日全展开 daily_trend(cap 31) + staff_top5 等明细
- 报告仍 02:00 半夜执行,数据量大但按日明细全部分析
测试:
- 新增 governance/onboarding prompt service 各 12 用例(24 全过)
- conftest _engine fixture DROP 两表后 create_all 重建,修复测试库
旧 schema 缺列 + 旧 code unique 约束导致的 InFailedSqlTransaction
- ai_feed_compact 两条用例改为断言 hourly_summary 不再嵌入
- 聚焦回归 204 用例全过,ruff 干净
2026-09-23 23:30:29 +08:00
c6fc1e4b89
fix(ai): 扩容 ai_reports.window_label 64->255, 修复滚动报告落库失败
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Failing after 1h7m13s
backend-test / frontend-lint-test (push) Successful in 1m2s
生产端到端验证发现: rolling_7d 预生成 window_label 形如
「预生成(template=rolling_7d_compare, snapshot=..., rolling_7d(近7天·
截止昨天 2026-09-16~2026-09-22))」约67字符, 超 varchar(64) 触发
StringDataRightTruncation, LLM 已正常出稿但 save_report 静默失败
(自然周/月报 label 短未暴露)。该列纯说明文本, 扩到 255 根治;
新增迁移 k2l3m4n5o6p7(alter column)。
2026-09-23 22:12:31 +08:00
54f10f6699
feat(ai): 滑动周/月对比报告 + 日报 feed 补齐专项环比 yoy
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
需求(2026-09-23): 每天凌晨出一份「截止昨天近7/30天 vs 前一等长周期」滚动对比;
完整周报/月报仍周一/1号出整周期; 日对比维持今日0点~当前 vs 昨日同刻。
数据层补环比(原 feed 仅接待总数/退款总额笔数3个顶层数有 yoy, 专项明细缺失):
- today_kpi 透出已算好的前周期四路 cash_routes_prev(原 routes_y 算了未透出)
- _complaint_enhanced 增 prev 窗口三源 registered/handled/backlog(同源实现不漂移)
- refund_decision_overview summary 增涉诉率/金额/笔数前周期值
- _build_brief 统一计算 prev 窗口(实时=昨日同刻, 区间=等长平移)并接线四路/三源/涉诉
滑动窗口与命中:
- ai_report_window 增 rolling_7d/rolling_30d(截止昨天近N天, 长度恒定, 无退化)
- worker 调度零改动: 新模板 frequency=daily+trigger_at=02:00 每天到点,
period=rolling_* 决定窗口, refresh_on_sync=false 不随30分钟同步刷新
- 迁移 j1k2l3m4n5o6 幂等 seed rolling_7d/30d 两个对比模板
- matcher 区间候选新增窗口贴合度 Jaccard 排序(置于 period 匹配前),
解决「近7天环比」被时间解析强制 weekly 后误命中自然周报; 大窗口天然低分
- admin/前端: rolling period 不被自动判为随同步刷新; 模板页补 rolling 选项
测试: 新增滚动窗口/Jaccard/滚动命中用例, 更新 brief 契约断言; 全量 pytest 通过。
2026-09-23 21:57:10 +08:00
be9e6ba361
fix(ai): 周维度快捷问改为「上周」, 修复默认问题必走 live 不命中快照
...
gitleaks / gitleaks (push) Successful in 8s
backend-test / backend-lint-test (push) Failing after 4m50s
backend-test / frontend-lint-test (push) Successful in 51s
生产实测(ai_reports hit_mode 证据): 快捷问「本周接待量和各专项会话设备…」
历史 5+ 次全部 hit_mode=live。根因: 周报 weekly_compare_last 每周一 02:00
才生成上一周快照(trigger_at=1-02:00, 窗口截止昨天), 周中问「本周」窗口
(周一~今天)与上周快照不重叠 → L1 候选=0 → L2 无候选 → 实时 LLM 兜底。
改为「上周接待量和各专项会话、设备情况怎么样?」: 窗口=上周一~周日,
与 weekly_compare_last 快照重叠, 生产数据复现稳定 L1 命中。
其余 3 条默认快捷问经生产复现均正常命中(full/section)。
2026-09-23 19:40:15 +08:00
cb12a8b112
feat(ai-eval): 报告评价采集 + 周期汇总归因 + 版本字段可溯 (#173/#174/#178/#179)
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Failing after 3m55s
backend-test / frontend-lint-test (push) Successful in 36s
- 评价=采集:ai_report_feedbacks 落库(report_id/user_id/rating 1~5/opinion),
结果页打分区+留言,只采集不实时逐问分析(设计 §1.4 非目标)
- 分析=周期聚合:prompt_eval.build_summary 并入 _feedback_aggregate
(avg/low_ratio/low_reports/top_opinions),评价并进评估中心
- prompt_configs 加 change_reason/references:版本迭代依据/参考来源可溯
- 人工意见闭环:评估中心低分/高频意见可一键转版本草稿
- 页面工作流导引:采集→汇总→分析→迭代→生效 分组展示
2026-09-23 18:23:34 +08:00
8a085321a6
fix(api): 提示词版本管理路由未挂载导致 404 ( #178 )
...
gitleaks / gitleaks (push) Successful in 8s
backend-test / backend-lint-test (push) Failing after 26m0s
backend-test / frontend-lint-test (push) Successful in 36s
现象:提示词评估中心「版本管理」区报 404
GET /api/v1/admin/ai/prompt-eval/prompt-configs?code=gen_system|extract_system
同前缀下 #173 评估侧功能正常,故表现为「页面部分功能 404」。
根因:backend/app/api/v1/admin_ai_prompt_config.py 的 router、4 个端点、
service 依赖全部就绪,但 app/api/v1/router.py **既未 import 也未
include_router** → 该模块成了「孤儿」,从未挂载到应用。
全仓检索确认无任何文件引用它;其余 63 个 v1 模块 import/include 一一对应,
孤儿仅此一个。
修复:app/api/v1/router.py 补 1 行 import + 1 行 include_router。
验证(可复现命令)
- 路由表:include 的 67 个 router 中已含 prefix
/admin/ai/prompt-eval/prompt-configs,其 4 条路由(GET/POST 列表、
POST activate、POST rollback)路径与前端 aiPromptEval.js 调用完全一致
- HTTP 层(TestClient):/prompt-configs?code=gen_system → 401(原 404);
code=extract_system → 401;对照组 summary → 401
- pytest tests/test_prompt_config_router.py:修复前 2 failed、修复后 2 passed
- ruff check 通过
防复发:新增 backend/tests/test_prompt_config_router.py —— 只做路由契约断言
(不依赖 DB、不 mock),补上既有 service 测试的盲区:模块未挂载时 service
再正确也不可达。断言刻意区分 404(未挂载)与 401/403(鉴权拦截)。
2026-09-23 17:14:05 +08:00
bb89347907
feat(sso): 跨子域 SSO 反向链路(netmgr 面板 → csctalon)
...
gitleaks / gitleaks (push) Successful in 8s
backend-test / backend-lint-test (push) Failing after 31m59s
backend-test / frontend-lint-test (push) Successful in 50s
方向:本系统为消费方,netmgr 面板(panel.kimirock.top)为身份源。
面板管理员已登录后,点导航「数据分析系统」入口 → 面板签 60 秒 HMAC
一次性票据 → 302 到 /api/v1/auth/sso → 本系统验票 → 复用自身登录链路
签 JWT → 引导页写 localStorage 跳转,实现无感免密登录。
- 新增 services/netmgr_sso.py:HMAC-SHA256 验票(常量时间比较,60s+540s
时钟偏差容忍),与面板 sso.js 票据格式逐字符对齐
- auth.py 加 GET /sso 端点:无票 302 回面板签票,坏票 403,好票查账号
签 JWT;复用 login-json 的 create_access_token + token_version 机制
- 前端 axios 401 拦截:JWT 过期自动跳 /api/v1/auth/sso 续期(面板有会话
则无感续期);在 /login 页不跳 SSO 避免死循环
- settings 加 NETMGR_SSO_SECRET/PANEL_URL/DEFAULT_USER;.env.example 同步
- 与既有 csctalon→netmgr 方向(SSO_SHARED_SECRET)是两个独立密钥,不混用
- 不依赖 OpenResty 边缘网关:用户从面板入口主动跳入,www 站点保持纯反代
2026-09-23 13:49:15 +08:00
84f6257e54
feat(sso): talon 入口把「仅办公网/ZeroTier 内可达」显式化,不新增公网暴露 ( #188 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 27m12s
backend-test / frontend-lint-test (push) Successful in 38s
talon 部署在内网、无公网入口,原来单一直链在「既不在办公网、也未接入 ZT」时点开只有
浏览器连接错误,看不出原因。本次不改暴露面,只把限制说清楚:
- SSO_TALON_URL 拆为 SSO_TALON_URL_LAN / _ZT,/auth/sso/entries 一并返回
- 前端按当前访问来源择一:本页就是从办公网地址打开 → 用办公网地址,否则用 ZT 地址
- ⓘ 写明「仅在办公网 / ZeroTier 内可达,打不开请先连 ZeroTier」
- 设计说明的取舍段登记「为何不公网暴露」:talon 按对端 IP 判可信网段,任何反代都会让
来源变成代理机 → 实测云端反代源 IP(10.147.17.34) 落入 TALON_TRUSTED_ADMIN_NETWORKS
即免密拿到 /admin/keys 全部 API Key 明文
- 无法在前端探测可达性(https 页面 fetch http:// 会被混合内容拦截),故用来源择一 + 文案提示
- 单测 10 passed(新增双地址返回、都未配置两种情形);ruff 通过
2026-09-23 12:32:23 +08:00
d364d21aaf
feat(auth): 管理端免密进入 netmgr 面板(一次性票据 SSO,仅 ADMIN)( #187 )
...
gitleaks / gitleaks (push) Successful in 8s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
方向为「csctalon 签发 → netmgr 消费」:netmgr 面板持有 docker.sock(= 宿主 root),
不适合当业务系统的身份权威,故由本系统签发 60 秒一次性票据、面板验签后建自己的会话。
- services/sso_ticket.py:HMAC-SHA256 签票,payload=base64url({sub,aud,exp,jti}),
与 netmgr 消费方逐字符对齐;票据与 next 均放 URL fragment,不进两侧 access log、
不随 Referer 外泄
- POST /api/v1/auth/sso/ticket(AdminUser,操作日志只记目标与 TTL、不记票据内容)
与 GET /api/v1/auth/sso/entries(只回可用性与地址,页面加载不产生凭证)
- MainLayout 用户下拉新增 admin-only 外部管理台入口,按《界面设计说明规范》补三层说明
(入口放用户下拉而非 menu.js:菜单树只承载站内路由,外部链接塞进去会破坏防悬空契约)
- settings / .env.example / deploy/env.production.example 新增 SSO_SHARED_SECRET 等 4 项
- tests/test_sso_ticket.py:9 例(独立重算验签、jti 唯一、ADMIN 200 / 非 ADMIN 403、
未配置 503、entries 可用性),ruff 与 pytest 均通过
2026-09-23 11:42:39 +08:00
67a69cd88f
fix(config): 接线 profile 字段 + 保持生产行为不变 ( #190 )
...
gitleaks / gitleaks (push) Successful in 8s
backend-test / backend-lint-test (push) Failing after 16m10s
backend-test / frontend-lint-test (push) Successful in 36s
## 背景:自检抓出「声明了但没人读」的字段
上一提交引入的 `test_no_profile_field_is_obviously_unused`(扫描 Profile 字段名是否
在别处出现)**立刻抓出 4 个空承诺字段**。这比功能缺失更危险 ——
使用者会**误信一个不成立的保证**。逐个处置:
| 字段 | 问题 | 处置 |
|---|---|---|
| `docs_enabled` | 声明了但 main.py 从未读 → 以为"prod 关了 /docs",实际一直开着 | **接线**到 create_app() |
| `data_dir` | 未接线 | **接线**(派生 BROWSER_STATE_DIR) |
| `allow_destructive_on_real_db` | 未接线 | **接线**到 _bootstrap(决定真实库护栏) |
| `allow_db_mismatch` | 与 `db_guard.allow_real_db` **语义重复** | **删除**(两名一事必漂移) |
| `vite_app_mode` | 前端环境实际走 compose 环境变量,字段是第三处声明 | **删除**(前端真值来源统一为 compose) |
## 关键取舍:重构不得改变行为
接线时 `_PROD.data_dir` 一度被写成 `"/data"`(改造前是 `"./data"`)——
这是**非预期的行为变更**:框架升级不该顺带改动生产的数据目录。
已改回与改造前**逐字一致**(`./uploads` / `./data/browser_states`),
容器部署时由 docker-compose 显式覆盖为 `/app/uploads` 等。
同理,`/docs` 在 prod **保持开放**(改造前 FastAPI 用默认值 → 一直开放)。
关掉它是独立的决策,需走 `OPENAPI_ENABLED=false` 开关,不夹带在框架升级里。
## 新增测试(4 例,锁住"接线"这件事本身)
- `test_docs_enabled_is_read_by_main` 关键字段必须被读取
- `test_prod_keeps_docs_open_by_default` prod 保持现状(防顺带变更)
- `test_openapi_env_switch_exists` 必须提供显式关闭开关
- `test_no_profile_field_is_obviously_unused` 启发式扫描(正是它抓出了上表问题)
## 验收
全量 **1913 passed / 0 failed**(1909 + 4)
prod/dev 路径默认值与改造前逐字一致(逐项比对 `git show 829f963^`)
2026-09-22 11:25:16 +08:00
829f9637a9
feat(config): 运行模式框架 —— 让「模式 ↔ 行为」从标签变为驱动者 ( #190 )
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Failing after 4m52s
backend-test / frontend-lint-test (push) Successful in 43s
## 问题:不是"配置没分离",而是"模式机制根本不存在"
2026-09-10 事故(refunds 170960→2)与 2026-09-22 未遂事故**同因复发**,
证明前次修复(只在 tests/ 加守卫)只堵了一个入口。
实测根因:
· `settings.ENV` 全仓仅 **2 处**引用(微信 mock、错误措辞),**不驱动任何环境差异**
· `env_file=".env"` 按 **CWD** 解析 → 从仓库根跑会加载根目录那份 `ENV=prod` 的 .env
· `POSTGRES_DB` 默认值就是真实库名 `csctalon` → "忘记配置" = "连真实库"
· 本地与生产**库名同名** → 连错库时配置/日志/界面无任何信号
· 防线只覆盖 pytest:`scripts/*` 与 `python -c` 完全绕过
## 机制(三条原则)
1. **模式是驱动者,不是标签**
`APP_MODE` → `Profile`(单一真值来源,app/core/config/profiles.py)
profile **硬决定**:库名 / .env 路径 / 日志级别与格式 / 数据目录 / 危险开关
▸ 新增任何"环境行为不同"处,必须加进 Profile 字段,禁止在别处写 `if ENV == ...`
2. **危险值不可默认,安全值不可覆盖**
`POSTGRES_DB` 默认值 → 占位符 `__MUST_SET_POSTGRES_DB__`(缺失即启动失败)
默认模式 → `dev`(安全侧:dev 不连真实库)
3. **错误必须可见**
本地开发库改名 `csctalon` → `csctalon_dev`(生产保持 `csctalon`,用户决策)
启动期硬校验「模式 ↔ 库名」:
prod + 含 _dev/_test → 拒绝启动
dev + 不以 _dev 结尾 → 拒绝启动
test + 不以 _test 结尾 → 拒绝启动
## 覆盖的入口(含此前完全裸奔的两个)
| 入口 | 修复前 | 修复后 |
|---|---|---|
| pytest | ✅ conftest + tests/_db_guard | ✅ 追加 APP_MODE=test,与启动守卫双层 |
| scripts/*.py | ❌ 无守卫 | ✅ scripts/_bootstrap.py + db_guard |
| python -c | ❌ 无守卫 | ✅ 默认值改动 + 启动守卫双重阻断 |
`app/core/db_guard.py` 为**通用实现**,`tests/_db_guard.py` 委托给它 ——
避免"第二份守卫"(A-20260910-004 双份定义漂移教训)。
## 兼容性(关键)
· `app/core/config.py` → 包;全仓 **175 文件 / 62 处** `from app.core.config import settings`
**零改动**(实测导入形式 100% 统一)
· `ENV` 保留为兼容别名(settings 优先读 `APP_MODE`,回退 `ENV`)
→ 生产 `.env` 即使只有 `ENV=prod` 也能正确解析
· 生产 `.env` 已加 `APP_MODE=prod`(已备份 `.env.bak-20260922`)
· `docker-compose.yml` 不再硬编码 `ENV=prod`:
`ENV` 是 compose 保留变量(无法走 env_file),`APP_MODE` 不受此限
→ 本地可用 override 切 dev,生产行为不变
## 前端:拆开两个被混用的概念
`VITE_BUILD_MODE`(菜单档位)与 `APP_MODE`(环境)本为正交,原先只有一个概念 →
新增 `VITE_APP_MODE` / `APP_MODE` / `isNonProdEnv`;**菜单语义完全不变**(不破坏现有行为)。
## 验收
· 新增 `tests/test_runtime_mode_framework.py`(25 例)锁定上述不变式
· 全量 **1909 passed / 0 failed**(原 1884 + 25)
· 实测三场景(backend/ 、仓库根、/tmp)**均连 csctalon_dev** —— CWD 不再影响结果
· 生产 prod + csctalon → 通过;prod + csctalon_test → 拦截
文档: docs/arch/A-20260922-001;铁律挂到 AGENTS.md 第 3 条(不新开第 14 条,
避免稀释既有 13 条权威性);审查项进 review-checklist「安全」章。
2026-09-22 10:56:00 +08:00
0e3b93527b
test(complaint): 用合成 fixture 替代「依赖真实数据」的跳过 (42→0)
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Failing after 11m19s
backend-test / frontend-lint-test (push) Successful in 42s
背景: 测试库 `csctalon_test` 恒为空 —— `db` fixture 走显式事务 + rollback,
测试写的数据一律不落库。于是所有"库中无数据就跳过"的用例**从未执行过一次**。
代价已经显现, 其中两处暴露出真实缺陷:
① test_complaint_overview_a2a5.py: 调用 `complaint_overview()` 时多传了
`source_type`, 而该函数签名里**没有**这个参数(重构 ce778a5 前后都没有)
→ TypeError。因测试被 skip, 该错误潜伏至今。已修。
② test_metric_same_source.py: `evaluate_metric()` 默认窗口是**今天**,
而用例 `_window()` 算的是**昨日** —— 空库时两边都是 0, `0 == 0` 恒真,
窗口错配被完美掩盖。已统一为昨日。
③ test_metric_same_source.py 的 `test_refund_count_equals_four_routes`
是 #83 的核心等式, 空库下 `0.0 == 0` **恒真通过** —— 等于没测。
覆盖(全部改为在回滚事务内造合成数据, 并把 skip 换成 assert):
- test_complaint_overview_a2a5.py 18 用例(含上述签名错误修复)
- test_complaint_subject_filters.py 16 用例
- test_complaint_overview.py 23 用例
- test_metric_same_source.py 7 用例(原 6 + 新增分级边界)
★ 数据设计原则: 每处数据都让断言**有辨别力**, 而非"凑够非空":
· 主体榜三种排序键刻意产出**三种不同顺序** → 排序键写错立刻失败;
并加元断言守住这一前提(防止后人改数据把 ④⑤ 变恒真)。
· 刻意放入卡商数据 → "卡商不得进入投诉总量"(#53)才有验证对象;
不放则恒真。
· 刻意放入**冻结**提现 → 验证 B 路失败/冻结排除; 若误计入, 笔数 420→421。
· 阈值造两个版本(一条已失效)→ `get_threshold` 的"取最新生效版"才有意义。
· metric 数据量按**真实规模**(~420/日)造 —— 阈值量级断言的前提是
"值处于常态量级", 造 5 笔会让它假失败。
· 原有的"防假绿"初衷保留: 数据由 fixture 保证, 故改为 assert —— 数据没了
必须**失败**, 不能悄悄退回 skip。
全量: 1884 passed / 0 skipped / 0 failed(此前 42 skipped)。
2026-09-22 10:06:16 +08:00
aa923fff31
fix(test): 消除测试数据泄漏 + 清理历史残留
...
问题: 三个测试文件用真实会话(或 commit)往里写数据, 绕过了 `db` fixture
的事务回滚, 在测试库里累积残留, 且这些残留会**伪装成真实数据**影响别的用例。
修复:
- wide_table_builder: `_build_wide_batch(db, ..., commit=True)` 新增 commit 参数;
commit=False 时用 db.flush() —— 测试改传 False, 随 fixture 事务回滚
- test_reconcile: 清理模式只写 `SN-D-%`, 但本文件还用 SN-DUP/SN-NONE
→ 每次跑都残留设备行; 改为按全部前缀清理
- test_wallet_dedup: `upsert_wallet` 内部 `_sync_devices` 会自动建 devices 行,
加上用例里的 db.commit() → 每跑一次残留一行 SNTEST0001; 加 autouse 清理
新增 scripts/cleanup_test_residue.py: 一次性清理历史残留。
多重护栏: 库名必须是 test 库; `register_time` 非空的真实数据拒绝删除。
2026-09-22 10:06:07 +08:00
1b3a6864ad
fix(test): 修复 4 个因出域防护而失败的既有测试
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 54m38s
backend-test / frontend-lint-test (push) Successful in 54s
## 根因(**是我今天早些时候引入的回归**)
我在堵 `.env` 兜底通道的出域后门时(`_call_env_single` 增加策略校验),
没检查该改动对测试的影响 —— `tests/test_ai_assistant.py` 有 4 个用例
**故意**用公网 URL(`https://mock.deepseek.com ` / `https://www.deepseek.com `)
测 `.env` 单源通道,于是被防护正确拦下:
LLMError: .env 兜底通道指向出域地址(...), 但当前出域策略=explicit_only 不允许
⚠️ 我先后两次误判:
1. 起初以为是「既有问题」—— 但用 git stash 对比只证明了「与本次改动无关」,
没有进一步追根因。**"对比证明无关"不等于"可以放过"**。
2. 真正原因查出来后发现: 它既不是既有问题, 也不是本次意图识别改动引入的,
而是**中途那次安全加固**引入的。
## 修复
加 autouse fixture `_allow_env_egress` 设置出域策略为 auto。
**为什么不改测试用的 URL 为内网地址**: 这些用例验的就是「公网端点的
`.env` 单源调用链」, 换成内网 URL 会让测试失去原覆盖意义(测不到真实分支)。
正确做法是补上缺失的**策略前提**, 而非绕过被测路径。
## 验证
tests/test_ai_assistant.py: 53 passed(原 4 failed)
AI 相关子集: 120 passed, 0 failed
## 教训(值得记下)
安全加固会改变既有代码的前置条件; 加固后必须跑**全量**测试,
而不是只跑新写的用例 —— 被拦下的往往是「原来能过、现在被正确拒绝」的老用例。
2026-09-21 21:20:22 +08:00
8566732072
feat(intent): 统一问句意图识别层 + 个人绩效域接入
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
## 背景(调研报告 20260921-助理语义识别现状调研.md)
「理解用户问什么」此前散在 **5 处**,各自字面包含匹配且互不通信:
ai_time_parser / _KEYWORD_TO_SCOPE / _question_matches_keywords
/ classify_question / _has_deep_req_intent
## 新增 app/services/ai_intent_service.py
一次算出四个维度:
domains: overview/refund/complaint/reception/**personal**(新增)
granularity: brief/standard/deep(消除与 detail_level 的双重定义)
freshness: snapshot_ok/needs_live(**事前**判定)+ 可读理由
mentions_time: 只判断有无,解析仍复用 ai_time_parser(不重复实现)
**刻意不调 LLM**: 识别在最热路径上,调 LLM 会让 brief 路径多 1~7 秒。
漏判的代价由下游兜底承担(域漏→feed 全量;粒度漏→brief 秒回;
新鲜度漏→默认快照,未命中会自动降级到实时)。
## 修的三个真实缺陷(均由实测发现)
### ① 实时性判定倒置(调研里的最严重缺口)
原: 是否走 30~50 秒实时,取决于「模板命中与否」
新: 由**问句特征**决定 —— 个人绩效域直接判定 needs_live
落地: 个人绩效问句跳过命中链直接实时。此前它要等
「L1 未命中 + L2 未命中 + 无候选」才落到实时,而个人绩效**根本没有
预生成模板**(看板有数据但无模板),必然一路失败到实时。
### ② 内容域覆盖窄
- 新增 **personal** 域(用户确认「今日看板有数据和界面」,
数据源 today_staff_efficiency / operator_rank / reception_breakdown.top_staff)
- 补 overview 的口语变体(总计/合计/一共/全部门)
### ③ 关键词脆弱(我自己的词表也踩了两次)
实测漏判并修正:
· 「本周专项接待**总计**」→ 原表只有"整体/总览",缺"总计"
· 「谁接待**最多**」→ 原表只有"谁最",缺"最多/最高/第一"
· 「接待量**多少**」→ "多少"是**疑问词不是粒度词**,误判 standard
→ 移除 多少/几个。疑问词到处都是,拿来判粒度会把绝大多数问句
误判为 standard,触发不必要的完整性校验与补算(慢而无益)
教训已写进代码注释: 词表按**用户实际说法**收集,不按设计者书面语收集。
## _question_dims 改用识别层
原先自己 `if "退款" in question`,漏掉"客诉/退单/总计"等真实说法。
个人绩效维度对齐**今日看板**的数据结构(排名/接待量/挽回率/工作效率)——
不另造指标,否则 AI 说的与看板显示的对不上,用户会认为 AI 算错。
## 测试
新增 tests/test_ai_intent.py(47 例,含上述三个漏判作为回归)。
其中「明显口语化变体」逐域覆盖,使词表漏词在 CI 失败而非等用户发现。
52 passed(含选型守卫 5 例)。
## ⚠️ 发现的既有问题(非本次引入,已 git stash 对比确认)
tests/test_ai_assistant.py 有 **4 个长期失败**:
test_llm_client_success / test_chat_completion_timeout_s_override_short
/ test_llm_client_retry_then_success / test_llm_client_permanent_400
改动前后失败集合完全一致 → 与本次无关,但需要单独处理。
2026-09-21 21:12:32 +08:00
2790312405
docs(report): 助理语义识别现状调研(只读,未改代码)
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
## 起因
用户提出「助理对语义的识别还是个问题」,列出 4 个应区分的维度:
时间窗口 / 内容域 / 明细vs简报 / 可裁剪vs需实时
## 核实结论
用户直觉正确 —— 确实缺统一的意图识别层,但具体形态与设想有差异:
| 用户维度 | 核实结果 |
|---|---|
| 时间窗口 | ✅ 已实现且完善(正则+LLM双解析,显式参数优先) |
| 内容域 | ⚠️ **已存在** _KEYWORD_TO_SCOPE(3域16词),我初判有误 |
| 明细vs简报 | ❌ 缺失,但 detail_level/format 字段已存在未被使用 |
| 可裁剪vs需实时 | ✅ **最严重缺口** |
## 核心问题(不是「没有识别」而是「识别散落」)
识别逻辑散在 **5 处**,各自字面包含匹配、互不通信:
ai_time_parser / _KEYWORD_TO_SCOPE / _question_matches_keywords
/ classify_question / _has_deep_req_intent
## 三个具体缺陷
- A: 粒度概念**双重定义**(模板 detail_level vs classify_question 词表)
- B: 实时性判定**倒置** —— 是否走实时取决于「命中与否」而非「问句是否需要」
→ 措辞稍异导致未命中,直接掉进 30-50 秒实时,中间无「可否用快照回答」判断
- C: 关键词字面匹配脆弱(「客诉」匹配不上「投诉」)
## 生产覆盖盘点(决定命中率上限)
仅 5 个模板;空白:接待专项(有域词无模板)、个人绩效(完全无)、
weekly/monthly × refund/complaint
## 待用户确认 3 点
个人绩效是否新域 / 接待模板是否待补 / 是否接受「先给快照+提示可深挖」
报告: docs/reports/20260921-助理语义识别现状调研.md
2026-09-21 20:40:51 +08:00
b10cf158e7
fix(limits): 调大两处导致真实截断的 max_tokens
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
## 问题(由 talon 侧截断率分层暴露)
真实业务截断 6 条, 输出**恰好等于上限** —— 是输出被腰斩, 不是探测。
| 任务 | 原上限 | 实测被截 | 典型输出 |
|---|---|---|---|
| ai_semantic | 200 | out=200 | 400~466 chars |
| ai_analyze | **800** | out=800 | 1034~1089 chars |
## ai_analyze: AI_MAX_TOKENS 800 → 1500
`config.py` 的默认值被 #98 从 1500 降到 800(为控制耗时),
但实测该任务的**正常输出是 1000+ token** —— 即上限比正常输出还小,
**每次都被截**。
降上限换耗时应通过其他手段(缩短 prompt / 换快模型),
而不是把上限压到低于正常输出 —— 那让结果**静默不完整**。
⚠️ 生产 `.env` 里有 `AI_MAX_TOKENS=800` 会覆盖代码默认值, 已同步改为 1500。
## ai_semantic: 200 → 400
原注释假设「只输出 JSON 数组, 短」, 但实测**恰好被截在 200**,
说明候选条目多时数组会超。被截后果是**静默的**:
返回的 JSON 数组被砍掉尾巴, 解析后少几个匹配结果, 调用方看不出异常。
400 为实测输出的 2 倍余量。已同步修正两处过时注释。
2026-09-21 20:29:25 +08:00
d89d548a5b
test(tiers): 加选型守卫测试 —— 让「降级到已知会算错的模型」无法再潜入
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
## 为什么加这个(真实痛点)
同一个问题(specialist 把 Dense 降到 MoE coder-30b → 数值任务静默算错)
在评审中**被反复提出多次**,每次都当作新发现。
根因不是没修,而是**修了没锁**:
· 约束散落在 4 处(csctalon TIERS / DB llm_role_members /
talon CAPABILITIES / talon MODEL_FALLBACKS)
· 没有任何测试断言「specialist 不该出现 coder-30b」
· 于是每次读代码都会重新发现同一问题
把实测能力矩阵编码为**可执行断言** → 约束在 CI 失败,
而不是在评审里被人反复发现。
## 新增 tests/test_model_selection_guard.py(5 个用例)
- 涉数值档位(specialist/expert)的候选不得含 coder-30b
(实测数值聚合 3/4 偏差 20000、相对时间解析 1/5)
- 锁定 specialist 备选 = 122b(Dense,数值聚合 4/4)
- **对照**:assistant 仍可用 coder-30b(其用途不涉数值,实测语义匹配 6/6
且快 3x)—— 防止矫枉过正,不能因数值不行就把它从所有地方删掉
- 源码级扫描 4 处定义,在**改配置那一刻**就拦住
## 已验证测试真的有效
故意把 secondary 改回 coder-30b → 3 个用例同时失败,错误信息含
实测数据与修复方向;恢复后全通过。
## 同步修正过时注释
- `llm_tiers.py` 中「coder-30b 需同时服务 specialist(备选)」已过时
- talon `config.py` 的**配置示例**原示范 `specialist=...,coder-30b` ——
这是最危险的一处:后人照抄示例就会重新引入该问题
2026-09-21 20:19:30 +08:00
6e7481b2ff
fix(tiers): specialist 备选改 122b(原 coder-30b 会静默算错)
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
## 问题
`specialist` 的用途明确包含「**结构化输出 / 治理研判**」, 二者都可能涉及
数值聚合, 而 coder-30b 实测:
· 数值聚合 **3/4**(偏差 20000)
· 相对时间解析 **1/5**
即降级时会**静默算错** —— 比直接失败更糟: 调用方拿到的是格式合法但数字错了
的结果, 无任何异常信号。
原注释「可承接非数值的降级场景」与实际用途矛盾 —— specialist 并不只跑
非数值任务。且降级链是**无条件**生效的(27b 超时/宕机就切),
无法按任务类型甄别, 故不能依赖「它只承接非数值场景」这个不成立的前提。
## 修复
备选改为 122b: 与 27b 同属 Dense 架构, 数值聚合实测 4/4 全对。
代价是 122b 冷启动慢(实测约 80s), 但降级场景本就罕见 ——
用一次慢而正确的模型优于一次快而算错的模型。
2026-09-21 20:04:44 +08:00
014a200ea6
fix(llm): 出域放行头接线 + .env 兜底通道堵漏 + 语法修复
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Failing after 20m2s
backend-test / frontend-lint-test (push) Successful in 48s
## 修复三处我引入的缺陷 (均属最终关卡校验才发现)
1. EGRESS_HEADER 引用但从未定义 → 运行即 NameError
2. _headers() 三个调用点未传 allow_egress → 请求头永远设不上, 改动等于没做
3. 编辑造成 IndentationError (def _prompt_chars 后换行被吃掉) → 模块无法导入
## 出域放行语义
以「provider 是否在链中」为权威依据 (get_provider_chain 已按策略过滤),
不重复判断策略 —— 重复判断会与链条构造分歧, 而分歧只表现为 404。
两层关系是「csctalon 批准 + talon 复核」= 纵深防御。
## 新发现并修复: .env 兜底通道是出域后门
_call_env_single 不经过 get_provider_chain, 故 _apply_cloud_filter 对它
完全不生效; 而 LLM_BASE_URL 现状正是 https://api.deepseek.com →
一旦 DB 无可用 provider, 会**静默直连出域**, 绕过全部出域策略。
恰好在「DB 故障/空链」这种最不该出事的时刻生效。
处理: 新增 _is_cloud_endpoint() 按地址推断 (fail-closed: 无法判定按出域),
策略不允许时直接拒绝调用。
2026-09-21 19:32:11 +08:00
632c8782b3
docs(llm): 记录 talon 入口与调用方身份收敛的执行结果
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Failing after 3m11s
backend-test / frontend-lint-test (push) Successful in 32s
在出域管控设计文档追加「执行记录」节(与 draft-1 设计草案分开,
避免把「已做」和「计划做」混在一起)。
## 记录的三件事
1. **三个此前未发现的问题**
- 有 1 个 provider 绕过 talon(`talon-macmini-*` 直连
`http://10.147.17.20:11434/v1 `)。实测调用前后 `audit_logs` max id 不变
→ 无鉴权/无审计/无熔断/无降级
- csctalon 复用 ADMIN key → 审计里推理与运维混记为同一个 `ADMIN`
- talon 侧把开发机 IP 误命名为 `csctalon-admin`
2. **⚠️ 推翻一条此前的建议**
原建议「删掉 mac-mini provider(无用配置)」是**错的** —— 它的 note 写着
`L3 备用(给 gmk-evo 减负)`,`priority=25 enabled=t`,是有意的**节点亲和策略**。
正确做法是保留 provider、只把 `base_url` 改经 talon,
让「减负」由 talon 侧权重体现(实测改后 mac-mini 仍分到 1/6 流量)。
3. **改动方式的关键细节**
通过 `update_provider()` service 层改,而非裸 `UPDATE` SQL ——
前者会 `encrypt_secret()` 加密 key 并 `invalidate_cache()` 刷新 30s 缓存。
裸改 SQL 会漏掉这两步,导致「改了库但进程仍用旧缓存」的静默问题。
## 验证
- 5 个 talon provider 全部指向 4080;业务 3 个模型全 200
- 审计可区分:`SN-7B884E144377`(csctalon) / `ADMIN`(运维)分列
- 回归:talon 243 tests passed;ADMIN key 运维通道仍 200
## 明确未做(附理由)
- **不做**「多 provider 收敛为单 `capability:*`」——
出域管控 `llm_cloud_policy` 按 provider 判定,收敛会使其失去依据
- 免密调用方(`ZT-dev-workstation` 等)仍是免密,**不可作为计费主体**;
若需结算级计量须逐个改签发 key(本次已对 csctalon 示范)
2026-09-21 18:48:45 +08:00
8e680c6571
fix(llm): 修生产崩溃的错误 import + 补「不 mock」冒烟测试
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Failing after 3m0s
backend-test / frontend-lint-test (push) Successful in 31s
## 生产崩溃(B4 部署后立即暴露)
```
GET /llm-providers/cloud-policy
→ _list_cloud_providers() 内 `from app.database import SessionLocal`
→ ModuleNotFoundError: No module named 'app.database'
```
正确路径应为 `app.core.database`。该错误**只在真实调用时触发**。
## 为什么测试没抓到
`test_llm_cloud_policy_db.py` 把 `_read_policy_from_db` 等方法 mock 掉了
→ **mock 掉的东西测不到它内部的 import 错误**。
## 修复 + 防回归
1. 修正 import 路径(`app.core.database`)
2. 新增 `tests/test_llm_cloud_policy_smoke.py`(11 例)——
设计原则: **只 mock 数据层,保留真实 import 与函数体结构**
- `TestImportPaths`: 扫描源码,禁止 `from app.database import` 等错误路径
(含"可疑的 app.xxx 顶层 import"通用检查)
- `TestFunctionSignatures`: 参数签名稳定性(前端/调用方依赖)
- `TestCloudPolicyShapeWithMockedData`: 返回结构(source/options/cloud_providers)
- `TestListCloudProvidersBody`: **执行崩溃过的那段函数体**(mock SessionLocal)
> 取舍说明: 曾考虑用真实测试库,但发现**直接 `python -c` 读到的
> `settings.POSTGRES_DB` 是生产库 `csctalon` 而非 `csctalon_test`**
> (pytest 内的保护只在 pytest 内生效)——
> 险些误删生产库,仅因活跃连接而失败。**故改用「mock 数据层 + 真实结构」方案**:
> 同样能抓 import/签名错误,且零风险。
## 验证
- 11 例冒烟测试全绿
- 全量 `-k "llm or provider or tier or cloud or role"` 无新增失败
(仅既有 test_onboarding_router 失败,与本次无关)
2026-09-21 09:50:14 +08:00
65b0b4dc83
feat(llm): 出域策略落库(B4)—— 界面可直接切换,无需改 .env 重启
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Failing after 2m45s
backend-test / frontend-lint-test (push) Successful in 31s
## 背景(设计文档 §3.3 未完成项)
B1 出域管控的策略取自 `settings.LLM_CLOUD_POLICY`(.env),改动需重启服务,
与「管理员界面自助调整」诉求不符 —— B3 界面只能只读展示并提示"需改 .env 重启"。
## 数据模型(alembic d4e5f6a7b8c0)
新增 `llm_cloud_policy` **单行表**,照 `scheduler_settings` 的既有模式:
- 单行(id 恒为 1): 避免"多行配置哪行生效"歧义
- DB CHECK 约束: 防手工改库写入非法值(最后防线)
- **刻意不预置行** → 运行时回退 settings.LLM_CLOUD_POLICY,
保证升级后行为与 B1 完全一致(零行为变更)
## 读取优先级
1. DB 单行表(管理员界面可改,即时生效)
2. settings.LLM_CLOUD_POLICY(.env 兜底;DB 无行时)
3. 常量默认 explicit_only(保守)
非法值一律回退 explicit_only(不放行也不过度禁止)
## ⚠️ 缓存设计(这是数据边界,不容许窗口期)
`cloud_policy()` 在每次调用链构造时都会被调用,不宜每次查库 →
用 30s 进程缓存;但 `update_cloud_policy()` **立即失效缓存**,
避免"改了策略但 30s 内仍用旧值"的窗口期。
## API
GET /llm-providers/cloud-policy 读策略(含 source: db/env、最后修改人)
PUT /llm-providers/cloud-policy 写策略(立即失效缓存)
## 前端
- 策略面板改为**可保存**(原为只读+提示)
- 显示策略来源标签:数据库(界面可改)/ 环境变量 .env
- 切换到 auto/never 时给出差异化提示(明确告知数据边界后果)
## 测试
`tests/test_llm_cloud_policy_db.py`: 10 例
- DB 优先于 .env / 无行回退 .env / 非法值回退 / 读库异常回退
- **invalidate 后必须重读**(数据边界无窗口期)
- never 策略生效后云端立即从链中消失
- 三种策略语义未变(与 B1 一致)
全绿(52 项:cloud_policy 23 + cloud_policy_db 10 + tiers 19 + role_db 10 等)
## 配套(talon 侧,已另行提交 5012d26)
Mac mini 注册入集群(3 节点 healthy)+ 校正 .env.example 三处错误。
⚠️ 过程中发现 talon 存在**两个 .env**(部署级 vs 服务级),
真实生效的是 `/opt/talon/services/talon-api/.env`;且 `docker compose restart`
不重读 env_file(须 `up -d`)。已存入 repo memory。
2026-09-21 09:44:55 +08:00
d0e6734e8f
fix(llm): 角色成员改关联表(B2.1)—— 修 B2 单值列无法支持多角色
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Failing after 3m7s
backend-test / frontend-lint-test (push) Successful in 31s
## 问题(部署后立即暴露)
B2 用 `llm_providers.role_scope`(**单值列**)表达角色归属,设计时假设
「一模型一角色」,理由写的是"实测模型能力互斥,多角色场景不存在"。
**该假设在配置生产角色的第一步就被证伪**:
`qwen3-coder:30b` 需**同时**服务两个角色:
- assistant 的首选(实测语义匹配 6/6 全对、快 3x)
- specialist 的备选(27b 失败时的降级目标)
单值列下两次 UPDATE 后者覆盖前者,实测得到:
assistant → talon-gmk-qwen2.5-7b ← 降级为 7b(实测语义匹配 5/5 全错!)
specialist → talon-gmk-qwen3-coder-30b ← 错位
**比源码常量更差** —— 等于把实测已知会错的模型放到首选位。
## 修复:关联表(alembic a3b4c5d6e7f8)
新增 `llm_role_members(role_key, provider_code, sort_order, enabled)`,
多对多。同一 provider 在不同角色中的顺序**互相独立**。
数据迁移:把旧单值列迁入新表 + 补齐已知的多角色归属。
旧列 `role_scope`/`role_order` **保留为兜底**(关联表无记录时才读),
一个版本后删除 —— 保证迁移期不出现"配了没生效"。
## 代码
- `llm_tiers._resolve_from_db()`: 优先关联表 → 回退旧列 → 回退源码常量
- `list_roles()`: 同上,输出仍用 `role_order` 字段名(前端无需感知表变更)
- 新增 `add_role_member` / `remove_role_member` / `set_role_member_order`
- 新 API: POST/DELETE/PATCH `/llm-providers/roles/{role}/members[/{code}]`
- 前端 `moveMember`/`removeMember` 改用关联表接口
## 测试
- `test_llm_role_db.py`: 重写 mock 支持三类查询(RoleConfig/Member/Provider),
新增 `TestMultiRoleSupport` —— 显式锁定「同一 provider 两角色顺序独立」
- 52 项全绿(tiers 19 + cloud_policy 23 + role_db 10)
## 设计文档已如实记录该证伪
docs/design/20260921-模型角色分组与出域管控设计.md §3.1
新增「⚠️ 2026-09-21 实测证伪」段,说明方案 B 不成立、根因、临时缓解与正式修复。
## 教训
把「当前不存在该需求」当作设计依据时,**必须尽快用真实数据验证** ——
本次是配置生产数据的第一步即暴露,代价仅是一次迁移;
若等界面做完才发现,返工面会大三倍。
2026-09-21 09:36:00 +08:00
51bcdefa09
feat(ui): 模型角色分区 + 出域策略面板(B3)
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Failing after 2m54s
backend-test / frontend-lint-test (push) Successful in 35s
## 设计说明(按 docs/standards/界面设计说明规范.md 五要素)
目的: 替**系统管理员**回答三个决策 ——
① 出域策略该设成什么(数据允不允许出内网)
② 每个角色**现在实际会用哪个模型**(而非"我配了什么")
③ 某模型挂了会退到谁
逻辑: 按**角色分区**而非扁平列表 —— 提问是"这个角色够用吗",
不是"系统里有哪些模型";扁平列表答不出前者。每区直接显示**生效链**
(含 DB/常量回退与云端过滤的真实结果),避免"配了没生效"的误解。
口径: 成员顺序=role_order(小=先尝试);生效链=resolve_tier() 真实返回;
云端标记=is_cloud(按 base_url 回填,非人工标记)
取舍(不做什么及原因):
❌ 不做拖拽排序 —— 上下箭头够用,且拖拽在移动端不可用
❌ 不做模型自动优选(按历史成功率排序)—— 实测数据量不足,会引入抖动
❌ 出域确认弹窗归**调用时**交互,不在此处
❌ 保留原扁平列表作为折叠区 —— 删掉会让调用日志页无入口
## 界面结构
1. **出域策略面板**:三选一(禁止/仅显式/自动)+ 受约束的云端 provider 清单
2. **角色分区**(助理/专员/专家):生效链 + 成员表(↑↓ 调序、移出角色)
+ 角色级 allow_cloud 开关 + 选型依据(实测数据)
3. 原扁平列表折叠保留,新增「出域」「角色」两列
## 实现要点
- `loadAll()` 并行刷新 Provider/角色/策略(三者互相影响)
- `moveMember()` 处理 role_order 并列:相等时按方向强制区分
- `removeMember()` 置 role_scope 为空(**不删 Provider**,避免误删配置)
- 出域策略目前由后端 .env 控制(只读)—— 界面改动提示"需改 .env 重启",
并回滚显示为实际生效值(避免"改了但没生效"的错觉)
- 前后端字段缺省兼容(`?? false` / `?? null`),旧后端不报错
## 验证
`npm run build` 通过(✓ built in 28.19s)
## 待办
- 出域策略落库(当前只读,需改 .env)—— 见设计文档 §3.3 未完成项
2026-09-21 09:30:30 +08:00
889a3b2bc5
feat(llm): 角色落库(B2)—— 档位从代码常量升级为 DB 可配
...
## 背景(设计文档 §1.1 P3)
三档位(assistant/specialist/expert)的成员原先是 `llm_tiers.py` 的 Python 常量,
管理员无法调整,换模型必须改代码(已实际发生:修 7b 语义匹配失效需改业务服务)。
## 数据模型(alembic e5f6a7b8c9d1)
新增 `llm_role_configs` 表(仅 3 行,存文案与角色级开关):
role_key / display_name / description / rationale / enabled / allow_cloud / tier_order
- seed 三角色,文案取自 llm_tiers.py 的实测结论(避免两处漂移)
- `allow_cloud` 默认 false(保守):与全局策略形成**双重管控**
角色**成员**存在 llm_providers 的 role_scope / role_order 两列(B1 已建)。
## 选型逻辑
`resolve_tier(tier, include_cloud, db)`:
1. 传 db 且 DB 有配置 → 用 DB(按 role_order 排序,界面可上下调整)
2. DB 无该角色/角色 disabled/查库异常 → **回退源码常量**(首次部署即生效)
3. db=None → 与 B1 行为完全一致(向后兼容)
云端双重闸门(取交集,保守优先):
全局 LLM_CLOUD_POLICY=never → 任何情况都不出域(含角色允许)
角色 allow_cloud=false → 即使全局 auto 也不出域
## API(管理员)
GET /llm-providers/roles 角色列表 + 成员 + 实际生效链
PUT /llm-providers/roles/{role_key} 更新角色开关/是否允许出域/文案
GET /llm-providers/cloud-policy 读全局出域策略 + 云端 provider 清单
## 修复的遗漏
`update_provider` 的字段白名单未含 B1 新增的 is_cloud/role_scope/role_order
→ 界面本应改不了这三列,已补上。
## 测试
- tests/test_llm_role_db.py: 10 例(DB 优先/空角色回退/disabled 回退/
db=None 兼容/查库异常回退/角色级云端闸门三态)
- 全量 `-k "llm or provider or tier or cloud or role"` 全绿
既有失败 test_onboarding_router 与本次无关(已验证)
## 踩坑记录(已存入 repo memory)
初次生成迁移时用了猜测的 rev-id `c2d3e4f5a6b7`,与已有迁移冲突,
导致 `alembic heads` 抛 CycleDetected(整条链损坏)。已删除重建,
新 ID `e5f6a7b8c9d1` 经唯一性检查。教训: **迁移前必须验证 rev-id 唯一**。
## 待办
B3: 前端 LlmProviders.vue 改角色分区 + 出域策略面板
2026-09-21 09:28:07 +08:00
cd0be57d82
feat(llm): 数据出域管控(B1)—— 修静默出网风险
...
## 问题(设计文档 §1.2)
`llm_tiers.CLOUD_FALLBACK = "deepseek-v4-flash"` 原先**无条件挂在所有档位链尾**。
本地模型 transient 失败时(实测 27b 曾 30% 超时)会**静默把 prompt 发往
api.deepseek.com**,而 prompt 含真实客服工单数据 ——
与核心定位「数据不出内网」直接冲突,且**无开关、无日志标记、无用户确认**。
这是「为可用性牺牲数据边界」,且危害静默(调用日志显示 success)。
## 修复
新增全局策略 `settings.LLM_CLOUD_POLICY`:
never 云端从所有链中移除(数据绝对不出域)
explicit_only 仅当调用方显式 include_cloud=True 才加入 ← 默认
auto 保留链尾(旧行为,需管理员显式选择)
## 数据基础(alembic b1c2d3e4f5a6)
- llm_providers.is_cloud 是否出域;按 base_url 回填(公网域名=true, 内网IP=false)
- llm_providers.role_scope 角色归属(为 B2 预留,本次不启用)
- llm_providers.role_order 角色内兜底顺序(同上)
- llm_call_logs.is_cloud 调用快照,供事后审计「数据去过哪」
## 两道闸门(缺一不可)
1. `llm_tiers.resolve_tier()` 过滤**档位自己声明的**云端兜底
2. `_apply_cloud_filter()` 过滤**常规链尾巴**的云端
← 补漏: get_provider_chain 会把其余 enabled+healthy 的 provider 按 priority
追加到链尾,那里同样含云端(deepseek priority=30)。只改 1 会漏。
## 测试
- tests/test_llm_cloud_policy.py: 23 例(策略解析/allow/链取舍/尾巴过滤/兼容旧库)
- tests/test_llm_tiers.py: 更新 2 项失效断言
⚠️ 旧断言 `chain[-1] == CLOUD_FALLBACK` 锁定的正是本次要修掉的「静默出网」行为
—— 测试抓住了行为变更,这是它应有的作用
全绿(40 项)。既有失败 test_onboarding_router 与本次无关(已验证)。
## 待办(后续批次)
- B2: role_scope 落库 + resolve_tier 读 DB
- B3: 前端 LlmProviders.vue 改角色分区 + 出域策略面板
2026-09-21 09:25:20 +08:00
4329d2fd7b
feat(llm): 引入能力档位(助理/专员/专家),消除业务层硬编码模型名
...
gitleaks / gitleaks (push) Successful in 8s
backend-test / backend-lint-test (push) Successful in 15m8s
backend-test / frontend-lint-test (push) Successful in 47s
## 动机
`ai_semantic_matcher` 把 provider_code 硬编码为 "talon-gmk-qwen2.5-7b",
实测该模型在本任务 5/5 全错(confidence 恒 0,语义匹配从未命中)。
修它必须改业务服务代码 —— 说明「模型选型知识」放错了位置。
## 新增 app/services/llm_tiers.py(选型单一来源)
三档位(按**任务能力需求**,与 scheduler.level 的「紧迫性」正交):
assistant 助理 速度优先: 分类打标/语义匹配/摘取排版/短摘要 → coder-30b
specialist 专员 平衡: 实时问答/报告生成/治理研判 → 27b
expert 专家 质量优先: 长文深度分析/复杂推理/预生成 → 122b
每档声明 primary → secondary → 云端兜底,选型依据均附实测数据。
支持环境变量 LLM_TIER_PRIMARY_OVERRIDE 覆盖首选(运维不发布代码即可调整)。
## llm_provider_service.get_provider_chain
支持 provider_code 为 list(档位链): 按序置顶 + 逐个跳过不可用者 +
全部缺失时退回常规链。单字符串行为与旧实现完全一致(向后兼容)。
## 调用点改造
ai_semantic_matcher: provider_code=resolve_tier("assistant")
ai_extract_service: provider_code=resolve_tier("assistant")
→ 两处不再硬编码模型名;换模型只改 llm_tiers.py
## 测试
tests/test_llm_tiers.py: 17 例全绿
- 三档位定义完整性 + 回归(assistant 首选不得是 7b)
- 链解析(含无重复、云端兜底、非法档位返回空)
- 环境变量覆盖
- get_provider_chain 的 list 支持(按序/跳过不可用/全缺失回退/单字符串兼容/无重复)
## 注
talon 侧的「能力级路由」(P1-1) 是下一阶段——届时业务改传 capability:xxx,
talon 查表+降级+审计。本提交是 csctalon 内部的过渡实现,接口刻意对齐该方向。
2026-09-21 08:58:16 +08:00
5494e884ad
fix(ai-semantic): 修两个使语义匹配从未命中的 bug(实测复现)
...
## Bug 1: 模型选型错误(#145 的选型被实测证伪)
`_SEMANTIC_PROVIDER_CODE` 指向 qwen2.5:7b,理由是"封闭分类任务,7b 足够"。
用**生产真实 prompt 格式**复现 5 轮,7b **5/5 全部返回 confidence=0.0**:
[{"index":0,"confidence":0.0,"reason":"候选报告均未涉及退款原因"}]
理由自相矛盾——摘要里明写「退款原因以网络故障为主」。
→ 生产逻辑取 max(confidence)>=0.6 判命中,故**从未命中过**,
用户每次走 clarify 兜底、看不到报告。
实测对比(同 prompt,多轮):
qwen2.5:7b ❌ 5/5 全错(恒 0.00)
qwen3-coder:30b ✅ 6/6 全对(命中 0.8~0.9 / 闲聊 0.0),快 3x
qwen3.8:27b ✅ 5/5 全对(0.95,最稳)
→ 改用 talon-gmk-qwen3-coder-30b(置信度远高于阈值,且快 3 倍)
## Bug 2: index 基准 off-by-one
prompt 用 "[1]" 起编号,解析固定写 `idx = int(index) - 1`。
但实测模型返回基准不一致:
qwen2.5:7b → 0-based(index 从 0 开始)→ idx = -1 → **该条被静默丢弃**
coder-30b / 27b → 1-based
→ 改为按数组内实际基准自适应(_has_zero_idx + _to_zero_base),两种输出都能正确解析。
## 影响面
ai_semantic 生产 7 天 23 次调用全部"success"(HTTP 层面),但业务层置信度恒为 0
——**这类 bug 不会报错,只会静默失效**(调用日志看不出)。
## 测试
pytest -k "extract or semantic" 全绿(含 2 个 skip)。
2026-09-21 08:52:24 +08:00
67c61271a9
refactor: 清理 4 处长期未接入的占位功能(充值上游/邮件通道/登记中心查看/客诉名单规则) ( #185 )
2026-09-21 08:27:30 +08:00
d781b256c5
refactor: 拆分 3 个巨型 Vue SFC 的 script setup 为 composable ( #184 )
gitleaks / gitleaks (push) Successful in 8s
backend-test / backend-lint-test (push) Successful in 7m25s
backend-test / frontend-lint-test (push) Successful in 30s
2026-09-21 08:02:13 +08:00
ce778a59eb
refactor: 拆分 complaints.py(3923行) 为路由域子模块包 ( #184 )
...
- complaints.py → complaints/ 包: _router(APIRouter 单一定义) + _helpers(共享
辅助/常量) + _health/_summary/_detail/_reconcile/_misc/_backlog/_overview/
_subject/_analysis/_audit(各路由域)
- 拆分器修复: stmt_text 现含 decorator 行, 16 个 @router 注册与 cached_json
包装完整保留(初版曾丢装饰器致路由 404, 由 test_issue65 捕获)
- test_issue75 源码断言改为聚合 complaints/ 包内全部 .py
- 全量 pytest 1717 passed/42 skipped, ruff 全绿
2026-09-21 07:52:06 +08:00
2754c5554a
refactor: 拆分 upsert.py(2662行) 与 bms_fetcher.py(4275行) 为兼容 facade 包 ( #184 )
...
- upsert.py → upsert/ 包: _core/_refunds/_tickets/_complaints/_masters/_runner
(_convert 并入 _core 消 _convert↔_core 环; _manual_dedup_key/_manual_dedup_register_date
移入 _core 消 _core↔_refunds 环)
- bms_fetcher.py → bms_fetcher/ 包: _browser/_login/_fetch
(BmsSessionExpired/EmptyQueryResult/_parse_filename_utc_ts 移入 _browser 消
_browser↔_fetch 环; OCR Try 块归 _login)
- __init__.py 兼容 facade re-export 全部顶层名(含私有 _), 外部引用零改动
- 全量 pytest 1717 passed/42 skipped, ruff 全绿
2026-09-21 07:52:01 +08:00
e9610bd2ba
refactor(services): 拆分 analytics.py 11131 行为 8 子模块包 #184
...
analytics.py(11131 行)按主题拆为包 app/services/analytics/:
_core(口径真身/纯工具)/cash(经营流水)/complaint(投诉统计/健康)/
reception(接待人员)/refund_chain(退款链路)/today(今日看板)/
refund(退款挽回主分析)/rr_views(RR 预置视图+HANDLERS)。
__init__.py 作兼容 facade re-export 全部 175 个顶层名字(含私有 _),
既有 `from app.services.analytics import X` / `import analytics as A`
引用零改动。纯移动 + import 重定向, 不改变任何函数逻辑。
测试适配(口径断言改为聚合全包源码 / monkeypatch 打在子模块):
- test_complaint_specialty_merge / test_cache_warmer_alignment:
inspect.getsource(facade) → 聚合包内全部 .py 源码
- test_analytics_utils: 门控打桩改到 cash 子模块
验证: 全量 pytest 1717 passed / 42 skipped, ruff check 全绿,
冒烟确认 175 个顶层名齐全 + 10 个消费模块导入正常。
2026-09-21 07:30:23 +08:00
dc92bf08fa
#183 C: 依赖锁定——pyproject 全部依赖加版本上界, Dockerfile 改单一依赖源
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 7m38s
backend-test / frontend-lint-test (push) Successful in 32s
- pyproject.toml: 直接依赖从裸 >= 收紧为 >=X,<Y(基线=生产容器 2026-09 实测版本,
锁主版本线防大版本漂移); dev 依赖同步( pytest>=9.1,<10 / pytest-asyncio>=1.4,<2 )
- Dockerfile: 删除硬编码 pip install 清单(含 pyproject 没有的 bcrypt>=4.0.0),
改为 COPY pyproject+app 后 pip install . —— 消除双清单必然漂移
- 附带: ruff --fix 排序 tests/test_llm_timeout_headroom.py import(I001 预存, 阻塞 CI)
验证: pip install --dry-run -e '.[dev]' 无冲突; 本地 .venv 已对齐下界
(fastapi 0.141.1 / gunicorn 26.2.0 等与生产一致); 14 passed; ruff 全绿。
2026-09-21 07:14:12 +08:00
f551a9c4e6
#182 C: pytest.ini 增加 pythonpath 配置, 本地跑测试免 PYTHONPATH 拼接
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Failing after 3m19s
backend-test / frontend-lint-test (push) Successful in 31s
统一本地测试入口: cd backend && .venv/bin/pytest 即可运行,
无需手工拼 PYTHONPATH=.。注释标注 .venv_test 为 SCRM 调试精简环境勿误用。
验证: test_test_db_guard + test_smoke 11 passed; 全量 collect 1759 用例无 ERROR。
2026-09-21 07:04:43 +08:00
ba1e57b936
#181 C: AI 提示词命中升级为 窗口×专项(scope)×体裁(format) 多维分桶
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Failing after 3m21s
backend-test / frontend-lint-test (push) Successful in 35s
意见 id=6「提示词命中应该几个维度 时间窗口 专项类型的简报还是明细」:
命中/生成只有「关键词→整体经营简报」单一链路, 缺 专项类型(scope)×体裁(format)
两维 → 命中后内容错配(实测 ai_reports#387「涉诉情况: 数据截断, 暂无」)。
根因: 单一日报 feed 覆盖全维度超长被 _trim_feed 截断, LLM 只能答"暂无",
而非命中时间问题。方案: 生成侧按专项分桶(feed 聚焦不截断), 匹配侧多维决胜。
- 迁移 p7q8r9s0t1u2: ai_report_templates 加 scope/format 列(存量回填
overview/brief、周月报 detail) + seed daily_refund_brief(退款含涉诉, kw 含
涉诉/四路/挽回) 与 daily_complaint_brief(投诉三源), 均 refresh_on_sync=true
- 生成侧: build_messages 支持 scope 切片(专项桶强制并入 feed 段) +
format 体裁要求注入(简报 ≤2 行/明细逐维度); worker 透传模板 scope/format
- 匹配侧: match_template_report 排序升级
(-hits, period_match, -scope_score, -fmt_score, -created_at),
_scope_priority(同专项 2 > overview 1 > 其它 0) + _question_format_intent
(明细侧优先, 双侧/未命中中性 None)
- 摘取侧: extract_section 按命中模板 scope 权威路由对应 H2 段落
- admin_ai CRUD + AiTemplates.vue 支持 scope/format 配置与展示
- 测试: matcher +3(fmt 意图/scope 优先/专项桶同分胜出), feed +4(scope 切片/
union/体裁注入), 全量 pytest 100% 绿, ruff 全绿
2026-09-21 01:40:36 +08:00
19545f9961
#179 C: 金丝雀激活 + 一键回滚 UI(提示词版本管理)
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 5m16s
backend-test / frontend-lint-test (push) Successful in 1m14s
后端:
- prompt_config_service.canary_smoke: 激活/回滚前用候选提示词调一次 LLM,
非空返回 → ok=True 放行; 空 → ok=False 拒绝; LLM 服务不可用 → degraded 降级放行
(不因基建故障阻塞版本管理); 新增 get_version
- admin_ai_prompt_config: activate/rollback 路由内置金丝雀冒烟(smoke=true 默认),
ok=False → 400 拒绝激活/回滚并透出原因; 激活自动记基线
(近 7 天命中率/live 占比, 存 baseline 字段供防振荡参照)
前端:
- AiPromptEval.vue 区块7「提示词版本管理」: 生成侧/摘取侧 tab + 版本表
(生效标记/激活基线/激活人时间) + 新建版本弹窗(先改后升, 预填生效版) +
激活(金丝雀冒烟由后端执行) + 生效行「回滚到上一版」一键回滚(激活旧版本)
- ⓘ 气泡含口径与取舍(草稿制不自动激活/不自动回滚/不展示代码常量正文)
- aiPromptEval.js 新增 list/create/activate/rollback 四个 API
测试: canary_smoke ok/空/degraded/空内容 4 项, 后端相关 91 项全绿;
前端 eslint + vite build 通过
2026-09-21 00:29:26 +08:00
683219517c
#178 B: 两级提示词 DB 化 + 版本化(可回滚)
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
- prompt_configs 表(部分唯一索引: 每 code 至多一个生效版本) + 迁移 e8d7c6b5a4f3
(初版 a1b2c3d4e5f6 与 add_shanmao_phone_paid_amount 撞车已换 id; seed 双行
is_active=FALSE → 升级后行为与现状一致, 零回归)
- prompt_config_service: load_effective_prompt(DB 生效优先, 异常回退代码常量)/
list_versions/create_new_version(version+1 不激活)/activate/rollback(激活旧版本)
- 加载点改造: ai_feed.build_messages(gen_system) / ai_extract_service._load_extract_system
(extract_system), 均 DB 优先 + 代码常量兜底(提示词铁律)
- admin 路由 /admin/ai/prompt-eval/prompt-configs(list/create/activate/rollback),
已注册进 api router
- 测试 9 项: 加载兜底/版本递增/激活切换/回滚/双 code 独立; autouse 清理防污染
共享测试库(create/activate 会 commit)
2026-09-21 00:21:30 +08:00
6e9c172928
feat(ai-prompt-eval): 迭代建议升级为「规则+LLM 辅助」双通道 ( #177 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
- build_llm_suggestions: LLM 依命中率/高频兜底问句/已采纳人工意见/锚点漂移
产「补关键词/改问句」建议,走 llm_scheduler L1,失败回退纯规则不阻断
- schema 校验: 仅 template_keyword/template_question;target_id 必须是真实模板;
keywords 2~8 字去重;template_question 必须保留 {{占位符}}
- build_template_eval 输出补 question_template(占位符校验依赖)
- /suggest 返回 source=llm|rule;前端 genSuggestions 提示建议来源
2026-09-21 00:08:40 +08:00
313335f7a5
fix(ai-onboarding): 事务回滚态下保证 failed 事件落库 ( #175 )
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Failing after 57m56s
backend-test / frontend-lint-test (push) Successful in 1m6s
_draft_core 落库 commit 抛未预期异常时,session 会进入需回滚态,
原 except 分支未先 rollback 即写 system_event,导致二次写入抛
「transaction rolled back」而丢失 failed 事件(#157 未覆盖的次生缺陷)。
修复:DraftError / 未预期异常分支写事件前先 db.rollback();补回归测试
test_commit_failure_still_writes_failed_event 模拟 commit 失败后的回滚态场景。
2026-09-20 22:30:28 +08:00
a77905f5eb
feat(ai): 评估中心新增人工意见输入通道(异常/不合理/业务深化) #174
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 22m48s
backend-test / frontend-lint-test (push) Successful in 35s
复用 prompt_iterations 表:新 scope=human_opinion(reason 承载分类),
只记录 + 采纳/不采纳状态跟踪,归 RECORD_ONLY 禁 apply(自由文本不自动执行)。
后端 propose_opinion + POST /admin/ai/prompt-eval/opinions + 2 条单测;
前端 AiPromptEval.vue 新增人工意见区块(表单+列表+采纳/不采纳),三层落地补齐。
2026-09-20 19:31:35 +08:00
d7c333403e
feat(ai): AI 提示词评估中心 — 规则级+提示词段评估 + 半自动迭代闭环 #173
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 3m8s
backend-test / frontend-lint-test (push) Successful in 32s
两级评估(规则级=模板命中/治理规则健康度;提示词段=关键词命中/锚点一致性)+
跨链路关联(生成侧 _SYSTEM_PROMPT 锚点 ↔ 摘取侧 H2_ANCHORS ↔ 报告实际 H2 分布)+
半自动执行(prompt_iterations 状态机 proposed→confirmed→applied/rejected,
人工一键确认后服务端写模板/规则,不改代码常量提示词铁律)。
- 迁移 s9t0u1v2w3x4: governance_rules 加 effect_* 效果回填列 + 新建 prompt_iterations 表
- 服务 prompt_eval_service: build_summary/template_eval/anchor_check/governance_eval/
build_suggestions(纯规则归因不调 LLM) + propose/confirm/apply 状态机
- 路由: /admin/ai/prompt-eval 评估四端点 + 迭代建议 CRUD;governance /rules/{id}/effect 回填
- 前端: AiPromptEval.vue(四区块+迭代抽屉,五要素三层落地)+ 路由/菜单
- 测试: test_prompt_eval_service 13 用例 + AI/governance 回归全绿
2026-09-20 18:33:01 +08:00
67fc6a20f7
fix(governance): persist_findings 去重纳入 judged,重扫不再堆重复行 #172
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 19m2s
backend-test / frontend-lint-test (push) Successful in 42s
生产实证(#170 实测):已研判(judged)但未处置的 finding 会被下一轮
体检重复落库 —— persist_findings 去重只认 open,judged 落到任何分支外,
每轮定时体检为同一问题新增一行,表堆积、界面出现多个「待处置」条目。
修复:去重条件改用 CLOSABLE_STATUSES(open/judged,与 close_stale_findings
同源,避免两份定义漂移);judged 命中时只刷新证据(severity/metrics/
samples/repro_sql/detected_at),不覆盖 verdict/suggestion(AI 产物)。
resolved/ignored 终态行为不变(问题复现仍重新暴露)。
补测试:judged 重扫不新增行且证据刷新、verdict/suggestion 保留;
resolved 后重扫新落 open(守护原行为)。
2026-09-20 17:00:59 +08:00
51f0bd2b5d
docs(governance): 沉淀 E3 经验 —— 探测「空缺/可补」须先剔除已处置排除的行 #170
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Failing after 23m54s
backend-test / frontend-lint-test (push) Successful in 42s
「有多少行可处理」不等于「有多少行值得处理」:凡产出「待人工处理清单」的探测器,
都要先问「这些行已经被处置过了吗」。
2026-09-20 16:18:47 +08:00
bce1e1ccd2
fix(governance): D6 已排除出经营统计的行不算空缺(生产实测暴露)#170
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
生产实测发现 manual_refunds 的 2918 个 device_sn 空缺里 2049 行已是 dup_residual
(治理 #21 的「同单占位残留行」,已排除出经营统计):
- 补 SN 对经营口径毫无影响,报出来只是噪音(实测占 71%),稀释真正可省人工的部分;
- mark_pending 有红线拒绝把排除态拉回统计(#169 教训)→ 样本全是这种行时
执行恒为 affected_rows=0,正是禁止的「点了没反应」。
故目标表有 quality_status 列时,dup_residual/ignored 的行不计入空缺与可补;
其行数记入 metrics.excluded_null_rows 留痕(数字才解释得通),repro_sql 同步加一段。
排除状态清单的唯一来源是 quality_disposition_service.EXCLUDED_STATES(不另写一份)。
另:样本行改为按目标行主键升序取(可复现,不用随机)。
2026-09-20 16:12:53 +08:00
ebe609be43
feat(governance): D6 可推断空缺探测器(有权威来源可推,只标记不写值)#170
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
只认「值在库里另有权威来源」的三类,源里查不到一律不报(禁止猜):
- A SN→设备属性:devices.device_type 空 + bms_orders/device_flow_usage 的 device_type_name
- B 订单→SN:manual_refunds/shanmao_refunds.device_sn 空 + 同订单号在
refund_record_reports/refunds/device_combos/bms_orders 上的 SN
- C 别名→标准人:person_id 空 + person_aliases 命中**已归并**的 person_id
(字段清单取 PERSON_FIELD_SOURCES,只保留同时有 person_id 列的表)
来源清单按 2026-09-20 生产只读探测校正(设计文档原表举例与库中实际不符:
device_combos 无设备类型列、device_profile 是导入通道、refunds.device_sn 无一为空)。
样本必带**推断来源** samples[]._source(哪张表哪一行推出的值),多来源不同值时记
_alts 交人工定夺 —— 否则等于要求人盲信 AI。metrics 与 repro_sql 逐段可对账。
执行分级 L1 mark_pending(标待核,仍计入经营统计)+ report_only;**本期不自动写值**。
2026-09-20 15:46:57 +08:00
fda4d9437c
fix(llm): 超时自洽保障 — 修主力模型 30% 调用超时
...
## 事故
生产 7 天数据: qwen3.8:27b 的 352 次调用中 **104 次 ReadTimeout (29.5%)**,
且失败会触发 provider 降级,污染调用链(transient 17 次)。
## 根因
max_tokens 与 timeout_s 不匹配,且无任何校验:
调用点 max_tokens timeout 27b 需时(实测 20~27 t/s)
ai_analyze 1500 60s 56~75s ⚠️ 临界
governance_judge 3000 120s 113~150s ⚠️ 临界
onboarding_draft 1500 60s 56~75s ⚠️ 临界
ai_extract 1200 60s 45~60s ⚠️
报告预生成 2000 600s 75~100s ✅ (故 122b 零超时)
60s 内产出 1500 token 在物理上不可能 — 等于"按必然失败的时限去等"。
## 修复
新增 _ensure_timeout_fits_tokens(): 发请求前按 max_tokens 反推最低所需超时,
取 max(调用方值, 所需值)。调用方仍可放宽,但不会再有"装不下"的组合。
needed = max_tokens / 15 tps + 30s 预留 (保守基准,低于实测最慢值)
仅对本地端点生效(_is_local_endpoint 判定内网 IP / localhost);
云端(deepseek 等)延迟低且有 prefix cache,不适用。
接入两条路径: _call_multi_provider(DB 多 Provider)+ _call_env_single(.env 单源)。
## 测试
新增 tests/test_llm_timeout_headroom.py: 20 例
- 本地/云端端点判定(含 talon 生产实际值 192.168.10.21)
- 按需放宽 / 不缩小 / 云端不误放宽 / None 处理 / 下限保护
- 覆盖 4 个现有调用点的真实参数组合
全绿。另: 既有失败 test_onboarding_router::test_background_task_marks_failed_on_llm_error
经 git stash 验证为**改动前既有**,与本次无关。
## 注
未改动 governance 相关文件(工作区内有其他未提交改动,未触碰)。
2026-09-20 15:44:16 +08:00
0e3f97a441
fix(governance): 研判批大小/超时按生产实测调整(10 条批会 livelock)#169
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Successful in 14m21s
backend-test / frontend-lint-test (push) Successful in 49s
生产实测:单条 finding 研判 22~25s,2 条 115s(已贴 60s 超时并触发重试),
10 条批 267s **全部 ReadTimeout** → 该批保持 open,下一轮仍是同样 10 条 → 研判永不收敛。
修法:_TIMEOUT_S 60→120、_BATCH_SIZE 10→3(≈75s,留足余量);新增切批单测。
2026-09-20 15:25:40 +08:00
68e840e998
fix(governance): mark_pending 不得覆盖排除态(改口径红线)#169
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
生产实测(真实 finding #28 的 8 个样本行)踩到:其中 4 行原本是 dup_residual
(note 带「治理#21…判定与保留行重复」= 已排除出经营统计),被 mark_pending 覆盖成
pending → 重新计入统计,等于**改了经营口径**(AGENTS.md 绝对禁止第 10 条)。已复位。
修法:mark_pending 遇到 EXCLUDED_STATES(dup_residual/ignored)的行跳过并记录
(execute_result.skipped + note 提示),不写、不改口径。
另:设计文档补该红线、经验文档补 7b/7c/7d(format 花括号坑、枚举派生、口径双向防)。
2026-09-20 15:05:43 +08:00
1faa15070e
fix(governance): D5 判据按生产分布二次收敛(列对比例上限 + 计划类列白名单)#169
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 7m40s
backend-test / frontend-lint-test (push) Successful in 44s
生产全量扫描(11.2s / 13 findings)暴露两类误报,按真实分布收敛:
- 判据 C 列对比例上限 30%:service_tickets.dispatch_time/active_time vs register_time
倒挂占比 90.7%/67.5% —— 该表 register_time 语义是「服务登记时间」而非创建时间,
按列名推断的列对本身是错的;超限列对跳过并记入 metrics.skipped_pairs(可审计)
- start_date 进 PLANNED_DATE_COLUMNS:device_combos 39938 行 start_date 在未来且
全部 end_date 也在未来且 end>start,属预购/长期服务正常形态
- 新增 2 例测试 + 经验文档 4d(「先探测后定阈值」要做第二次)
2026-09-20 14:44:23 +08:00
0c6bdf97ac
feat(governance): 采集语义增强 E2 —— D5 不可能日期 + fix_time(L2) + mark_pending(L1) #169
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 7m35s
backend-test / frontend-lint-test (push) Successful in 44s
- D5 探测器:四类判据(年份越界/未来日期/时间倒挂/兜底值聚集),逐 (表,列) 产 finding;
metrics 含 kinds/counts/min_date/max_date/top_dates,hit_rows 按四类并集(可对账 repro_sql)
- 防误报三道门:兜底值聚集 = 占比≥0.30 且行数≥3 且形如 01-01/12-31(大促这类真实集中日期不报);
倒挂按阶段序推断列对(同阶段不配对)+ 北京日期比较;计划类列/系统审计列/物化宽表不扫
- 判据同源:date_anomaly_rules 单一来源,导入期 _persist_date_suspects 留痕 date_impossible
- 执行层:_exec_fix_time(L2,确认→强制备份→逐行 UPDATE→old/new 凭证)、mark_pending(L1,不改口径);
确认接口支持 params 覆盖 AI 的 updates(记入 execute_result,不改 suggestion)
- 红线加固:_IRREVERSIBLE_ACTIONS/_VALID_ACTIONS 改由 DISPOSITION_LEVELS 派生(原先漏了 fix_time)
- 提示词 v3 迁移 + build_spec_rules(detectors) 按探测器注入判据段
- 前端:探测器下拉 + top_dates/old-new 渲染 + 动作性质文案 + 设计说明口径段
2026-09-20 14:18:43 +08:00
3078336fdb
fix(governance): 业务允许不得建议 mark_ignored(提示词 v2 + 代码兜底) #171
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 55m5s
backend-test / frontend-lint-test (push) Successful in 44s
E1 生产实测发现: 真实 LLM 对 verdict=allowed_by_business 的 finding 建议 mark_ignored
(L1, 会把行排除出经营统计) —— 与「业务允许」自相矛盾, 管理员一键执行即改经营口径
(AGENTS.md 绝对禁止第 10 条)。
- 系统提示词加铁律 6: allowed_by_business 禁止 mark_ignored/mark_dup_residual,
应给 report_only + 人工 dismiss 沉淀规则(数据一行不动); 模块默认提示词同步
- 迁移 q7r8s9t0u1v2: governance_prompts.judge_system v1→v2, **仅在内容仍等于 v1 时更新**
(管理员可能已按「提示词 DB 可迭代」手改过, 绝不覆盖; 未匹配则打印提示跳过)
- 代码兜底(第二道闸): _normalize_suggestion 对「业务允许 + 标排除动作」强制降级为
report_only + 留 note(可审计 AI 原本建议了什么)
- 测试 3 例: 降级生效/真问题不受影响/系统提示词含铁律
- 新增 docs/reports/20260920-数据治理框架实施经验.md(REF): A→E1 经验复盘
(判据证据驱动与同源、提示词+代码双守、采集热路径四条纪律、生产实测与迁移做法、留尾纪律)
2026-09-20 13:20:15 +08:00
f3f387950c
feat(governance): 采集语义增强 E1 —— D4 文本异常探测 + 导入期留痕 #168
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 7m32s
backend-test / frontend-lint-test (push) Successful in 47s
- 新增 text_anomaly_rules: 判据**单一来源**(导入期 Python 谓词 kinds_of / 扫描期 SQL
渲染 sql_predicates, 同一阈值常量 near_limit_threshold + 同一白名单), 单测断言
「同一份入库后的数据两边结论一致」
- 白名单**证据驱动**: 生产探测 33 个文本列仅 5 列含换行 → problem/handle_detail
(投诉原文/处理详情, 多行是业务真实形态) 入白名单并写明理由; remark 类不进白名单
- D4 探测器 scan_text_anomaly: 判据①含换行 ②长度贴列宽上限(varchar≥200, Text 天然跳过);
逐 (表,列) 产 finding, 样本把换行渲染为 ⏎ + 带 _length/_kind, 复现 SQL 两类各一段
- 导入期留痕 add_suspect_row(统一入口, 供 E2/E3 复用): 与 add_rejected_row 分工写死
(前者=已入库但值可疑, 后者=未采纳); 新 reject_category truncate_suspect/newline_suspect
(无 CHECK 约束, 无需迁移); run_upsert 在入库前扫描 df(拿截断前原始值), 每批上限 200,
失败不阻断采集
- 注册 5 处: detector 常量/探测器/gather_findings 枚举/router DETECTORS 白名单/前端下拉
- 前端: 探测器下拉加「文本异常」+ 样本区「⏎=换行」说明 + 设计说明口径段补齐(谁改动谁补齐)
- 测试 15 例: 判据同源(值级一致) / 白名单不报 / 贴上限报出 / 短 varchar 不扫 /
全链路 run_upsert 留痕且入库数据与源值一致 / 上限生效
2026-09-20 13:05:08 +08:00
a53c887d31
docs: 采集语义增强第二期详细方案 + 治理框架工单状态收尾 #168 #169 #170
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Successful in 7m36s
backend-test / frontend-lint-test (push) Successful in 32s
- 新增 docs/design/采集语义增强设计-20260920.md(REQ):三场景(CSV 换行截断 /
明显错误日期 / 语义补全)详细设计 —— 判据 SQL 与阈值、证据形态、导入期留痕
(复用 #40 的 IngestRejectedRow,不建新表)、分级执行(改值恒 L2 fix_time)、
与治理框架的 5 处注册落点、验收标准、E1/E2/E3 拆分、风险表
- 核心取舍:双时点(导入期拦新增 + 事后扫描治存量)且**判据同源**(同一函数,
否则拦截口径 ≠ 扫描口径又是一次漂移)
- docs/README.md §六 登记新方案(REQ);治理框架文档状态 评审中 → 已实施,
§十一 工单表补 A/B/C/C+/D 的工单号与 commit
2026-09-20 12:17:50 +08:00
23d7fd2402
feat(governance): 治理框架 D —— 接口 + admin 管理页 + 分级执行 + 端到端 #165
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
后端:
- governance_execute_service: 分级执行(设计 §六)。L0 report_only 只出报告; L1 mark_ignored/
mark_dup_residual 复用 quality_disposition_service 标样本行(可 restore); L2 delete_dups
须人工确认 + 强制备份(gov_bak_<表>_<finding_id>) + 凭证 + 重建受影响宽表 + 失效缓存
- 三条安全设计: 判据与探测器同源(index_columns/index_where + 排除整组全 NULL)、
dry-run 行数与 finding 报数不一致即拒绝执行、备份行数必须等于待删行数
- create_index 明确拒绝(须走 Alembic 迁移); fix_time 属第二期
- DISPOSITION_LEVELS 动作→级别映射唯一来源; 规则不得配置不可逆动作(400)
- 11 个端点 /api/v1/governance/* (设计 §八 9 个 + judge/judges 补充 2 个) + 路由注册
- 异步 202+轮询: 占位事件(running)由 update_system_event 回填, 一行事件贯穿全程;
当日语义只认 success/partial(占位不阻塞自动体检)
- 迁移 p6q7r8s9t0u1: confirmed_by/confirmed_at/executed_at/execute_result
- 测试 12 例: L2 未确认 400 且数据未动 / 确认后备份存在且行数相等 / 判据漂移拒绝执行 /
L0 不改数据 / create_index 拒绝 / 规则红线 / 异步占位事件
前端:
- web/src/views/system/AiDataGovernance.vue: 概览四卡(含口径与空值含义+下钻) + 体检/研判
(202+轮询) + findings 筛选表 + 详情抽屉(证据/样本/复现SQL/研判/建议/凭证) + 确认/执行/
判定业务允许 + 规则管理; 分级一律取服务端 auto_level/requires_confirm
- api/aiDataGovernance.js + router + menu(系统管理→数据质量, admin)
- 界面设计说明三层落地(头部五要素注释块 + ⓘ 含「不做什么」6 条)
2026-09-20 12:00:26 +08:00
ba47191007
feat(governance): 研判自动触发 + 重扫收敛 #167
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Successful in 15m0s
backend-test / frontend-lint-test (push) Successful in 31s
- 重扫收敛 close_stale_findings: 体检后把本次未再命中的 open/judged finding 置
auto_closed(新状态, 语义=不再命中, 未执行任何动作, 区别于 resolved); 只收敛本次
跑通的探测器(gather_findings 返回 ok_detectors), 防探测失败误关历史 finding;
终态 ignored/resolved/auto_closed 不动
- 低峰自动研判 judge_idle: governance_idle 体检后自动研判一次(当日语义, 规则命中零
LLM 成本), 解决「体检持续报新 open finding 而无人研判」的闭环缺口; 被抢占不阻塞主循环
- 留痕: governance_scan 事件 detail 含 auto_closed 条数与 ids
- 迁移 o5p6q7r8s9t0: governance_findings.status 注释补 auto_closed(无数据变更)
- 单测 6 例: 收敛正反例(未命中→关/仍命中→留/探测器未跑通→不动/终态→不动) + judge_idle 当日语义
2026-09-20 11:25:59 +08:00
b93bf6c385
feat(governance): 治理框架 C —— AI 研判层(提示词配置化 + 规则短路/沉淀) #164
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
- governance_prompts 表(迁移 n4o5p6q7r8s9) + v1 提示词幂等 seed; 规范原文段由代码
常量注入, 提示词里不存第二份 META 定义
- governance_judge_service: 规则短路(命中 governance_rules 则不调 LLM) → 批量结构化
JSON 研判 → schema 校验(含 L2 红线 requires_human_confirm) → 非法整体重试 1 次 →
两次仍非法保持 open 不落脏数据; 回填 verdict/suggestion/confidence/rule_id + 留痕
system_events(ai_governance/governance_judge); 经 llm_scheduler L1 门闸
- dismiss_finding: 人工裁定业务允许 → finding 置 ignored + 沉淀 governance_rules
(verdict=allowed_by_business, auto_level 恒 L0 只读) + 记 hit_count/last_hit_at
- 单测 18 例: mock LLM 结构化产出 / 非法重试 1 次 / 规则命中断言 LLM 未调用 /
dismiss 沉淀后二次命中短路 / 不可逆动作强制人工确认
2026-09-20 10:55:00 +08:00
af2a411b57
fix(governance): D2 加绝对量下限, 消除低频表噪音误报 #163
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Successful in 17m27s
backend-test / frontend-lint-test (push) Successful in 36s
生产首次体检实证(人工逐条核验 3 条 D2 finding):
- card_complaints 2→6(3倍, high) / device_tags 1→15(15倍, high) → **噪音**:
低频表/标签字典绝对量小、比值天然剧烈, 纯比值判据持续产噪音
- refund_wide 2613→4330(1.66倍) → 真实量变(日量 350→700+), 非噪音, 留待研判层定夺
判据收紧(两处):
1. 绝对量下限 _D2_MIN_ABS_ROWS=20: max(cur,prev) 低于此值直接跳过
2. high 需绝对变化量 _D2_HIGH_ABS_DELTA>=100: 防小表 2 倍被标 high 淹没真实量变
测试 +2: 个位数波动(1→15)不检出 / 比值 3 倍但绝对变化量 50 只报 warn。
2026-09-20 10:17:00 +08:00
5ec5c7e72c
feat(governance): 治理框架 B —— D2/D3 探测器 + 体检编排 + 留痕 #163
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
设计: docs/design/AI数据治理框架-20260919.md §四/§九
- D2 行数突变: 时间列权威来源 data_sources.time_field(免另建映射);
本期 vs 上期窗口 ratio, 增长(>=1.5)与跳水(<=0.5)都报 —— 只盯增长会漏
「整批数据丢失」(D-P1-2 审计缺口); 产出按日曲线便于定位突变日。
曲线覆盖本期+上期(2x窗口): 只看本期时 drop 类曲线是空的, 看不出「原本有数据」。
- D3 外键: 生产 100 个 FK 全 validated → PG 强制校验, 孤儿恒为 0, 只查孤儿
等于没检测。真正靶子是 NOT VALID(约束看似存在、实际不拦新增) → 有孤儿报,
无孤儿但 NOT VALID 也报(gap_kind=unvalidated_constraint), 与 D1 保护缺口同类。
用 NOT EXISTS 避开多列 FK 的 NULL 比较坑。
- 时区一致性: 窗口计数与 repro_sql 共用 _date_expr(按列类型生成), timestamptz 走
AT TIME ZONE, naive 直接 ::date(naive 用 AT TIME ZONE 会受会话时区影响偏 8h)。
- 编排 governance_scan_service: 批量跑 D1/D2/D3 → 落 findings → 记
system_events(category=ai_governance, action=governance_scan); 进程级单飞锁。
- 调度: 挂 cache_warmer 空闲循环, 每日低峰(0-5点)自动一次(当日语义), 避开 8-24
点同步窗口; 手动入口 run_full_scan 供 #165 接口调用。
测试 tests/test_governance_scanner.py 7 例: D2 增长/平稳不报/跳水, D3 NOT VALID 孤儿/
NOT VALID 无孤儿/健康不报, 编排落库+留痕。
实测: 本地库 D2 扫出 10 个突变(曲线可定位), D3 0(全 validated); 全量 1588 passed。
2026-09-20 10:13:00 +08:00
6e610d1c9f
feat(governance): 治理框架 A —— findings/rules 两表 + D1 唯一索引重复探测器 #162
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 7m33s
backend-test / frontend-lint-test (push) Successful in 31s
设计: docs/design/AI数据治理框架-20260919.md §三/§四
用户 2026-09-19 提出: 数据治理现状是「人发现坏数据 → 写脚本 → 人工判断 → 改数据」,
每次走开发流程成本高且易误判(C-P1-7 实证: 人工扫 5 个候选索引, 4 个是误报)。
本工单落地框架第一步: 探测层(确定性, 不含 AI)+ 两表, 为后续 AI 研判/分级执行打底。
- 两表: governance_findings(探测结果, 只存证据不含结论) + governance_rules
(判断规则沉淀, 命中即跳过 LLM); 迁移 m3n4o5p6q7r8, 含完整 downgrade
- D1 探测器 app/services/governance_detector.py, 两条铁律写在文件头:
① 逐字符复现索引真实表达式(WHERE 部分条件 / date() / COALESCE) —— 防误报
② 重复 = NULL 视为相等(GROUP BY 语义), 但排除「整组全 NULL」—— 防漏报 wallet
这类真问题, 又不错把「业务允许多个 NULL」当问题
- 每条 finding 产 metrics + 脱敏 samples(敏感列确定性哈希, 保留「是否相等」可判) +
repro_sql(可人工执行核对数字, 拒绝黑盒结论); 同 (detector,表,目标) 未处置时不堆叠记录
- 旧排查脚本改为薄封装(只调服务), 消除两套判据逻辑漂移
测试 tests/test_governance_detector.py 5 例: 表达式部分索引不误报(#161 回归)、
NULL 列重复能检出(#160 回归)、整组全 NULL 排除、部分索引覆盖内重复仍检出、落库幂等。
实测: 本地开发库(未治理)精确报出 wallet 1240 组/15601 行/占比 38.7%, repro_sql 数字一致。
全量 1581 passed; ruff 通过。
2026-09-20 02:49:38 +08:00
e566e7f171
chore(ops): 收尾——补每日定时备份 + 登记同类风险排查结论 + 沉淀 D1 探测器脚本 #161
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 8m3s
backend-test / frontend-lint-test (push) Successful in 36s
AI 治理大任务(#162~#165)前的收尾:
1. C-P1-2 定时备份落地: 排查发现生产从未配置自动备份(仅 upgrade.sh 升级时
顺带全量包) → 已配 /etc/cron.d/csctalon-backup 每日 03:30 跑既有 backup.sh
--keep 14 + logrotate。实测: 738M 包/2273 TOC/3 手工 dump 未被误删。
2. C-P1-7 收尾+同类排查: 登记止损(#160 )/治理(#161 )完成态 + 全库 59 唯一索引
(含可空 19)排查结论 + 备份 09-26 到期清理待办。
3. D1 探测器原型入库 scripts/governance_scan_unique_dup.py:
两条铁律——复现索引真实表达式 + 重复判据(NULL 相等且排除整组全 NULL)。
⚠️ 首版判据曾把"整组全 NULL"当豁免→会在未治理环境漏掉 wallet 真重复,
已修正并用本地库(未治理)反证: 修正后本地库精确报出 wallet 为唯一缺口、
生产库归零。生产实测结论: 无保护缺口。
4. deploy/README §5.1 更新为"已配置"现状。
经本地 + 生产双环境实测(同一脚本, 两种环境结论正确区分); ruff 通过。
2026-09-20 01:56:32 +08:00
5f39a586a5
fix(ai-report): warm_on_startup 只补「当前该有的报告」+ 快照窗口节流 #166
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Failing after 48m40s
backend-test / frontend-lint-test (push) Successful in 37s
周报/月报被服务重启频繁重复生成(生产一天 7~21 次):
原 warm_on_startup 遍历全部 active 模板无条件生成, 与 trigger_at 无关,
每次 upgrade.sh 部署重启就重算一遍。生产实证: 报告生成时刻与部署重启
时刻逐一吻合(09-19 当天 5 次部署 → 5 份重复周报/月报)。
修复:
- 分流: refresh_on_sync=True(日报)启动即生成(行为不变);
refresh_on_sync=False(周报/月报)只在 trigger_at 到点才生成
(新增 _template_due_now, 与 schedule_due_templates 同口径)
- 节流: 新增 _window_generated_recently, 按
(template_id, snapshot_period, snapshot_date) 三元组判「同一份报告」,
20min 内已生成则跳过; 同时替代原 _daily_generated_recently
(两个近似函数并存必然漂移, 收敛为一处)
- 模块 docstring 同步启动预热语义
测试: tests/test_ai_report_worker_schedule.py 新增 6 用例
(未到点周报/月报不生成 + 到点生成 + 日报行为不变 + 同窗口节流 +
节流按 period 隔离); 已用旧实现反证用例能捕获原 bug(3 红)。
全量 1576 passed。
2026-09-20 00:32:37 +08:00
8435c122cf
docs(governance): AI 数据治理框架设计(探测→研判→分级执行)P0
...
用户诉求(#160 实证催生): 数据治理现状是「人发现坏数据→写脚本→人工判断→改数据」,
每次都走开发流程且易误判(C-P1-7 人工扫描 5 个候选索引 4 个误报)。
应由系统 AI 机制自动分析→给建议→分级执行。
框架三层+一闭环:
- 探测层(确定性无AI): D1 唯一索引重复(复现索引真实表达式, 含部分条件/函数/COALESCE
—— #161 教训)/ D2 行数突变 / D3 外键孤儿; 产出结构化证据不下结论
- 研判层(AI只出建议): 区分「真问题 vs 业务允许」; 结论沉淀为 governance_rules(下次短路不调LLM)
- 执行层(分级红线): L0只读/L1可逆自动; L2不可逆(删/改)必须人工确认+强制备份+凭证
采集语义增强(CSV换行截断/错误日期/语义补全)纳入同一框架, 第二期(工单 E)。
分四步实施: A(#162 表+D1) B(#163 D2/D3+编排) C(#164 AI研判) D(#165 接口+页面)。
设计文档: docs/design/AI数据治理框架-20260919.md; 已登记 docs/README.md 索引
2026-09-19 23:48:11 +08:00
d047f866e8
fix(wallet): 部分唯一索引(device_sn NULL) + upsert 分流冲突目标 #161
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 55m41s
backend-test / frontend-lint-test (push) Successful in 45s
C-P1-7 治理: 原 6 列唯一约束在 device_sn 为 NULL 时对 NULL 不去重(PG NULL≠NULL),
导致高频同步对同一笔 device_sn 为空的提现反复插入(#160 事故累积 40603 行重复)。
- 迁移 h3i4j5k6l7m8: 新增部分唯一索引 uq_wallet_withdrawals_null_sn
(account_id,op_type,op_time,operator,op_amount) NULLS NOT DISTINCT WHERE device_sn IS NULL
(PG15+; 表内已有重复时须先按 #160 清理)
- 模型 wallet.py 镜像声明该索引(postgresql_where + postgresql_nulls_not_distinct),
保证 create_all 与迁移一致
- upsert_wallet: 按 device_sn 是否为空**分流到两个冲突目标**(非空走原约束,
空走部分索引 index_elements + index_where), 修复跨批次重复插入
- 新增 tests/test_wallet_dedup.py 5 用例: 跨轮次不重复(NULL SN)/NULLS NOT DISTINCT
覆盖其余 NULL 列/非空 SN 回归/同批次去重/不同金额两笔均保留
- 规范对齐: 数据采集合并去重机制说明.md 的 wallet 行改为实际实现
(原记 business_id 列从未落地)
测试: 全量 1570 passed (2 个宽表测试 fixture 因新索引暴露重复数据, 已加 device_sn 区分);
迁移在 scratch 库 upgrade/downgrade/upgrade 通过
2026-09-19 23:33:09 +08:00
8ac8ebb439
feat(onboarding): 落地包→可提交代码草稿(迁移/模型/seed/overrides) #159
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 7m30s
backend-test / frontend-lint-test (push) Successful in 1m43s
P2: 把落地包从「给人看的建议」升级为「可粘贴的成品代码」, 转写成本降到接近 0。
后端 onboarding_apply_service 新增 apply_package.code_drafts:
- migration_draft: 完整 Alembic 文件(revision/down_revision/create_table/downgrade),
down_revision 从 DB 实读当前 head(SELECT version_num FROM alembic_version),
revision_id 随机 12 hex + 头部提示提交前 grep 确认唯一(撞车会致 Cycle/Multiple heads)
- model_draft: ORM 类(Mapped 风格 + comment + 按需 import datetime/Decimal)
- seed_draft: DATA_SOURCE_SEEDS 条目(对齐 registry 现有写法)
- overrides_draft: FIELD_OVERRIDES 条目(只输出非默认项: 角色非 dimension/is_pk/不可筛选)
- apply_steps: 7 步落地清单; 金额 role=amount → sa.BigInteger()/int(分铁律)
前端 AiOnboarding.vue: 下载迁移/模型草稿 .py + 4 段草稿折叠展示 + 落地步骤;
ⓘ 取舍段补「不写服务器文件」理由(生产容器代码只读, 写了会被下次部署覆盖且绕过 review)
取舍: 草稿一律以文本产出, 由开发者下载到本地仓库 review 后提交; apply 仍不写库/不改码/不建表。
测试: 新增 test_apply_package_contains_parseable_code_drafts(ast.parse 迁移/模型草稿 +
down_revision 等于库 head + overrides 只含非默认项); onboarding 相关 70 passed, 全量 exit 0
设计文档 §六 新增 6.1 节。
2026-09-19 22:25:48 +08:00
72770d9505
docs(onboarding): 补齐 #157 未落盘的设计文档与索引 #157
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 8m12s
backend-test / frontend-lint-test (push) Successful in 38s
中文路径下 Edit 工具静默失败, 导致以下改动此前未落盘(工具返回成功但磁盘未变):
- 设计文档 §七 接口契约: 端点表更新为实际实现(7 端点顺序/权限/编辑限制) +
新增「异步编排实现要点」段(占位行 code 可空 / 后台任务更新同一行 / apply 重跑校验)
- docs/README.md §六 索引: 设计文档状态「评审中」→「已实施(#155/#156/#157/#158)」,
断言数 14 → 16(#158 )
核验: grep 关键标记全部命中(异步编排实现要点=1 / 供前端边填边提示冲突=1 / 已实施=True / 16 条断言=True)
2026-09-19 20:52:54 +08:00
f9413e392f
docs(onboarding): 设计文档 §4.1 规则表补 R15/R16 #158
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
上一条 commit 漏带该文件(Edit 写入未持久化,改用脚本补写):
- R15 同一声明多个 sn 角色 → warn
- R16 pk_field 须出现在 field_specs → warn
2026-09-19 20:48:32 +08:00
56b24ee104
feat(onboarding-validator): 新增 R15 多 sn 角色 + R16 pk_field 引用 #158
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
#157 生产实测暴露的规则覆盖缺口(AI 高置信误判无法靠 R14 拦截):
- R15: 同一声明出现多个 sn 角色字段 → warn。SN 语义唯一, 实测 AI 把 ID 类列
(record_id/stat_id) 判为 sn 且 confidence 0.9+, 与真 device_sn 并存;
registry 无同名字段可对照故 R14 不触发 → 用「声明内部自洽性」兜住。
- R16: pk_field 若声明须出现在 field_specs 中 → warn(与 R9 sn_field 对称)。
实测发现校验器测试基线本身就违反此规则(pk_field="id" 但字段清单无 id)。
测试: 新增 TestDeclarationSelfConsistency 6 用例(正反各半) + 基线自洽化 +
规则覆盖回归扩到 R1~R16; 校验器/起草/路由 69 passed, 后端全量 1522 passed。
设计文档 §4.1 规则表同步 R15/R16。
2026-09-19 20:47:15 +08:00
654857b3f6
fix(ai-onboarding): code 占用处理,修生产实测暴露的起草失败 #157
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
生产实测发现两处缺陷(异步起草路径):
1. 缺 code 唯一性预检 → 直接 commit 撞 uq_data_source_specs_code,
抛 IntegrityError(错误信息对使用者不可读, 表现为"起草失败")。
2. failed/rejected 旧草稿永久占住 code → 同一样例重试时 AI 每次建议同一 code,
重试永远失败(死循环)。
修复: 新增 _release_stale_code(db, code, keep_id) 两条路径共用 ——
- failed/rejected 旧草稿让出 code(置 NULL + error_message 留痕), 重试可继续;
- draft/confirmed/applied 有效草稿占用 → 抛可读 DraftError(须人工处置)。
新增 2 个回归用例(stale 让出 / live 冲突拒绝), 路由测试 19 passed。
2026-09-19 20:12:03 +08:00
198cb8b07d
feat(ai-onboarding): 接口 + admin 管理页 + 落地包 #157
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 7m37s
backend-test / frontend-lint-test (push) Successful in 1m17s
后端
- 迁移 g2h3i4j5k6l7: code 改可空(异步起草占位行)+新增 error_message;
status 值域扩为 drafting/draft/failed/confirmed/applied/rejected(无 DDL)
- 起草服务支持「更新既有占位行」(spec_id) + create_draft_placeholder /
start_draft_task(后台任务自开 session) / _mark_failed 兜底(防 drafting 僵尸行)
- 新增 onboarding_apply_service: confirm(重跑校验+fail清零+warn逐条处置)、
apply(落地包 JSON, 不写库不改码不改表)
- 新增 7 个端点 /api/v1/ai-onboarding/*(202 异步起草 + 轮询), 权限:
草稿可见/可编辑=创建者+admin, confirm/apply 仅 admin
- 17 个路由测试: 异步轮询/权限/编辑重跑校验/confirm warn 处置/apply 断言
(未写 data_sources、未建表、未改代码文件指纹)、后台失败兜底
前端
- 新增 /system/ai-onboarding「数据源接入助手」(归「数据与接入」组):
粘贴样例(CSV/TSV/JSON)→起草轮询→AI 建议+校验报告→人工修正→确认→落地包
- 三层设计说明落地(代码注释块五要素 + ⓘ 气泡含取舍段 + review-checklist 项)
验证: 后端 1515 passed / 前端 110 passed; ruff + eslint + vite build 通过;
迁移在 scratch 库 upgrade/downgrade/再 upgrade 全通过
2026-09-19 19:54:18 +08:00
dd86d4ced7
feat(ai-onboarding): AI 起草服务 + 提示词配置表 #156
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Successful in 5m17s
backend-test / frontend-lint-test (push) Successful in 46s
- onboarding_prompts 配置表迁移(f1g2h3i4j5k6): draft_system/draft_user v1 seed,
幂等 ON CONFLICT DO NOTHING; 参数化插入避免 SQL 字面量转义
- OnboardingPrompt ORM 模型(含设计说明注释)
- onboarding_draft_service 全链路: 样例脱敏(PII 掩码, 证件号优先于手机号)
→ 提示词格式化(DB 可覆盖, 兜底镜像) → LLM 结构化 JSON(temperature 0.2,
source=onboarding_draft, L1 门闸) → schema 校验(非法输出整体重试 1 次)
→ 校验器自动复核(validator_report 落库) → DataSourceSpec(draft) 落库
→ system_events 留痕(category=ai_governance, 含 provider/耗时/fail 数)
- 45 用例: 脱敏/提示词覆盖/校验/重试/事件/冲突拒绝全过; 全量 pytest + ruff 通过
设计: docs/design/AI数据接入助手设计-20260919.md §五
2026-09-19 19:23:20 +08:00
96b5e2f047
feat(ai): AI 数据接入助手 A——data_source_specs 接入声明表 + 规范校验器 #155
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 4m29s
backend-test / frontend-lint-test (push) Successful in 39s
- 新增 data_source_specs 表与 ORM 模型(声明主体对齐 DataSource + AI 产物
field_specs/dedup_recommendation + 校验报告 validator_report),不触碰
现有 data_sources/registry 机制
- 新增 onboarding_validator:R1~R14 纯规则确定性校验(命名/分组/去重红线/
字段角色/金额单位/低置信),规则=函数清单可扩展,fail 未清零禁止确认
- 测试:25 用例覆盖每条规则正反 + build_registry_snapshot 集成 + 规则覆盖回归
- 设计文档 docs/design/AI数据接入助手设计-20260919.md 登记 docs/README §六
2026-09-19 18:36:06 +08:00
2570162138
docs: 补 #154 工具/能力接入架构评估参考系章节
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 31m11s
backend-test / frontend-lint-test (push) Successful in 42s
《跨项目经验迁移指南》v1.0→v1.1,新增 §十「工具/能力接入架构评估
参考系」:以 MCP 设计哲学为评估框架复盘本项目(registry/feed/模板/
接入配置包 已隐含实现其思想),含通用判断清单、对照结论、可落地改
进项、AI 技术框架评估矩阵、第三方录入≠MCP 易混淆点。
2026-09-19 17:07:57 +08:00
bf67daaa89
docs(ai): 补 #153 提示词反馈闭环架构文档与实现计划
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 5m16s
backend-test / frontend-lint-test (push) Successful in 42s
- docs/arch/A-20260919-002-ai提示词反馈闭环.md: 三层闭环(收集→评审→人工迭代)
架构设计(REQ), 含 latest_missing 收集写回/健康报告聚合口径/周期调度契约/
只出建议不自动改提示词取舍, 验收点已随 #153 生产实测
- docs/design/20260919-#153-ai提示词反馈闭环实现计划.md: 派工单形态实现计划
(迁移→写回→服务→调度→测试部署五步 + 契约/约束/禁止/测试/部署凭证)
- docs/README.md: §六登记架构文档索引行
2026-09-19 16:48:58 +08:00
b66f037ffd
feat(ai): AI 提示词反馈闭环——latest_missing 收集 + 周期健康报告 #153
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 5m9s
backend-test / frontend-lint-test (push) Successful in 52s
收集层: ai_reports 补 latest_missing JSONB(迁移 e4f5a6b7c8d9),
L1/L2 standard 校验路径 check_dimensions 后写回缺失清单(record_latest_missing,
直接 commit, 补算路径不 commit 也落库)。
评审层: ai_feedback_service.build_health_report 聚合近7天——问答命中分布
(full/section/live/rule + hit_rate + live_ratio)/按模板快照命中与缺失校验状态/
缺失维度 TopN(含历史快照 check_dimensions 重算兜底)/高频未答好问题/
三层建议(feed供给/模板层/提示词层, 只出建议不自动改)。每日一次落
system_events(ai_feedback_health_report, partial+warning 当有缺失/高频/live偏高),
挂 cache_warmer 空闲循环(8-23点+当日未生成)。
注意: SQLAlchemy JSONB 列存 Python None 落的是 JSON null 而非 SQL NULL,
聚合 SQL 用 jsonb_typeof='array' 守卫"已校验", 避免 jsonb_array_length scalar 报错。
附: docs/README.md 补登记 #152 架构文档索引行(存量补齐)。
2026-09-19 16:24:19 +08:00
9a757298eb
refactor(ai): 维度校验改为纯结构检查, 结构由提示词对齐保证 #152
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Successful in 4m20s
backend-test / frontend-lint-test (push) Successful in 56s
用户拍板: 报告结构源头在提示词, 代码不做语义猜测。
- ai_feed 生成侧【维度输出格式】约定二级锚点行 **<维度>**:<数据>, 缺失写暂无
- ai_extract 摘取侧对齐识别该锚点(优先摘锚点行, 不从旁述拼凑)
- ai_completeness 重写为纯结构匹配(锚点行+非空+非缺失标记), 删除全部
语义正则(数字短路/路由标识/前文检查)——旁述/比较/建议句天然排除
2026-09-19 14:57:29 +08:00
394cd506af
fix(ai): 四路维度收紧为行首标签行, 旁路比较/建议句不再误判为覆盖 #152
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
实测 09-18/09-19 快照: "B 路钱包提现""确认 A 路占比"等旁路句含 A/B/C/D 仍被
误判。四路类维度必须满足: 关键词在行首 12 字符内(标签行) + 冒号数据段 +
渠道标识, 否则判空触发补算。
2026-09-19 14:51:19 +08:00
c79bc4df66
fix(ai): 四路维度要求数据段含渠道标识, 旁路比较句不再误判为覆盖 #152
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
实测 09-18 快照: "- **对账基准 E**:0 笔 / 0.00 元,与四路合计差异 100%" 被
数字短路误判为四路覆盖。四路类维度(KW 含"四路")数据段必须含 A/B/C/D 或
渠道词(线上/线下/门店/网络/公众号/平台/直播/视频/小程序)才算覆盖。
2026-09-19 14:48:03 +08:00
a2e778698e
fix(ai): 数据段分隔支持全角冒号, "**四路分布**:"空锚点行不再误判为覆盖 #152
gitleaks / gitleaks (push) Successful in 5s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
2026-09-19 14:45:03 +08:00
62d5aeb6c3
fix(ai): 缺失语境检测升级——正则"未…提供" + 关键词前文检查 #152
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
实测 09-18 快照: LLM 写"未直接提供四路金额"/"未在给定 JSON 中提供"时,
精确词表"未提供"匹配不到(非连续子串)。改为 _MISSING_RE 正则匹配 +
关键词前 30 字符检查(标记在词前)+ 首片段含数字短路(部分未提供不算空)。
2026-09-19 14:43:05 +08:00
eb5c657a07
fix(ai): 完整性校验补「缺失」标记, 数据缺失语境不再误判为已覆盖 #152
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 4m33s
backend-test / frontend-lint-test (push) Successful in 52s
生产实测(09-18 快照)发现: 四路行写"数据缺失——未提供 A/B/C/D", 而校验器
把同段"四路退款金额及涉诉率数据缺失"误判为覆盖(缺失词不在标记表) →
complete=True 掩盖真实缺失。补 "缺失" 进 _MISSING_MARKERS, 触发补算更诚实。
2026-09-19 14:33:25 +08:00
274f01436e
feat(ai): AI 回答模式分级框架——简报/明细/深度/兜底四级链路 #152
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 56m27s
backend-test / frontend-lint-test (push) Successful in 57s
#152 落地(A-20260919-001):
- 模板配置: ai_report_templates 加 detail_level + required_dimensions(JSONB),
迁移 a7b6c5d4e3f2 种子回填日报(standard)/周报·月报(deep)维度清单
- 三服务: ai_mode_service 意图分级(规则, deep>standard>brief)/
ai_completeness_service 维度完整性校验(排除"未提供/暂无"空语境)/
ai_recompute_service 补算器(同窗口完整快照优先, 无则 needs_live 交实时)
- ai.py 路由: L1/L2 命中按问句分级——brief 快照摘取秒回/standard 校验+补算/
deep 实时 27b 注入 required_dimensions; 兜底层 deep_req(太简单/再详细/重新分析)
跳过命中链直接实时重算, 结果前缀「我现在去深度分析」; 响应带 mode/source
- 预生成(ai_report_worker)与 admin 模板管理支持新字段
- 测试: 3 新测试文件 20 用例, AI 回归 138 passed
2026-09-19 12:48:13 +08:00
e1e4efa13a
fix(ai): 问句窗口与预生成报告窗口对齐, 修复"昨天"命中今日日报 #151
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 4m39s
backend-test / frontend-lint-test (push) Successful in 44s
现象: 问「昨天的退款明细分析」窗口识别为 09-18(昨天), 内容却是今日快照
「统计窗口 09-19 00:00 至 11:58(当日实时, 预生成)」, 窗口与内容错位。
根因: _AiWindow.effective() 对区间形态(单日也是区间)返回 date=None →
matcher SQL 落入 snapshot_date=今天 分支 → 命中今日日报。
同类问题: 问「上周/上月」会命中本周/本月周月报。
修复(三层):
1. ai.py: 单日区间(start==end)回退为 win_date 语义, 且把完整窗口
(win_start/win_end)透传给匹配层。
2. match_template_report/list_candidates 三态查询:
- 区间窗口(start<end): 用报告内容窗口[start,end]与问句窗口重叠匹配
(weekly/monthly 预生成已落 start/end, #132 ); daily 日报 start/end=None
被排除 → 问「上周」不再命中本周周报/今日日报;
- 单日: 按 snapshot_date 匹配(日报只能靠快照日期对齐);
- 无窗口: 取当日(原逻辑)。
测试: matcher 新增 3 用例(单日走 snapshot_date / 区间走重叠 / start==end
回退), 76 例全过。
2026-09-19 12:09:48 +08:00
60eab066e0
fix(ai): 闲聊不落库 + 不依赖 LLM 开关 #150
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 4m22s
backend-test / frontend-lint-test (push) Successful in 40s
按审查决策处理两处取舍:
1. 闲聊闸门移至 _require_enabled() 之前: LLM_ENABLED=false(助手禁用)时
闲聊问句仍应答固定引导文案而非 503(闲聊不需要 LLM);
业务问句行为不变(_require_enabled 移至闲聊后、LLM 时间兜底前)。
2. 删除闲聊落库(save_report hit_mode=chitchat): 闲聊无经营信息,
落库只污染「我的待办」历史。
测试: 新增 test_ai_analyze_chitchat_ok_when_llm_disabled(LLM 禁用时闲聊 200),
test_ai_analyze_disabled_503 保持通过(业务问句 LLM 禁用仍 503), 53 例全过。
2026-09-19 11:54:12 +08:00
379093ad69
fix(ai): 闲聊闸门误杀面修复——"加班了/下班没"不再当闲聊 #150
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 4m14s
backend-test / frontend-lint-test (push) Successful in 1m22s
代码审查发现: "加班了""下班没"等运营/值班短句(≤12字符、无业务词表词)
会被剥离寒暄短语后判空 → 误命中闲聊闸门 → 返回引导文案而非经营应答。
修复: 从 _CHITCHAT_PHRASES 移除"加班/下班没/下班了么", 业务词表新增
"加班/下班/值班/挂/运营"(先于短语剥离检查, 命中即放行)。
30 例误杀面扫描 30/30 匹配, 新增 4 个回归用例(加班了/下班没/谁在值班/服务器挂了吗)。
2026-09-18 19:47:35 +08:00
7f2776ada8
fix(ai): 闲聊闸门+无关拒绝,修复寒暄问句误命中日报与处理慢 #150
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Failing after 1m15s
backend-test / frontend-lint-test (push) Successful in 10m52s
问题: 「吃饭了没」等闲聊问句无经营意图,却走 L2 语义匹配(7b)+摘取层(7b)
两次 LLM,7b 对无关问句与日报候选给出 ≥0.6 模糊置信度 → 误命中当日日报,
返回一屏接待分析 + "未提供关于'吃饭了没'的相关信息";且触发 LLM 时间兜底
致秒级延迟。
修复:
1. ai_rule_engine 新增 try_chitchat_answer 闲聊闸门(毫秒短路, 不进 LLM):
- 判据: 剥离寒暄短语+语气词后剩余为空(纯闲聊) → 短路;
- 含业务词(退款/接待/今日...)一律放行, 零误杀("你好,今日退款如何"正常走链路);
- 问句 >12 字符放行; 任何异常放行(主链路零影响)。
2. ai.py 在窗口解析后、LLM 时间兜底前接入闸门: 闲聊不再触发 LLM 时间解析
(慢的来源), 返回固定引导文案(不生成经营数据), 落库私有报告 hit_mode=chitchat。
3. ai_semantic_matcher 加无关拒绝铁律: 无关问句(闲聊/寒暄/个人话题)所有候选
confidence 必须输出 0, 杜绝 7b 对无关问句的模糊命中。
测试: test_ai_assistant.py 新增 3 用例(闸门命中/业务问句放行/端到端短路),
84 个 AI 相关测试全过。
2026-09-18 18:03:10 +08:00
379c049e1f
feat(shared-doc-login): #149 登录历史抽屉加来源列 + 覆盖提示展示同平台受影响源数
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Failing after 44s
backend-test / frontend-lint-test (push) Failing after 25m20s
2026-09-18 16:49:32 +08:00
07518022d3
feat(shared-doc-login): #149 登录态可管理性 — 登录时间/账号展示 + 登录历史表 + 独立登录态文件
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 1m15s
backend-test / frontend-lint-test (push) Failing after 4m18s
① 登录态文件内嵌 _meta{last_login_at,uid,cookies,source_id}(save 时写, 不影响 Playwright 重新加载),
state_health 优先读 _meta 展示真实登录时间/账号, 旧文件回退 mtime;
② AccessCenter 登录态表加「登录账号/登录时间」列 + 采集源卡片加「登录历史」抽屉(GET /login/logs 分页);
③ 新建 doc_sync_login_logs 表(migration dc20f480c17e) + confirm 成功/失败写历史, 保留 90 天;
④ 源列表显示「共享/独立」登录态, 编辑可指定独立 state_file(纯文件名+ .json 校验防路径穿越),
扫码登录前提示覆盖共享 or 写入独立文件。
2026-09-18 16:43:37 +08:00
18097a9d34
fix(shared-doc-login): #148 金山 chooseaccount 页加 DOM dump + 选账号选择器
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 45s
backend-test / frontend-lint-test (push) Successful in 7m27s
日志: 扫码后停在 account.wps.cn/v1/chooseaccount, 现有按钮选择器(#confirmCode/
js_toProtocolDialog/文本)全部未命中 —— 该页是"选择账号"列表而非登录按钮页。
修复:
① 该页 DOM dump 到日志(可点击元素 tag/class/id/文本/坐标), 下次扫码即可定位真实选择器
② 选择器追加 [class*='account']/.account-item/li.my-account 等选账号形态
③ 兜底: 仍无命中时点击页面首个可见 [class*='account'] 或 li
2026-09-18 16:02:30 +08:00
c472572b2c
fix(shared-doc-login): #148 金山 chooseaccount 先等3s再找按钮+加#confirmCode选择器
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Failing after 44s
backend-test / frontend-lint-test (push) Successful in 3m54s
日志: 扫码后页面停在 chooseaccount(不是 mobilelogin), 旧选择器未匹配到按钮。
chooseaccount 可能需要几秒才跳到 mobilelogin(有 #confirmCode 按钮)。
修复: 先等3s让页面跳转, 再用 #confirmCode/js_toProtocolDialog/文本选择器逐个找。
找不到时打 warning 日志。
2026-09-18 15:40:23 +08:00
fdf7b81d1e
fix(shared-doc-login): #148 金山扫码后自动点中间页"Login"完成登录
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Failing after 44s
backend-test / frontend-lint-test (push) Has been cancelled
根因(日志): 金山微信扫码后跳到 account.wps.cn/v1/mobilelogin?loginerrcode=HaveNotVerify,
页面有"Login/Register Now"按钮需手动点击才完成登录。无头浏览器里用户看不到点不到,
导致页面一直停在中间页, __WPSENV__.user.id 始终为空/fake。
修复: verify 第一步前检测到 URL 含 account.wps.cn 时, 自动点击
js_toProtocolDialog/Login/登录 按钮并等 5s 跳转回 kdocs。
2026-09-18 15:31:50 +08:00
11a0a71afc
fix(shared-doc-login): #148 verify 不 goto 打断回调, 先在当前页检查
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Failing after 1m15s
backend-test / frontend-lint-test (push) Has been cancelled
根因(日志): 金山 verify goto doc_url 重新加载页面冲掉了 __WPSENV__.user.id
(用户扫码后页面已自己跳回 kdocs 并设好了 __WPSENV__, goto 重载又变回 fake_)。
修复: verify 三步渐进检查:
① 先在当前页(不 goto)检查访客标识, 通过则直接返回
② 不通过则等 8s 再查(回调慢)
③ 仍不通过才 goto doc_url 复查
测试: _StuckPage 改为初始 URL 就在登录页(新逻辑先查当前页不 goto)
2026-09-18 15:28:12 +08:00
c92fc20a1e
fix(shared-doc-login): #148 verify 用同步层一致的访客检测, 不靠 cookie 名
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Failing after 1m15s
backend-test / frontend-lint-test (push) Has been cancelled
根因(日志): 金山登录态校验是 JS __WPSENV__.user.id 不以 fake_ 开头, 不是
cookie 名 wps_sid(可能金山根本不用这个 cookie)。旧 verify 查 wps_sid 必败,
用户扫码成功也被判失败。
修复: verify 改用与 shared_doc_sync_service._check_*_guest 一致的判据:
- 腾讯: goto doc_url → body 不含 skeleton-guest-mode + cookies 含 skey/uin
- 金山: goto doc_url → __WPSENV__.user.id 不空且不以 fake_ 开头
goto 后多等 8s 让微信回调设 cookie/设 __WPSENV__。
2026-09-18 15:09:11 +08:00
0616f256b6
fix(shared-doc-login): #148 verify 严格查会话 cookie + 等微信回调完成
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Failing after 44s
backend-test / frontend-lint-test (push) Has been cancelled
根因(日志定位): 用户扫码确认后微信侧 cookie(access_token/openid) 立即就位,
但腾讯文档 skey/uin、金山 wps_sid 要等微信回调跳转(约 3~8s)才设上。旧 verify
只查「有任何 cookie」→ 访客态 cookie(uid/TOK 但无 skey)被当登录态保存 → 同步层
校验时「服务端不认账」→ 用户看到失败。
修复:
1. 先在当前页等 5s+5s 让微信回调设上 skey/uin/wps_sid(不 goto, 避免打断回调)
2. 严格查 _SESSION_COOKIE_HINTS(腾讯 skey/uin; 金山 wps_sid/wpsua), 不满足直接
返回「未检测到登录态 Cookie, 请在手机上确认微信授权后重试」
3. 会话 cookie 就位后再 goto doc_url 确认不在登录页/二维码
2026-09-18 15:01:04 +08:00
3076bf8f44
fix(shared-doc-login): #148 verify 加详细日志定位 confirm 失败原因
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Failing after 45s
backend-test / frontend-lint-test (push) Has been cancelled
用户扫码后点确认仍判失败, verify 每步判断无日志无法定位。
加 logger.info 记录 final_url/in_login/qr_visible/cookie 列表, 部署后让用户再试。
2026-09-18 14:55:26 +08:00
d049c3e355
fix(shared-doc-login): #148 腾讯微信码在跨域 iframe, 按 iframe 卡片裁剪
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 45s
backend-test / frontend-lint-test (push) Has been cancelled
生产 DOM 探测确认: 腾讯微信二维码位于 open.weixin.qq.com/connect/qrconnect 跨域
iframe(主文档无法穿透取内部元素), 之前误命中的 apng canvas 实为视口外(y>3300)首页
营销动画。修复:
- _QRCODE_BOX_JS/PRESENT_JS: 新增可见微信二维码 iframe 判据(src 含 weixin+qr、在
视口内), box 标记 kind=iframe; img/canvas 候选补「完整在视口内」约束, 排除营销动画;
- _browser_screenshot: iframe 盒即完整登录卡, 只加 12px 边距(不再按方形码外扩),
二维码占画面主导、2x 下清晰可扫。
2026-09-18 14:44:26 +08:00
4d2a4580b1
fix(shared-doc-login): #148 识别腾讯微信 APNG 动画 canvas 二维码
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Failing after 44s
backend-test / frontend-lint-test (push) Successful in 5m44s
生产 DOM 探测: 腾讯微信码不是 img, 也不是 class 含 qr 的 canvas, 而是 16 层叠加的
<canvas class="common-apng-item-in/out">(200x200 APNG 扫描线动画)。导致:
1) 截图聚焦 _QRCODE_BOX_JS 找不到元素 → 腾讯一直回退整页截图(二维码被缩太小);
2) _QRCODE_PRESENT_JS 同样漏判 → 腾讯登录页二维码状态检测实际为 false(状态判态隐患)。
两处 canvas 选择器补充 canvas[class*="apng"]; 多层重叠取面积最大, bbox 一致无副作用。
2026-09-18 14:36:02 +08:00
0565832aac
fix(shared-doc-login): #148 二维码截图聚焦放大 + 金山协议自动勾选
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Failing after 44s
backend-test / frontend-lint-test (push) Has been cancelled
实测生产(192.168.10.21)两家并行登录均已出码(上一线程隔离修复生效), 但:
1. 金山登录卡「同意协议」checkbox 默认不勾, 扫码确认后无法回跳完成登录
→ 新增 _ensure_agreement_checked, kdocs 二维码展开后勾上所有未选 checkbox(幂等)。
2. 整页 1600 宽截图里二维码仅占 ~11%, 前端缩到 ~480px 弹窗后只剩 ~50px 扫不出
→ new_context 加 device_scale_factor=2(物理像素翻倍);
→ 新增 _QRCODE_BOX_JS 定位二维码包围盒, _browser_screenshot 按盒裁剪(含标题/协议
留白, 二维码占画面 ~1/3、约 380 物理像素), 定位失败自动回退整页截图。
2026-09-18 14:31:11 +08:00
ccca726e52
fix(shared-doc-login): #148 每个登录会话独占工作线程, 根治 Sync API asyncio 冲突
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 2m2s
backend-test / frontend-lint-test (push) Has been cancelled
根因(比表面更深): Playwright Sync API 判定用 asyncio.get_running_loop()(不看
set_event_loop, 故上一版 set_event_loop 修复无效)。sync_playwright().start() 后,
其 dispatcher greenlet 永久挂在 loop.run_until_complete(run_as_sync()), 把所在 OS
线程的 thread-local running-loop 一直置位。FastAPI sync 端点跑在 anyio 共享线程池:
① 线程复用 → 第二个会话/或同会话后续 status/confirm 落到被占用线程即报
"It looks like you are using Playwright Sync API inside the asyncio loop"(腾讯
会话存活时再开金山 → 金山 500, 实测复现);
② Playwright 对象绑定创建线程, 跨线程做 screenshot/status/confirm 同样会崩。
修复: 新增 _SessionWorker —— 每个登录会话一条专属 daemon 线程 + queue 任务队列,
所有 Playwright 操作(_browser_open/probe/screenshot/verify/save/teardown)都在该
线程顺序执行; Manager 方法只做短路判定并 worker.run() 提交。彻底避开线程池复用与
跨线程对象访问, 且不再依赖 set_event_loop。
测试: 新增 _FakeWorker(同步 run), _make_session 改为注入 fake worker; 41 个用例全过。
2026-09-18 14:23:05 +08:00
5458e774ab
style(shared-doc-sync): #148 修正 asyncio/playwright import 排序(ruff I001)
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Successful in 4m26s
backend-test / frontend-lint-test (push) Successful in 54s
2026-09-18 14:06:18 +08:00
b83f0b3a7d
fix(shared-doc-login): #148 修复页面内扫码二维码不显示/不清晰
...
gitleaks / gitleaks (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
backend-test / frontend-lint-test (push) Has been cancelled
腾讯: start() 原先直接 goto 共享表 doc_url → 进访客只读视图看到表格内容,
无登录二维码。改为打开腾讯首页 → 点登录 → 勾服务协议 → 点 Log in now
显示扫码二维码(新增 _open_tencent_qrcode, 与 CLI 脚本同一流程)。
confirm 时 verify_logged_in 仍 goto doc_url 严格校验, 登录态是账号级 cookie。
金山/通用: viewport 1280x800 → 1600x1000, Xvfb 屏幕同步放大,
登录弹窗二维码过小扫不清的问题解决。
2026-09-18 14:06:09 +08:00
a642e2b0e4
fix(shared-doc-login): #148 修复页面内登录 500 — Playwright Sync API 与 asyncio 循环冲突
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Successful in 4m12s
backend-test / frontend-lint-test (push) Successful in 52s
根因: /api/v1/sync/shared-docs/{id}/login/start 用 Playwright Sync API,
但 FastAPI(uvicorn worker)的 sync 端点跑在线程池, Playwright 仍通过
asyncio.get_event_loop() 检测到主事件循环在运行 → 抛
"It looks like you are using Playwright Sync API inside the asyncio loop" → 500。
修复: 在每次调用 sync_playwright() 前, 给当前线程设一个全新的、非运行的
事件循环(asyncio.set_event_loop(asyncio.new_event_loop())), 让
is_running() 返回 False, 绕过检测。
影响文件:
- shared_doc_login_service.py start(): 页面内登录启动浏览器
- shared_doc_sync_service.py _export_tencent_xlsx / _export_kdocs_xlsx: 同步导出
2026-09-18 13:30:39 +08:00
d9f2696360
fix(deploy): #148 同步修改 upgrade/package/install 脚本, 仅保留两个登录脚本进生产
...
gitleaks / gitleaks (push) Successful in 4s
backend-test / backend-lint-test (push) Successful in 4m24s
backend-test / frontend-lint-test (push) Successful in 41s
upgrade.sh --git 模式原先 rm -rf backend/scripts 导致登录脚本被删。
三处部署入口保持同一口径: 调试脚本不进生产, 但 tencent_doc_login.py /
kdocs_login.py 两个运维工具必须保留(生产环境需 headless 扫码更新登录态)。
- upgrade.sh: rm -rf backend/scripts → find ! -name 登录脚本 -delete
- package.sh: tar --exclude backend/scripts → tar cf 排除 + tar rf 追加两脚本 + gzip
- install-server.sh: tar pipe 排除 backend/scripts → 之后 cp 两个登录脚本
2026-09-18 12:58:44 +08:00
7ceefdc083
refactor(shared-doc-login): #148 登录脚本移入 backend/scripts/ 进入生产镜像
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
scripts/ 在仓库根, 但 docker build context 是 backend/, 导致登录脚本无法
打包进镜像。移到 backend/scripts/ 后, 配合 .dockerignore 的 scripts/* + ! 例外,
两个登录脚本进入生产镜像, 生产环境可直接 docker exec 跑 headless 扫码。
- git mv scripts/{tencent_doc_login,kdocs_login}.py → backend/scripts/
- 修正 sys.path: 脚本现在位于 backend/scripts/, _ROOT 即 backend/
- 更新 docstring 用法路径(本机 ./scripts/ 而非 ../scripts/)
2026-09-18 12:54:52 +08:00
c67bbcf0b8
fix(docker): #148 修正 .dockerignore 例外语法(scripts/* 才能 ! 例外)
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
2026-09-18 12:50:02 +08:00
32e9b49beb
feat(shared-doc-login): #148 登录脚本支持 headless 模式, 生产环境可扫码更新登录态
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
需求: 生产服务器无 GUI, 无法弹浏览器扫码; 需要在生产容器内通过截图二维码
+ 轮询登录态的方式更新登录态, 而不是本机扫完再拷 cookie 上去。
改动:
- scripts/tencent_doc_login.py / scripts/kdocs_login.py:
· 新增 --headless 模式: 用 headless 浏览器打开登录页, 截图二维码到 --qr-out,
轮询真登录凭证(腾讯 skey/uin; 金山 __WPSENV__.user.id 非 fake_)出现后自动保存
· 默认仍为 GUI 模式(本机用, 弹浏览器 + 按回车)
· 修复路径兼容: 本机 _ROOT/backend 与生产容器 /app 两种布局都能 import app
· 保留登录态有效性校验(防误存访客态)
- backend/.dockerignore:
· scripts/ 整体排除(审计 C-1/TST-02)基础上, 给两个登录脚本开例外
(!scripts/tencent_doc_login.py, !scripts/kdocs_login.py), 使其进入生产镜像
用法(生产):
docker exec -e PYTHONPATH=/app csctalon-backend \
python /app/scripts/tencent_doc_login.py --headless \
--qr-out /tmp/tencent_qr.png --timeout 300
2026-09-18 12:48:17 +08:00
b1d5846d27
fix(shared-doc-login): #148 修复扫码登录脚本的两个 UX 问题
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
1. 腾讯: 直接打开共享表 URL 会进访客只读视图, 不引导扫码登录
→ 改为先打开 https://docs.qq.com/login?from=docs 登录页(直接显示二维码),
用户扫码成功后再跳转到目标共享表
→ 兜底: 若登录页未出现, 尝试点击页面上的"登录"按钮
2. 金山: viewport 1440x900 导致登录弹窗二维码太小看不清
→ viewport 放大到 1600x1000 + 启动参数 --start-maximized 最大化窗口
→ 兜底: 若登录弹窗未自动弹出, 依次尝试"登录"/"立即登录"/button 按钮
2026-09-18 12:28:20 +08:00
68ca85f50e
fix(shared-doc-sync): #148 修复导出选择器 + 登录态防退化守卫
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
实测根因(生产容器内):
1. 金山菜单按钮在 xy=(38,6) wh=(28,28), 老坐标 (95,15) 完全错过 → 改用
page.locator(".app-header-more-btn") 选择器抗改版
2. 腾讯顶部已是 ribbon-toolbar(开始/插入/数据/公式/协作/视图), 右上角 ⋯ 更多
菜单已下线, 老坐标 (1361,15) 失效 → 暂保留坐标 + 加守卫, 等用户重新扫码
确认真登录态后再探测新前端导出入口(届时另起 commit 更新选择器)
3. 真根因不止选择器: cookies 落盘 ≠ 登录态生效 —— 用户访客身份也能 goto 成功,
但点击菜单触发"立即登录"模态拦截, hover 30s 超时被 #49 误判为"选择器失效"
改动:
- backend/app/services/shared_doc_sync_service.py:
· 新增 _check_tencent_guest (body.skeleton-guest-mode 检测)
· 新增 _check_kdocs_guest (__WPSENV__.user.id 以 fake_ 开头检测)
· 新增 _save_debug_screenshot (失败时落盘 .export_debug_*.png)
· _export_tencent_xlsx / _export_kdocs_xlsx goto 后立即检测访客,
失败时抛 PermissionError 而非让 hover 30s 超时(清晰报错)
· _export_kdocs_xlsx 改用 .app-header-more-btn 选择器代替坐标 (95,15)
· hover/click 失败时均落盘截图便于后续排查
- scripts/tencent_doc_login.py / scripts/kdocs_login.py:
· 保存 storage_state 前校验登录态真生效(防误存访客态)
· 腾讯: body 不应含 skeleton-guest-mode + cookies 应含 skey/uin 之一
· 金山: __WPSENV__.user.id 不应以 fake_ 开头
· 校验失败: 拒绝保存, 提示用户回到浏览器确认表格已正常显示后重试,
二次复校仍失败放弃保存(return 2)
- backend/tests/test_shared_doc_sync_export.py (新增):
· 13 用例覆盖访客检测、新金山选择器路径、失败截图守卫
· monkeypatch playwright 层, 不开真浏览器
· 13/13 全绿, 既有 test_shared_doc_sync.py 36/36 全绿(无回归)
2026-09-18 12:18:44 +08:00
69037f4702
feat(integration): #47 模板版本回滚 + #48 多 worker 部署守卫
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 4m23s
backend-test / frontend-lint-test (push) Successful in 1m58s
#47 : integration_profiles 加 previous_content_md 单字段(C 方案), 每次 update 前快照旧 content_md;
rollback_profile 把 previous 写回 content_md 并把当前存为新 previous(可逆回滚)。
API: POST /integrations/profiles/{id}/rollback 二次确认(confirm=true)。
前端: AccessCenter 模板编辑弹窗加「历史版本」区段 + 回滚按钮 + 二次确认。
测试 8 用例: 更新快照/同内容不覆盖/可逆回滚/连续两次往返/无历史400/二次确认/404/权限。
#48 : shared_doc_login_service 加 MultiWorkerDeploymentError + is_multi_worker
(multiprocessing.parent_process 判据) + check_single_worker(A2 WARNING 不 fail-fast)。
main.py lifespan 启动调 check_single_worker; 4 个 login 端点(/login/start|status|confirm|cancel)
加守卫多 worker 时返 501 + 指向「命令行」按钮。其他业务不受影响。
测试 9 用例: is_multi_worker 3 分支 + check_single_worker 单/多 + 4 端点 501 + 单worker放行 + /states+/cli-fallback 不受守卫。
2026-09-18 10:58:09 +08:00
dea53c79cd
feat( #147 ): AI历史区分命中粒度 + 模板关键词扩充
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 1h7m25s
backend-test / frontend-lint-test (push) Successful in 4m11s
- ai_reports 新增 hit_mode 列(迁移 c8d9e0f1a2b3):
full=命中·全量/section=命中·局部/live=未命中·实时/rule=规则秒回
- L1/L2 命中路径上次直接 return 不落库 → 历史无法区分粒度。
现命中也落本人私有报告(_persist_hit), 归因到源模板快照; 不改 matched_count
- 规则层/实时LLM分别标记 rule/live; list_reports/get_report 返回 hit_mode
- 前端 Agenda 我的待办·AI报告历史 每行加「命中」Tag 区分四态并保留内容查看
- 模板 question_keywords 扩充(迁移 b3d4e5f6a7c8)提升日常问法命中率
2026-09-17 21:16:37 +08:00
a02bf9e828
feat(ai): #146 模板列表显式标识绑定模型(27b/122b/7b 可读 label)
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 13m56s
backend-test / frontend-lint-test (push) Successful in 56s
前端 AI 报告模板表格新增「模型」列, 将裸 llm_provider_code 显示为可读标识:
- talon-gmk-qwen3.8-27b → 「27b·实时问答」(绿, 日常链)
- talon-gmk-qwen3.5-122b → 「122b·预生成」(橙, pregen_only)
- talon-gmk-qwen2.5-7b → 「7b·语义」(蓝)
- 留空 → 「自动(优先级)」(灰)
便于日常一眼看出模板默认走哪个模型, 与 #146 改绑 27b 配套。
2026-09-17 20:47:21 +08:00
9f7958007d
fix(ai): #145 摘取层/语义匹配改指定轻量 qwen2.5:7b 经 talon
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 13m52s
backend-test / frontend-lint-test (push) Successful in 1m7s
#145 消除大模型冷加载超时(用户问「本月经营数据」60s 超时根因):
extract_section / semantic_match 原 provider_code=None 走 default 链,
优先命中最慢的 27b(GMK 冷加载超时)。现指定 _EXTRACT_PROVIDER_CODE /
_SEMANTIC_PROVIDER_CODE = talon-gmk-qwen2.5:7b(经 talon-api 4080 统一管理接入,
#131 透传无 think 坑)。语义摘取/匹配是封闭分类任务, qwen2.5:7b 足够且已常驻内存秒回。
timeout_s 30→60(轻量模型更稳), 保留 clarify 兜底。实时问答仍走 27b(深度分析)。
测试: ai_extract/ai_semantic/ai_report_matcher 32 passed; ruff 全过。
2026-09-17 20:25:26 +08:00
8c0ecae778
fix(ai): #144 摘取层规则短路 + _visibility_clause 修正
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Successful in 38m2s
backend-test / frontend-lint-test (push) Successful in 3m9s
#144 修复两个问题:
1. 摘取层超时: extract_section 调 27b, 但生产 ollama 只加载了 122b,
27b 冷加载 35s > 30s timeout 必超时。加规则短路: 当 template 不为 None
且 _count_keyword_hits >= 2 时直接返 report.answer_md 全文, 不调 LLM,
跳过冷加载, <1s 返回。复用 ai_report_matcher._count_keyword_hits 函数,
全树统一 keyword 命中计数口径。
2. 我的待办空: #142 把 _visibility_clause 改成 `user_id = :uid` 后,
admin tanzk(id=3)看不到任何报告(预生成报告都是 user_id=1, is_public=TRUE)。
改为 `user_id = :uid AND is_public = FALSE`: 「我的待办」只看本人私有
实时问答; 预生成报告(is_public=TRUE)走「AI 与分析」入口。
测试: 新增 4 个摘取层规则短路场景测试, 全量 202 个 AI 测试 0 回归。
2026-09-17 19:36:56 +08:00
1063bcd3a0
fix(ai): L1 命中按 keywords 命中数排序, 修复跨天区间被 eff.period=weekly 误导 ( #143 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
#143 修复 #135 L1 命中链路的排序 bug: 用户问「本月经营情况总览,
含同比环比...」(模板 3 月报原文) 未命中月报, 反而命中 daily 日报,
导致摘取层返回「基于本周同期」内容。
根因:
1. effective() L182-186 把跨天区间统一改为 period=weekly (让 feed grain
走 week, #66 设计)
2. match_template_report SQL `ORDER BY (snapshot_period=:period) DESC`
用 eff.period=weekly, 让周报排前面
3. 遍历第一条 keywords 命中即返回: id=95 daily keywords 含 '整体'
在用户问句里 → 误命中 daily (1 个 keyword 命中)
4. 应命中 id=93 monthly: keywords 含 '本月'+'同比' 都在用户问句里
(2 个 keyword 命中), 应优先
修复方案:
- 新增 _count_keyword_hits(question, keywords) 函数返命中数
- match_template_report 候选遍历前先按「keywords 命中数 DESC +
period 匹配 DESC + created_at DESC」综合排序
- 取命中数 > 0 的第一条返回
- 不改 effective() (保留 #66 跨天区间走 weekly grain 的 feed 设计)
- 不改 SQL 候选集 (保持 is_public=TRUE AND snapshot_date=:sdate)
测试: 新建 test_ai_report_matcher.py 9 个测试(5 个 _count_keyword_hits
纯函数 + 3 个 match_template_report 排序逻辑 + 1 个 _question_matches_keywords
向后兼容), 覆盖核心场景「多 keyword 命中优先于单 keyword 命中」。
ruff 全过, 233 ai 测试全过(原 224 + 新 9)。
2026-09-17 19:24:12 +08:00
f239ccc411
fix(ai): AI 报告历史入口去重 + 按模板折叠展示 ( #142 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 3m53s
backend-test / frontend-lint-test (push) Successful in 34s
#142 推进菜单语义清晰化第三轮, 修复 #116 + #141 后两入口重复问题
(我的待办 vs AI 与分析 对 admin 都看全部 = 重复)。
A. ai_report_service._visibility_clause 改为只看 user_id=:uid
(不管 is_public, 不管 admin), 我的待办只显示本人用 AI 的轨迹
(自己的实时问答 + 自己触发的预生成), 不再显示他人触发的预生成公共报告。
is_admin 参数保留兼容现有调用方, 但语义统一(本人 user_id)。
B. admin_ai.list_admin_reports 默认 WHERE is_public=TRUE (仅预生成报告池),
加 include_private 参数(默认 false, true 才含他人私有实时问答)。
admin 在 AI 与分析看到的默认就是 AI 系统预生成报告池,
不再跟我的待办重复。
C. AiReports.vue 列表改用 el-collapse 按模板分组:
- 按 template_id 分组(NULL 归「实时问答」排最后)
- 每组头部 KPI: 模板名 + 周期 + 报告数 + 累计命中 + 最近生成时间
- 展开后看该模板报告列表(查看/删除)
- include_private 切换(默认 false), 模板筛选去掉(折叠已分组)
- 设计说明三层落地更新(口径段强调两入口分工)
测试: ruff 全过, pytest 224 ai 测试全过, vite build 8.57s PASS。
2026-09-17 18:01:02 +08:00
e9166b125e
fix(ai): #141 修正列名 content → answer_md (凭印象写 SQL 踩坑)
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 3m57s
backend-test / frontend-lint-test (push) Successful in 35s
2026-09-17 17:35:12 +08:00
dbba0369a9
fix(ai): AiAdmin 重组 + AI 报告历史独立 + 报告内容查看 ( #141 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
#141 推进菜单语义清晰化第二轮, 修复 #137 5 Tab 设计问题(用户反馈:
事务通知不该在 AI 管理里, AI 报告历史应该独立到 AI 与分析分组, 报告
本身看不到内容)。
A. AiAdmin.vue 移除 Tab1 报告列表 + Tab5 事务通知, 剩 3 Tab
(模板/模型/提示词, 配置侧), 设计说明三层落地同步更新。
B. 新建 AiReports.vue 独立报告查看页(/system/ai-reports)
- 列表 + 「查看」按钮 → el-drawer 显示完整 content (marked markdown 渲染)
- 元信息侧栏: 模板/快照日期/周期/可见性/命中次数/模型/耗时/tokens/问句
- 列表保留: 删除/筛选(template_id/is_public)/分页
- 设计说明三层落地(代码注释 + ⓘ 气泡 + review-checklist)
C. router/index.js 加 /system/ai-reports 路由(meta: admin, icon: Document)
D. menu.js AI 与分析 子项加「AI 报告历史」
子项: AI 管理 + AI 报告模板 + AI 报告历史
E. admin_ai.py 加 GET /reports/{id} 返回完整 content
列表接口不返 content 防过大(83 条 × 数 KB), 详情按需拉取
F. adminAi.js 加 getAiReport(id) 封装
测试: menu.spec.js 12 passed(新增 ai-reports 路径断言), ruff 全过,
224 ai 测试全过, vite build 8.96s PASS。
2026-09-17 17:33:02 +08:00
11352f0663
fix(menu): 修复 #137 漏改菜单 + 重组「投诉采集质量」到「数据质量」分组 ( #140 )
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Successful in 4m1s
backend-test / frontend-lint-test (push) Successful in 1m3s
#137 只改了 router/index.js 让 AI 管理路由可达, 漏改 web/src/config/menu.js,
导致侧栏菜单看不到「AI 管理」入口(用户反馈「左边菜单没有看到这个 AI 管理」)。
同时按用户反馈把「投诉采集质量」从「数据与接入」移到独立「数据质量」子分组。
A. 新增顶级「AI 与分析」分组(在 数据分析 与 系统管理 之间)
- icon=MagicStick, roles=['admin'], buildMode='always'
- 子项: AI 管理(/system/ai-admin) + AI 报告模板(/system/ai-templates)
- 设计说明三层落地: menu.js 注释块含「目的/逻辑/口径/取舍/工单」五要素
B. 系统管理下新增「数据质量」子分组(在 数据与接入 与 审计与配置 之间)
- icon=CircleCheck
- 子项: 仅含「投诉采集质量」(从「数据与接入」移过来, 后续可扩退款对账质量等)
- 「数据与接入」组保留: 数据采集/接入中心/缓存管理(语义=与外部接入相关)
测试: menu.spec.js 新增 2 条断言(投诉采集质量归位 + AI 与分析分组存在),
系统管理子分组数 3→4, 12 测试全过; vite build 8.90s PASS, lint 0 errors。
2026-09-17 17:15:43 +08:00
3f553d4fae
feat(ai): feed 数据维度扩充 4 方向 (A/B/C/D) ( #139 )
...
gitleaks / gitleaks (push) Successful in 4s
backend-test / backend-lint-test (push) Successful in 21m37s
backend-test / frontend-lint-test (push) Successful in 40s
#139 批次 C3 推进 ai_feed 数据维度扩充, 让 AI 在不同时间窗口下都有更
完整数据可分析, 减少幻觉。
A. 周报补 hourly_summary: _enrich_with_daily_trend 取消 #136 月报限定
(if span > 14), 周/月报均对每条 daily_trend 调 _enrich_daily_hourly_summary,
周报 7×6=~42 标量在 feed 6000 字符内可接受。
B. daily_trend 加投诉处理/积压: _days_range_trend 每条除 complaint(总数)外,
新增 complaint_handled / complaint_pending 两字段, 复用 complaint:enhanced:{d}:{d}
缓存(与 _build_brief 顶层 complaint 同源), 取 _complaint_enhanced.total.
{handled,backlog}, 口径同 complaints._is_real_finish。
C. 日报补投诉三源分布: _enrich_with_today_kanban 末尾投影 feed.complaint.
by_source 到顶层 complaint_breakdown, 加入 _CONSTANT_FEED_KEYS 让所有 scope
切片都保留(避免被 _select_feed_sections 切走, 让 AI 答"今日退款涉诉率"
时也能看到三源拆分)。
D. 周/月报补人均效率 top5: _enrich_with_daily_trend 新增 staff_top5 调用,
复用 today:staff:None:{s}:{e}:200:0:person:None:None:None 缓存(与今日看板
同源同预热), _compact_staff_efficiency 取 top3, AI 可答"本周/本月谁接待最多"。
测试: test_ai_feed_compact 更新 4 个 #136 测试以适配 #139 新行为
(span_gt_14/span_le_14/cap_31/future_days_skipped 的 cached_call 调用次数翻倍),
新增周报路径 hourly_summary + staff_top5 断言。224 个 ai 相关测试全过。
ruff 自动 fix 3 处 import 排序(llm_providers/ai_time_parser/test_ai_assistant)。
2026-09-17 16:52:35 +08:00
3ad9b9c19b
feat(ai): AI 管理页面 5 Tab 整合入口 ( #137 )
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
- 新建 web/src/views/system/AiAdmin.vue: 5 Tab 整合(报告列表/模板/模型/提示词/事务通知)
- Tab1 内联报告列表(复用 /admin/ai/reports API, 分页+筛选+删除)
- Tab2-5 lazy 嵌入存量组件(AiTemplates/LlmProviders/AiFocusConfigs/SystemEvents)
- 改 router/index.js 加 system/ai-admin 路由, 旧路由保留可达(书签不破坏)
- 设计说明三层落地: 文件头注释块 + ⓘ 气泡 + review-checklist
2026-09-17 16:35:22 +08:00
10a73d6e0e
feat(ai): 月报逐天小时粒度分析 ( #136 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
#136 (批次 C1) 月报路径补小时粒度数据, 让 AI 能回答「哪天哪个时段是高峰 / 夜间 AI 接管多少」:
- _enrich_with_daily_trend: 月报路径(span > 14)取消「前14天+周聚合」降级, 按日全展开(cap 31)
- 新增 _enrich_daily_hourly_summary: 月报路径对每条 daily_trend 调 today:hourly 取当日小时数据
- 新增 _compact_daily_hourly_summary: 压缩为 6 标量/天(peak_hour/peak_reception/low_hour/low_reception/night_reception/refund_peak_hour)
谷值只取工作时段(08-23)内避免凌晨 0 被当谷值
- 周报路径(span ≤ 14)沿用现行 daily_trend, 不补 hourly_summary(max_tokens=4000 较紧)
- 测试: 新增 13 个测试覆盖压缩函数 + 月报路径降级取消 + cap 31 + future guard + 失败隔离
feed 输入约 12KB/月报(30天×12标量), 122b 模型 max_tokens=5000 可容纳。
2026-09-17 16:18:36 +08:00
f18f9929d7
feat(ai): 摘取层 split_sections/extract_section + 27b 语义匹配 + 三级命中链路 ( #135 )
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Failing after 2m38s
backend-test / frontend-lint-test (push) Successful in 46s
实现架构文档 A-20260917-001 v1.0:
- ai_extract_service.py: split_sections 按 H2 锚点切分 + extract_section
按 keywords_to_scopes 路由选段 → 27b 按问句摘取+排版
- ai_semantic_matcher.py: semantic_match 27b 判定问句与候选报告整体相关性,
confidence≥0.6 命中(可配置 AI_SEMANTIC_MATCH_THRESHOLD)
- ai_report_matcher.list_candidates: 供 L2 语义匹配用, 复用 match_template_report
SQL 与排序但不调 _bump_matched_count
- ai.py L272-349: 三级命中链路
L1 关键词(match_template_report 不变) → 命中走摘取层
L2 27b 语义匹配 → 命中走摘取层
L3 clarify 兜底(返回 H2 标题列表让用户选段重发)
无候选 → 走原 #116 实时 LLM 兜底(保留兼容)
AiAnalyzeRequest 新增 force_llm 字段: clarify 状态下"直接生成"按钮跳过命中链路
- AssistantDrawer.vue: clarify 状态渲染(message + 段落按钮 + "直接生成"按钮),
askClarify/askForceLLM/buildPayload opts.forceLLM
测试: 19 新测试全过(split_sections/extract_section/semantic_match 各路径);
ai 全回归 79 测试全过(含原 52 + 新 19 + window 8); ruff/eslint 全过
2026-09-17 15:45:53 +08:00
10832043f9
arch(arch/A-20260917-001): 锁定 AI报告草稿摘取与三级命中架构文档 #135
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
issue-workflow §6 ②架构设计阶段门禁:
- 新增 docs/arch/A-20260917-001-ai报告草稿摘取与三级命中.md (LOCKED v1.0)
- 打 git tag arch/A-20260917-001
- 登记 docs/README.md §六「文档性质索引」REQ 区
设计内容(REQ 性质, 约束一个集成):
- 三级命中链路: L1 关键词(现行)→L2 27b 语义匹配→L3 clarify 兜底
- 摘取层 ai_extract_service.py: split_sections 按 H2 切分 + extract_section 27b 摘取+排版
- 语义匹配 ai_semantic_matcher.py: semantic_match 整体相关性判定
- ai.py L272-299 改造: 命中不再返回全文走摘取层
- AssistantDrawer.vue: 新增 status=clarify 渲染按钮列表
- 取舍: 不用 122b 摘取(慢)、不做问句归一化(关键词密度足够)、保留实时 LLM 兜底
#135 架构设计阶段产出, 待用户人工审核通过后进入③编码阶段
2026-09-17 15:22:59 +08:00
11cdae5db2
feat(ai): 草稿结构化 H2 锚点 + 三模板 max_tokens 放宽 ( #134 )
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
- _SYSTEM_PROMPT 第3条加 H2 锚点约束(## 接待分析 / ## 退款分析 /
## 投诉分析 / ## 趋势对比 / ## 结论建议), 周报/月报必须结构化输出;
日报保持简洁不强制 H2; 前台实时仍 500 字约束, 后台预生成可详尽展开
- 顺手清理 ai_feed.py 中未使用的 keyword_to_scope import (ruff 报)
- Alembic 迁移 g3h4i5j6k7l8: daily 2000→2500, weekly 2500→4000,
monthly 3000→5000 (用户原话"完整而详细的分析作为草稿", 不能被截断)
- 新增 3 单测验证 _SYSTEM_PROMPT 含 H2 锚点 + 500 字约束保留 +
预生成许可措辞 (防回流)
工单 #134 批次 B1, 为 #135 摘取层提供段落锚点前提
2026-09-17 15:12:30 +08:00
777485a207
feat(ai): trigger_at 调凌晨 + warm_idle 凌晨可触发 ( #133 )
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
- weekly_compare_last / monthly_overview trigger_at 从 1-09:00 调到 1-02:00
(凌晨2点执行, 避开0点数据同步高峰, 让早8点查询全部命中预生成草稿)
- daily_general_morning 保持 09:00 (走 refresh_on_sync 白天实时刷新)
- ai_report_worker.warm_idle 移除 _DAYTIME_HOURS 时段限制, 让凌晨 trigger_at
能被扫描到; warm_on_startup 仍保留白天限制(避免服务凌晨重启时大量并发)
- 新增 5 单测验证凌晨调度匹配 + 源码不含时段守卫(防回流)
- alembic 迁移 f8g9h0i1j2k3 (down_revision e2f3a4b5c6d7)
工单 #133 批次 A2
2026-09-17 15:07:06 +08:00
f48afe2978
feat(ai): 周报/月报预生成窗口截止昨天整天, 不含当天不完整数据 ( #132 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 2m51s
backend-test / frontend-lint-test (push) Successful in 50s
- 新增 ai_report_window.compute_pregen_window(period, ref_date) 纯计算函数
* weekly: 本周一~昨天; 今日周一退化取上周一~上周日整周
* monthly: 本月1日~昨天; 今日1号退化取上月1日~上月末整月
* daily: 返回 None(保持 date=None 实时数据, refresh_on_sync 仍生效)
- 新增 compute_compare_window(start, end) 等长前对比窗口(先实现不接入, 留批次 B)
- generate_for_template 改造: weekly/monthly 走 start/end 模式让 _window 返回
整天 T23:59:59(end<today 走非实时分支), daily 保持现行 date=None
- 落库 save_report 落 start/end 让 ai_reports.start/end 反映昨日截止
- 11 个单元测试覆盖正常/退化/daily/未知 period/snapshot_date 今日/等长对比窗口
- 138 个 ai 回归测试通过
2026-09-17 14:54:30 +08:00
8f9df1462a
feat(llm): provider_code 指定调用 + pregen_only 隔离, 为 122b 专供预生成铺路 ( #131 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 3m1s
backend-test / frontend-lint-test (push) Successful in 1m26s
- llm_providers 新增 pregen_only(默认 false): true 不进实时默认链、不参与启动 warmup
- 迁移 e2f3a4b5c6d7: 加列 + 插入 talon-gmk-qwen3.5-122b(初始 enabled=false,
pregen_only=true, timeout 600, retry_max=0), 拉取验证后再启用, 避免缺失模型 404 硬失败
- get_active_providers 排除 pregen_only(实时问答/时间解析);
新增 get_provider_chain(db, provider_code): 指定 provider 置顶 + 常规链 transient 降级兜底,
指定方不可用(停用/不健康/不存在)自动退回常规链
- chat_completion 新增 provider_code 参数; ai_report_worker 预生成透传
template.llm_provider_code, timeout 180→600s
- llm_warmup 排除 pregen_only(避免 81GB 冷加载挤掉常驻实时模型)
- provider schema/CRUD 支持 pregen_only 字段
2026-09-17 12:59:50 +08:00
5496b6d3ed
feat(ai): 模板级「随数据同步刷新」开关, 日报自动识别/可手动选择 ( #130 )
...
gitleaks / gitleaks (push) Successful in 4s
backend-test / backend-lint-test (push) Failing after 6m57s
backend-test / frontend-lint-test (push) Successful in 1m55s
- ai_report_templates 新增 refresh_on_sync BOOLEAN NOT NULL DEFAULT FALSE
True=每次高频同步成功后刷新(日报类); False=仅按 trigger_at 定时(周报/月报每日一次)
- alembic d1e2f3a4b5c6 加列 + 回填(daily/sync-triggered/period=daily→true)
- warm_on_sync_complete 候选从硬编码 frequency in(sync-triggered,daily)
改为 refresh_on_sync=TRUE; 20min 节流统一作用于随同步刷新模板
- admin_ai: 新建缺省按 period/frequency 自动识别(_auto_refresh_on_sync),
PATCH 仅显式传值才更新(不覆盖手动选择); _tmpl_dict 返回该字段
- AiTemplates.vue: 表单加开关+口径说明, 新建切周期/频率联动自动识别,
列表用 tag 显示「随同步刷新/每日定时」
2026-09-16 22:15:30 +08:00
3483604fa9
feat(ai): 日报跟随高频数据同步刷新, 20min 节流; 周报/月报维持一天一次 ( #129 )
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
- warm_on_sync_complete 模板范围: sync-triggered → sync-triggered + daily;
weekly/monthly 不入选, 仍由 warm_idle 按 trigger_at 一天一次
- daily 刷新前查当日最近公共报告 created_at, 20min 内则节流跳过
(同步周期 30min, 正常每轮触发; 挡手动频繁同步/推送重叠; sync-triggered 不节流)
- sync_task_manager._run_background finally(success/partial) 新增
warm_on_sync_complete 后台线程触发, 与 warm_after_sync 同模式, 不阻塞同步
- 落库维持每次新增留痕(matcher 取 created_at 最新一条命中)
- 循环前取模板标量快照, 避免 commit(expire_on_commit) 后属性 lazy reload 隐患
2026-09-16 21:48:16 +08:00
0f4d7b1917
feat(ai): #127 每 provider 独立并发队列(补做降级项)
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 2m27s
backend-test / frontend-lint-test (push) Successful in 39s
将全局单门闸改为按 provider_code 分组的 _gates 字典:
- 同一 provider 内仍按 (level, seq) 排队 + 高优先级抢占(互斥锁有效)
- 不同 provider 间并行不互斥(实现多 provider 分流)
- acquire 增 provider_code 参数; 预生成用 template.llm_provider_code,
用户实时路径 None 走 default 门闸
- queue_status 汇总所有门闸, 新增 providers 明细(每 provider 在飞/排队)
实测: 两不同 provider 并行 0.31s≈单个耗时, 同 provider 串行 0.61s≈两倍。
2026-09-16 17:51:06 +08:00
5cb72c1817
fix(test): 修复预存坏测试 test_llm_client_missing_base_url ( #127 收尾)
...
_endpoint(base_url) 需显式传参, 原测试无参调用必 TypeError。
存量不追溯但顺手修复(1 行)。
2026-09-16 17:51:06 +08:00
f3ec3ff53e
fix(ai): 命中预生成报告时 DetachedInstanceError 500 ( #128 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 15m0s
backend-test / frontend-lint-test (push) Successful in 51s
#122 预存 bug(被 #127 部署后产生的预生成报告首次命中触发):
match_template_report 返回绑定 session 的 AiReportTemplate, _wrap_row 内
_bump_matched_count 调 db.commit() → expire_on_commit 把 tmpl 属性标 expired;
ai.py 在 _match_db.close() 后访问 _tmpl.code → 无法刷新 → 500。
修复: 返回前 db.expunge(tmpl) 并显式读 .code 确保已加载, 调用方可脱离
session 安全读取。
2026-09-16 17:31:58 +08:00
723931c052
feat(ai): 真优先级队列 L0 抢占 L1/L2 + queue_length 真实化 ( #127 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 2m40s
backend-test / frontend-lint-test (push) Successful in 42s
第三阶段: 将 #123 简化调度器(仅活跃计数)升级为真优先队列与抢占。
- llm_scheduler: 进程级线程安全优先门闸(_Gate), (level, FIFO) 出队;
高优先级任务经 holder.loop.call_soon_threadsafe(task.cancel) 抢占在飞低优先级。
优先级遵循 #127 工单定义: L0 后台预生成 > L1 admin 触发 > L2 用户实时。
被抢占不恢复现场(以防过度工程), 由上层捕获 CancelledError 置"可重试"。
queue_status 暴露 running_level/run_s/queued, queue_length 真实化(不再恒 0)。
- ai.py::_runner 用户实时挂 L2; 被抢占时 ai_task_manager 捕 CancelledError 置可重试。
- ai_report_worker.generate_for_template 增 level 参数: 后台(level=0)/admin(level=1),
经 _pregen_llm + asyncio.run 走门闸; 被抢占跳过不写失败事件。
- admin_ai trigger 传 level=1。
- 多 provider 分流沿用 #114 _call_multi_provider 遍历兜底(单并行队列; 独立并发队列降级)。
2026-09-16 16:06:56 +08:00
eecc003bd9
收口第二阶段降级项 ruff/pytest+前端忙时提示+trigger_at格式 ( #126 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 2m46s
backend-test / frontend-lint-test (push) Successful in 58s
P0-1 ruff/pytest: 容器装 dev 依赖(pyproject.toml 已定义), ruff check 通过, pytest 438 passed
P0-2 前端忙时提示: aiAssistant.js 加 aiQueueStatus(); AssistantDrawer.vue 加忙时提示条+抽屉打开/submit前 re-fetch
P0-3 trigger_at 格式: weekly/monthly 从 "1:09:00" 改为 "1-09:00"(用 `-` 分隔 wday/day 与 HH:MM); 新 migration f4a5b6c7d8e9; _parse_wday_hhmm 兼容新旧格式; AiTemplates.vue placeholder 同步
实测:
- /ai/queue-status 路由 code=0 is_busy=false
- schedule_due_templates 临时模板 trigger_at="3-15:33" 命中当前时刻
- _parse_wday_hhmm 新旧格式都解析正确
2026-09-16 15:34:19 +08:00
01c4161a51
feat(ai): 报告与模板管理界面 admin ( #125 )
...
gitleaks / gitleaks (push) Successful in 9s
backend-test / backend-lint-test (push) Failing after 31m9s
backend-test / frontend-lint-test (push) Successful in 43s
#125 实现 AI 报告与模板的 admin 管理界面:
## 1. 新建 backend/app/api/v1/admin_ai.py
路由(prefix=/admin/ai, 全部 admin 权限):
- GET /templates: 模板列表(按 priority 升序)
- POST /templates: 新增(校验 code 唯一)
- PATCH /templates/{id}: 编辑(部分字段, code 不允许改)
- DELETE /templates/{id}: 删除(关联报告保留, template_id SET NULL)
- POST /templates/{id}/trigger: 手动触发预生成(同步等待 LLM 完成)
- GET /reports: 报告列表(分页, is_public/template_id 筛选)
- DELETE /reports/{id}: 删除报告
## 2. router.py 接入 admin_ai.router
## 3. 新建 web/src/api/adminAi.js
6 个 API 函数对应后端路由
## 4. 新建 web/src/views/system/AiTemplates.vue
admin 模板管理页面:
- el-table 列表: priority/code/name_cn/prompt/keywords/周期频率/max_tokens/启用
- 关键词 el-tag 展示
- 新建/编辑对话框: code/name_cn/question_template/keywords/period/frequency/
trigger_at/max_tokens/llm_provider_code/priority/visibility_scope
- 触发按钮(loading 状态) + 删除 popconfirm
- el-alert 说明模板同源设计
## 5. router/index.js 加 SystemAiTemplates 路由
- path: system/ai-templates
- meta: { title: 'AI 报告模板', icon: 'Document', admin: true }
本地校验:
- ruff All checks passed
- router 7 个路由全部注册
- 路由表 SystemAiTemplates 已加
2026-09-16 13:22:39 +08:00
58904d5daf
feat(ai): seed 内置默认模板 日报/周报/月报 ( #124 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
#124 在 ai_report_templates 表 seed 3 条内置默认模板, 让用户开箱可用:
1. daily_general_morning 每日晨会·整体经营
- keywords: [今天, 今日, 整体, 总览, 晨会, 日报]
- frequency: daily, trigger_at: 09:00
- max_tokens: 2000
2. weekly_compare_last 周报·本周与上周对比
- keywords: [本周, 这周, 周报, 对比, 上周, 环比]
- frequency: weekly, trigger_at: 1:09:00(周一9点)
- max_tokens: 2500
3. monthly_overview 月报·本月总览
- keywords: [本月, 这个月, 月报, 上月, 同比]
- frequency: monthly, trigger_at: 1:09:00(每月1号9点)
- max_tokens: 3000
设计:
- INSERT ... ON CONFLICT (code) DO NOTHING(幂等, 重复 upgrade 不报错)
- is_active=true, visibility_scope=public(全员可看预生成报告)
- keywords 用于 #122 匹配层关键词路由
- max_tokens 后台预生成参数(后台 timeout_s=180)
本地校验:
- alembic upgrade head 成功(e3f4a5b6c7d8 单一 head)
- SELECT count=3, 字段对齐
- keywords JSONB 数组正确(['今天', '今日', ...])
2026-09-16 13:19:39 +08:00
6d1940b497
feat(ai): 调度器接入 + queue-status 路由 + 注册/注销活跃计数 ( #123 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
#123 实现 LLM 调度仲裁层, 把 ai_report_worker 接入既有调度系统:
## 1. 新建 app/services/llm_scheduler.py
简化版调度队列(不实现真抢占, 用活跃计数 + 预计等待):
- register()/deregister(): 进入/退出 LLM 调用时调(用户实时 + 后台预生成都调)
- queue_status(): 返回 {active_count, queue_length, est_wait_s, is_busy, timestamp}
- is_busy 阈值: active_count >= 2 或 est_wait_s > 30
- est_wait_s = active_count × 35s(基于 #115/#117 实测平均)
未来扩展(超出 #123 ):
- 真 L0 抢占 L1/L2(需 asyncio.Task.cancel + 状态保存)
- 多 provider 并发分流(每 provider 独立队列)
## 2. ai.py::_runner + ai_report_worker.generate_for_template 注册
- 调 chat_completion 前 register(), finally deregister()
- 用户实时 + 后台预生成共享计数, /ai/queue-status 可见
## 3. 新增 GET /ai/queue-status 路由
- 返回 queue_status() 供前端展示忙时提示
- 前端 AssistantDrawer 可在用户请求前调此接口
## 4. 接入既有调度系统
- complaint_sync.py: 同步完成 finally 调 warm_on_sync_complete
(在 cache_warmer.warm_after_sync 之后, 共用单飞锁)
- cache_warmer.start_warm_loop: 启动延迟 8s 调 ai_warm_startup
+ 每分钟调 ai_warm_idle(扫描 daily/weekly/monthly 模板)
本地测试:
- queue_status() 计数器准确(init 0 → 2 register → 1 deregister)
- 阈值生效(active_count=2 → is_busy=True)
- 所有 imports OK
- ruff All checks passed (4 个 I001 import 排序自动修复)
2026-09-16 13:18:57 +08:00
dfc40e6e7a
feat(ai): 匹配层 match_template_report + ai_analyze 优先匹配 ( #122 )
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
#122 实现 AI 报告缓存与调度机制的匹配层, 让用户请求优先命中预生成报告:
## 新增 app/services/ai_report_matcher.py
核心函数 match_template_report(db, question, win_period, win_date):
- 取当日 is_public=True 报告, 按 (snapshot_period=win_period) DESC + created_at DESC 排序
- 在候选中找问句命中模板 question_keywords 的优先返回(强匹配: 窗口完全 + 语义)
- 命中时调 _bump_matched_count +1 (UPDATE ... RETURNING, 并发安全)
- 未命中返回 (None, None), 调用方走 LLM 兜底
辅助函数:
- _question_matches_keywords(question, keywords): 问句是否命中模板关键词(任一即命中)
- _wrap_row(db, row): 把 SQL row 包装为 AiReport ORM 实例 + 命中后 matched_count +1
- _get_template(db, template_id): 查询模板(命中匹配时返回)
- _bump_matched_count(db, report_id): matched_count +1 (失败仅 log, 不影响主流程)
## ai.py::ai_analyze 在 LLM 调用前先匹配
- 规则层秒回 → 匹配层(命中预生成公共报告) → LLM 实时生成
- 命中直接返回 success + result_md + report_id + matched_template 字段, 不调 LLM
- 未命中走原 #116 实时 LLM 路径(零侵入)
## ai_report_service._visibility_clause 改进
- 普通用户: (user_id = :uid OR is_public = TRUE) - 公共报告所有人可见
- admin: 1=1 (不变)
- 让 #116 的 list_reports / get_report 能看到匹配命中的公共报告
## ai_report_worker.generate_for_template 修复
- user_id=0 → user_id=1 (admin user_id=0 不存在, FK 违反)
- 落库公共报告归属 admin user_id=1, 通过 is_public=True 让所有人可见
本地测试(端到端):
- seed 1 daily 模板 + 1 public 报告
- "今天怎么样" 命中 (keywords 含"今天") + matched_count=1 ✅
- "上月对比" 不命中 (走 LLM 兜底) ✅
- "本周情况" 不命中 (当日只有 daily 模板) ✅
- 普通用户 list_reports total=1 (包含公共报告) ✅
- 普通用户 get_report(public id) 可见 ✅
ruff All checks passed
2026-09-16 13:16:13 +08:00
3e15e98d16
feat(ai): ai_reports 扩字段 + 预生成 worker ( #121 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 2m55s
backend-test / frontend-lint-test (push) Successful in 51s
#121 第二阶段基础工单, 包含两部分:
## 1. ai_reports 表扩字段(Alembic migration d2e3f4a5b6c7)
新增 5 字段 + 2 索引:
- template_id FK ai_report_templates ON DELETE SET NULL(模板被删时报告保留)
- snapshot_date: 报告数据时点(区别于 created_at 入库时点)
- snapshot_period: daily/weekly/monthly/range(与模板 period 对应)
- is_public default=false: true=公共报告(预生成, 所有人可看); false=私有(实时, 仅本人)
- matched_count default=0: 被命中复用次数(match_template_report 命中时 +1)
索引:
- ix_ai_reports_template_snapshot (template_id, snapshot_date) - 匹配查询用
- ix_ai_reports_public_created (is_public, created_at) - 公共报告列表用
## 2. ai_report_worker.py 预生成 worker
核心函数:
- schedule_due_templates(db): 扫描 ai_report_templates, 找当前时刻匹配的 daily/weekly/monthly 模板
(trigger_at=HH:MM / wday:HH:MM / day:HH:MM, ±1 分钟容差)
- generate_for_template(db, template): 用模板 prompt 调 LLM(timeout_s=180, max_tokens=template.max_tokens)
落库 is_public=True + template_id + snapshot_date; 失败写 system_event(category=ai)
- warm_on_sync_complete: 同步完成后触发 sync-triggered 模板(待 #123 接入 sync_task_manager)
- warm_on_startup(force=False): 启动延迟触发 active 模板(待 #123 接入 start_warm_loop)
- warm_idle: 空闲期每分钟扫描(待 #123 接入 bms_scheduler)
设计要点:
- 进程级单飞锁 _worker_lock 防并发(单 worker 串行)
- 失败仅 log + system_event, 不阻塞主流程
- 白天 8-23 才预热(夜间无人访问省计算)
- chat_completion 是 async, worker 用 asyncio.run/iscoroutine 桥接
## 3. save_report 扩参数(向后兼容)
ai_report_service.save_report 增加 template_id/snapshot_date/snapshot_period/is_public
可选参数(默认 None/None/None/False), 实时调用零侵入。
本地校验:
- ruff All checks passed
- alembic upgrade head 成功(d2e3f4a5b6c7)
- 空表 schedule_due_templates/warm_idle 测试通过
#123 将在 llm_scheduler.py 接入 sync_task_manager + bms_scheduler + 优先级队列。
2026-09-16 13:12:14 +08:00
d70b118987
feat(ai): ai_report_templates 表 + ORM + migration ( #119 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 2m51s
backend-test / frontend-lint-test (push) Successful in 44s
新建 ai_report_templates 表为 AI 报告缓存与调度机制(整体方案见对话)提供模板配置基础:
- code/name_cn/question_template/question_keywords: 模板编码+显示名+prompt+关键词
- period/frequency/trigger_at/sync_offset_min/priority: 调度时点+频率+优先级
- llm_provider_code/fallback_template_id/max_tokens: 多模型分工+失败降级+token 上限
- is_active/visibility_scope/created_by: 启停+可见性+创建者
设计要点:
- prompt 同源: question_template 是模板属性, 前台实时与后台预生成共用同一份
- question_keywords JSONB 数组用于匹配用户问句(类似 ai_glossary 关键词路由)
- 自引用 fallback_template_id ON DELETE SET NULL: 简版被删时降级字段置 NULL
- 关联 users ON DELETE SET NULL: 创建者被删时模板保留
- 索引 (is_active, frequency) 调度器扫描用, code unique 唯一标识
本地 alembic upgrade head 成功, 表结构 18 列 + 2 索引 + 2 FK 校验通过。
不 seed(留待 #124 内置默认模板工单)。
2026-09-16 12:41:02 +08:00
41270c4cc6
feat(ai): chat_completion 前台显式传 timeout_s=60 + 新增 2 个 timeout 测试 ( #120 )
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
#108 已为 chat_completion 加 timeout_s 参数(默认 None 走 provider 自带 timeout),
但 ai.py::_runner 调用时未显式传值, 走 provider 的 timeout_s 不够清晰。
本次:
- ai.py::_runner 显式传 timeout_s=60.0 (前台实时路径, 用户等)
- 新增 test_chat_completion_timeout_s_override_short: timeout_s=0.05 触发超时
断言 LLMError.retryable=True (transient)
- 新增 test_chat_completion_timeout_s_param_accepted: timeout_s=180 (后台场景)
用 MockTransport 验证大 timeout 不影响正常完成
后台预生成调用 (timeout_s=180) 将在 #121 工单中实现。
2026-09-16 12:39:25 +08:00
1852a44019
fix(ai): AI_MAX_TOKENS 500→800 解决输出截断 ( #117 )
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Failing after 2m58s
backend-test / frontend-lint-test (push) Successful in 39s
#114 把 AI_MAX_TOKENS 从 800→500 优化响应时间, 副作用是 LLM 想说长一点时
被 500 上限强制截断。生产 ai_reports 表 id=1/3 行 completion_tokens=500
达顶, 末尾不自然, 截断确认。
实测改为 800 后:
- completion_tokens=598 (>500 原上限, 证明修复生效)
- result_md_len=914 (vs 原 700-775, 更完整)
- 结尾自然收束, 含 3 条完整下钻建议
- total_ms=46.7s (< 60s LLM client timeout 上限, 不破坏)
生产 .env 同步由 deploy 升级脚本处理, 此处只改默认值与注释留追溯。
2026-09-16 12:14:58 +08:00
7eff00a00d
fix(ai_report_service): quote date/start/end 列名避免 PostgreSQL 保留字冲突 ( #116 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 2m37s
backend-test / frontend-lint-test (push) Successful in 35s
list_reports / get_report 手写 SELECT 时未引用 "" 导致 end 触发 syntax error;
date/start 虽未报错但同属保留字, 一并 quote 保险。bind params 不受影响。
2026-09-16 11:27:52 +08:00
a9864f10ff
feat(ai): 报告持久化 + 个人归属 + 我的待办历史 + token/用时显示 ( #116 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 3m9s
backend-test / frontend-lint-test (push) Successful in 1m14s
后端:
- 新增 ai_reports 表(user_id 隔离 + admin 全可见, AT TIME ZONE 'Asia/Shanghai' 时区换算)
- LLMResult 类继承 str 实现零侵入兼容旧调用, 同时携带 usage 元数据
- ai_report_service.py 提供 save/list/get/summary 四函数, 全部用 text() + bind params
- ai_task_manager AiTask 加 report_id/usage/total_ms/llm_ms/build_ms/llm_model 字段
- POST /ai/analyze 响应增 report_id/usage/total_ms/llm_ms/build_ms/llm_model
- 规则层短路也落库(llm_model=rule-engine, tokens=None, 耗时=0)
- 新增 GET /ai/reports /reports/summary /reports/{id} 三路由
- 详情用 404 而非 403 不暴露他人报告存在性(隐私)
前端:
- AI 助手页报告头部显示 token + 用时 + 模型(灰底气泡)
- 我的待办页加「AI 报告历史」tab: KPI / 时间+period 筛选 / 列表 / 详情抽屉
- 详情抽屉支持 Markdown 渲染(DOMPurify 消毒), 移动端响应式尺寸
- API 客户端加 aiReports / aiReportsSummary / aiReportDetail 三函数
2026-09-16 11:09:43 +08:00
8538f56857
perf(ai): glossary 未命中兜底改极简全域卡降泛问 prompt 体积 ( #115 )
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Failing after 22m20s
backend-test / frontend-lint-test (push) Successful in 44s
泛问(未命中 GLOSSARY_ROUTE 任一域)时原回退全量 8 卡(1287 字符),
而此时 feed 全量且 feed._spec 已覆盖 reg/handled、涉诉率、金额单位、
lead 双体系。改注入 _FALLBACK_MINIMAL 四条极简判据(327 字符, -75%):
三源判据/四路构成/接待指标/yoy 对比期语义, 与 _spec 互补不重复、
不新增口径、语义不漂移。命中关键词路径一行不动(0 回归)。
- 新增 ai_glossary._FALLBACK_MINIMAL
- glossary_for_question 未命中分支提前返回极简卡(第4条已含对比期语义)
- 更新 test_glossary_fallback_all→minimal, 新增命中路径不变测试
2026-09-15 19:23:02 +08:00
838c847d13
perf(ai): 去重 focus + 收紧长度约束 500 字 ( #114 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 9m43s
backend-test / frontend-lint-test (push) Successful in 43s
实测方差大 (34.6/36.1/39.5s), 单降 feed+max_tokens 不足以稳定进 35s。
继续优化:
- ai_focus_service: 跳过 3 个 _SHADOWED_BY_SYSTEM_RULE seed
(inference-discipline/business-benchmark/work-time-ai-assist)
→ 内容已由 _SYSTEM_PROMPT 铁律 8/9/10 强制注入, focus 段重复 ~800 字符
→ 省 ~320 tokens ≈ 1.3s prefill; DB 行保留让 admin 可见可编辑
- _SYSTEM_PROMPT rule 3: 长度约束 600→500 字
→ 收紧自然完成点, 减 eval ~50 tokens ≈ 1.6s
- 预期 total_ms 37s → 34s, 进 35s 目标
注意: 若 admin 修改了这 3 个 seed 内容, 须同步修改 _SYSTEM_PROMPT 对应铁律,
否则差异不会传到 AI (代码注释已说明)
2026-09-15 17:39:16 +08:00
77ae79edd5
perf(ai): 加回答长度约束 + 降 feed 上限到 3500 ( #114 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
实测发现 max_tokens=500 时模型用满 500 tokens 仍被截断(798 chars
未写完), 且总延迟 36.1s 仅差 1.4s 过线。单纯降 max_tokens 不省时
(模型仍用满 cap), 改用 prompt 约束让自然完成:
- _SYSTEM_PROMPT rule 3 加 "回答总长 600 字以内" 约束
→ 模型自然完成在 ~400 tokens (600 字) 不触发 max_tokens 截断
→ eval 省 3s (500→400 tokens @ 31 tok/s)
- AI_ANALYZE_MAX_CHARS 4000→3500 (default)
→ prefill 省 0.8s (514 字符 ≈ 206 tokens @ 254 tok/s)
- 预期 total_ms 36.1→32s, 进 30s timeout 不降级
2026-09-15 17:32:33 +08:00
beb32b123a
perf(ai): 压 feed + 降 max_tokens 优化 qwen3.8:27b 响应延迟 ( #114 )
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
- AI_ANALYZE_MAX_CHARS 6000→4000 (省 prompt ~2000 字符≈prefill -4.7s)
- AI_MAX_TOKENS 800→500 (省输出 300 token≈eval -9.7s)
- _compact_* topN 5→3 (5 处: reception/refund/refund_recovery×2/staff_efficiency)
- 预期总延迟 52s→~30s, 进 30s timeout 不降级
实测瓶颈(生产 qwen3.8:27b):
- prefill 19.6s @ 4980 tok (APU UMA 带宽天花板 254 tok/s)
- eval 25.8s @ 800 tok (27B dense 每生成 1 token 读全 17GB 权重)
- qwen3:30b MoE 速度快 67% 但 ollama 0.33.2 thinking 关不掉, 已放弃
2026-09-15 17:14:50 +08:00
e2a333817b
fix(llm): thinking=disabled 追加 think:False 顶层字段修 ollama+Qwen3 长 prompt 烧 token (#111/#113)
...
工单 #111 修复 chat_template_kwargs 在长 prompt 下被 talon-api openai_compat 丢弃
(只透传 model/messages/temperature/max_tokens/top_p/tools/tool_choice, 未声明 think 字段).
工单 #113 在 talon-api openai_compat.py 加 think: Optional[bool] = None + 透传到
ollama /api/chat 原生字段.
本 commit 是 csctalon 端补丁: _payload thinking=disabled 时追加
body["think"] = False (ollama /api/chat 原生字段, 由 talon-api 透传).
实测生产链路(修复后):
- 短 prompt(34 token): 13s 返回 298 字符
- 业务 prompt(8393 字符 / 4974 prompt_tokens): 44s 返回 1118 字符业务报告
(含窗口说明/接待退款分析/利好利空标注), 不再降级到 qwen3-coder:30b
llm_providers.timeout_s 从 30 调到 60 (Q4 27B dense 8400 字符业务 prompt 实测 44s,
30s 误触发降级).
测试:
- test_payload_thinking_disabled_adds_chat_template_kwargs (chat_template_kwargs + think:False 双校验)
- test_payload_thinking_low_no_chat_template_kwargs (low 不加 think)
2026-09-15 15:56:05 +08:00
b3b7dfda48
fix(llm): thinking=disabled 追加 chat_template_kwargs 修 ollama+Qwen3 烧 token ( #111 )
...
工单 #111 修复: 工单 #110 注册 qwen3.8:27b 后实测, 业务 prompt 30s 超时降级到
qwen3-coder:30b, 直接 curl ollama 发现 content 字段为空, max_tokens 全被烧进
thinking 字段。
根因: _payload() 在 thinking=disabled 时只传 DeepSeek 专用的
`body["thinking"]={"type":"disabled"}`, 但 ollama OpenAI 兼容端点不识别该字段,
Qwen3 系列默认 thinking=on 全程烧 token 致 content 空。
修复: thinking=disabled 时同时传
`body["chat_template_kwargs"]={"enable_thinking": False}` — DeepSeek/OpenAI 官方
端点忽略未知字段(无害), ollama 端点正确禁用 thinking。low/high 不动保持 reasoning_effort。
测试:
- test_payload_thinking_disabled_adds_chat_template_kwargs
- test_payload_thinking_low_no_chat_template_kwargs
生产实测(修复后): qwen3.8:27b 业务 prompt (8385 字符) 17s 返回 1278 字符业务报告,
content 正常, 不再降级。
2026-09-15 15:02:55 +08:00
5dce558af1
feat(scripts): verdict_vs_ingest.py 判据可信度验证脚本入库 ( #109 )
...
工单 #109 SKIP_EXPORT_IF_UNCHANGED 开关开启前的判据可信度验证。
脚本每轮跑真实同步(照常导出+入库), 同时对照 digest 变化 vs
inserted/updated 写入量, 判读 漏报/虚警/一致/都未变, 既完成正常
业务采集又积累判据证据。修正两脚本注释里的路径(/app/uploads →
/app/scripts, Dockerfile 是 COPY . /app) 与工单号(#79 是 D路漏
sheet bug, 已 closed, 与判据开关无关)。
2026-09-15 14:43:49 +08:00
be385891e9
#108 LLM 多 Provider 框架(优先级降级 + 健康探测 + 调用日志)
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Failing after 3m1s
backend-test / frontend-lint-test (push) Successful in 58s
后端:
- 新增 LlmProvider/LlmCallLog 模型 + alembic migration(含 3 条 seed:
Talon qwen3-coder:30b 主力 / qwen2.5:7b 兜底 / DeepSeek 云端兜底默认禁用)
seed 不硬编码任何 API key(gitleaks 兜底), key 由运维在管理界面手动填
- 改造 llm_client.py: DB 多 Provider 模式, 按 priority 升序遍历,
transient 错误降级下一个, permanent 立即抛; 库为空回退 .env 单源(向后兼容)
- 新增 llm_provider_service: CRUD + 健康探测 + 调用日志 + 失败计数(连续
N 次自动标 unhealthy, 成功一次自愈)
- 新增 llm_warmup: 启动后 30s 异步预热模型触发加载到显存, 不影响业务
- 新增 api/v1/llm_providers: CRUD + /test-connection + /warmup + /call-logs
- 改 ai.py / ai_time_parser.py: 调 chat_completion 时传 db=Session + source
前端:
- 新增 views/system/LlmProviders.vue: Provider 列表(CRUD + 测试 + 恢复)
+ 调用审计日志分页 + 独立测试连接对话框
- 新增 api/llmProviders.js
- 注册路由 /system/llm-providers
2026-09-15 14:22:32 +08:00
902926b789
fix(ai): refund_recovery by_staff 过滤零值+top5 控字符数 ( #107 )
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 2m45s
backend-test / frontend-lint-test (push) Successful in 49s
2026-09-15 13:25:35 +08:00
b747dd1a63
feat(ai): 日报补充退款挽回数据 - 设备成功/失败按人员+类型分布 ( #107 )
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
在 _enrich_with_today_kanban 中增调 today_refund_recovery (复用 today:rr 缓存),
压缩后放入 feed.refund_recovery:
- kpi: total_sn/success_sn/failed_sn/success_rate + 成功/失败/放弃笔数
- by_staff: top10 人员的 name/total_sn/success_sn/failed_sn/success_rate
- by_reason: top5 退款原因的 reason/order_cnt/success_rate
AI 可据此分析"退款挽回成功率、谁挽回得好、哪类退款难挽回"。
2026-09-15 13:23:49 +08:00
6458fe664c
feat(ai): 日报数据补全 - AI 接管率 + 时间趋势 + 专项分布引导 ( #106 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 2m51s
backend-test / frontend-lint-test (push) Successful in 52s
背景: 用户反馈 AI 报告分析不出今日看板已有的数据。
诊断: 生产 daily feed raw=4277 chars 未裁剪, 但缺三类数据:
① AI 接管率(真人/AI/系统/客户自助分层) - today_kpi.reception.ai_takeover 存在但 _build_brief 未透传
② 时间趋势(接待/退款/投诉按小时) - _enrich_with_today_kanban 未调 today_hourly_trend
③ 专项分布数据在 feed 里但 AI 未重点分析(需 prompt 引导)
修复:
- external._build_brief: reception 段补 breakdown(4类来源分层) + ai_takeover(会话占比+设备占比)
- ai_feed._enrich_with_today_kanban: 增调 today_hourly_trend, 压缩为 4 个关键指标序列
- ai_feed._compact_hourly_trend: 新增压缩函数
- ai_focus_configs.focus-reception: 补充专项分布/AI接管率/时间趋势分析引导
- _CONSTANT_FEED_KEYS 加 hourly_trend
验证: feed 含 ai_takeover/breakdown/hourly_trend, 总字符 < 6000
2026-09-15 13:19:02 +08:00
958ab4ea5e
fix(ai): _complaint_enhanced 无缓存致周/月报 build_ms ~5.8s ( #105 )
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
根因: external._build_brief 调用的 _complaint_enhanced 完全没有缓存,
每次请求执行 15+ SQL(3 source×3 指标 + 补充表 + 外部办结),
而 today_kpi/refund_decision/staff 均已缓存命中。
生产实测: 同窗口两次请求 build_ms=5666/5859ms, total_ms ~10s。
修复:
- _complaint_enhanced 加 cached_call: key=complaint:enhanced:{start}:{end},
TTL=cache_ttl_for(历史日 24h/今日 60s, 与 today:kpi 对齐)
- _external_done_sns 加 cached_call: key=complaint:ext_done_sns, TTL=60s
(全表扫描, 每次 _complaint_enhanced 都调; 办结状态可能随同步变更故 60s)
预期: 同窗口第二次请求 build_ms 从 ~5.8s 降至 <500ms(缓存命中)。
2026-09-15 12:58:36 +08:00
19db34ed9c
fix(data-source): 修 Browser.vue 引用了不存在的 Database 图标 ( #100 )
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
`Database` 不在 @element-plus/icons-vue 的导出中(实测 Object.keys 无此键),
导致表头与空状态两处图标静默不渲染 + 控制台持续告警
`[Vue warn] Failed to resolve component: Database`。
属存量缺陷(改动前该文件就没有 icons-vue import,同样报此告警),
因本次正在改造此文件,一并修正为实际存在的 `Coin`。
验证: DOM 实测表头图标已渲染;eslint 通过;Browser.tree.spec.js 18 例通过。
2026-09-15 12:49:23 +08:00
c313d66831
refactor(data-source): 数据查看树按经营口径重组 + 命名一致性约束 ( #100 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
分组重构(五段:钱从哪出→谁在抱怨→谁在处理→交易售后→设备底座):
- 现金流 → 经营与对账;A~E 五路加前缀(A·退费单 / B·钱包提现 / C·闪猫退款 /
D·手动退款登记 / E·退款记录报表(对账基准)),E 的「对账基准」写进**显示名**
(只写 description 不算 —— 树上看到名字的人不会点开描述)
- 投诉登记 → 投诉;补入「投诉·服务请求工单」投影(工单侧第一入口)
- 订单管理/退货跟踪 从「现金流」「工单与客服」抽到新分组「交易与售后」
- merchant_refunds 显示名标注「⚠️ 旧·...(已停用)」(原先 Upload.vue 说"作废"、
数据查看说"商户号现金流",两处打架)
投影机制(DATA_SOURCE_PROJECTIONS):
- 同表别名,不落 data_sources 表,避免同表双注册导致统计口径分裂
- 强制声明真身分组+名称(AGENTS.md 界面纪律第 5 条:禁止无声明重复展示)
- 后端 resolve_projection_code() 让投影复用真身的字段/记录/导出接口
排序单一来源:
- CATEGORY_ORDER 为分组顺序唯一来源;前端删除本地硬编码的一份(两份定义必然漂移)
- 组内顺序由服务端 sort_order 决定;menu key 改 `分组::code`
(同一 code 出现在两个分组时,原先会同时点亮两项)
边界说明:
- 页头补 ⓘ:本页只列 21 张可下钻原始业务表;其余约 80 张(工作流/配置日志/
维度主数据/中间过程表)各有功能页,全量清单指向 REF 文档
(根因:使用者反复问"表是不是都在这里",而页面从未说明边界)
新增 META 规范 docs/standards/数据源命名与分组规范.md(已登记 docs/README.md §六
并挂进 AGENTS.md 引用链)。
测试: pytest 19 例命名不变量 + 7 例接口契约;vitest 18 例树渲染/设计说明。
2026-09-15 12:38:05 +08:00
21197d4ddf
feat(ai): 时间解析混合方案 - 正则优先 + LLM 语义兜底 ( #103 )
...
背景: 纯正则对口语表达覆盖不足(如"最近一周"需补正则), 每加一个表达就要改代码发版。
纯 LLM 则每次问都增加 1-3s 延迟 + 模型成本 + 幻觉风险。
方案: 正则优先(95% 常规表达,<1ms, 免费, 确定) + LLM 兜底(仅正则未命中时调用)。
实现:
- ai_time_parser.llm_parse_time_intent (async):
· 复用 llm_client.chat_completion(DeepSeek), prompt 要求输出严格 JSON
{start, end, confidence, matched}
· 后端校验: confidence>=0.7 / 日期合法 / 不晚于今天 / start<=end
· 任一失败(配置缺失/超时/JSON错/日期非法/低置信度)静默返回 _NONE,
退回 period 默认窗口, 不影响主链路
- ai.py ai_analyze: win.resolve(q) 后若 window_source=="period"(正则未命中),
await llm_parse_time_intent(q) 兜底, 命中则改写 window 并标 window_source="nl_llm"
测试: 7 case 全部通过(合法/低置信/非法日期/未来日期/异常/坏JSON/markdown包裹)
2026-09-15 12:38:05 +08:00
22611b9ebc
fix(ai): 时间解析支持中文数字近N天/周 ( #103 )
...
问题: 用户问"最近一周的退款情况分析", parse_time_intent 的
_RE_RECENT_N 只认阿拉伯数字(\d), "一" 不匹配 → 退回 daily 默认窗口,
AI 提示"窗口差异: 您问的是最近一周, 但数据是今日"。
修复:
- 新增 _RE_RECENT_CN_DAY: 近三天/最近五日/过去两日 → N 天
- 新增 _RE_RECENT_WEEK: 最近一周/近两周/过去三星期 → N*7 天
- 支持中文数字 一/二/两/三/四/五/六/七/八/九/十
2026-09-15 12:38:05 +08:00
6b7f1125de
fix(ai): keyword_to_scope 单值返回致多域问句切丢业务段 ( #98 )
...
问题: 问句"今日经营总览:接待、退款、投诉一起说说。"同时命中
reception/refund/complaint 三域, 但 keyword_to_scope 只取首个命中
scope, _select_feed_sections 据此只保留 refund 段, 切掉 reception
与 complaint 段 → AI 答"二、投诉/三、接待 未提供明细"。
修复:
- 新增 keywords_to_scopes 返回所有命中 scope 集合
- _select_feed_sections 改接受 set/list, 保留所有命中业务段
- build_messages 改用 keywords_to_scopes(question)
- 保留 keyword_to_scope 向后兼容(其它调用方未迁移)
2026-09-15 11:21:53 +08:00
5d3221e17a
refactor(ai,external): 数据底座双层结构+专项硬编码治理 ( #102 , #104 )
...
- ai_feed._build_feed 改造为双层结构:
· 日运营: 增量补今日值班看板(reception_breakdown/refund_breakdown/
queue/staff_top5), 全部走 cached_call + today:* 命名空间与前端看板共享缓存
· 周/月/区间: 按日颗粒度补 daily_trend 走势数组, ≤14天逐日, >14天前14天
逐日+后续按周聚合(防 token 超限)
- _SYSTEM_PROMPT 追加兜底铁律 8/9/10:
8 防主观推断(registered≠handled 禁相减/禁"消化存量"无证据因果)
9 业务判定方向(退款越少越好/外部投诉>商户号>内部等)
10 工作时间(08:00~23:59, 非工作时间由 AI 协助)
与 ai_focus_configs 三条 seed 对应但不可关停
- external._reception_specialty 移除 _BUCKET 硬编码改读 specialty.efficiency_field
- external.today_kpi/refund:do TTL bug 修复: 固定 60s → cache_ttl_for 自动感知
(历史日 24h / 今日 60s)
- alembic b3c4d5e6f7a8: specialties 加 efficiency_field; 历史专项"内部投诉/
外部投诉" is_active=False; ai_focus_configs 插 4 条新 seed(business-benchmark/
work-time-ai-assist/inference-discipline/ai-incremental-analysis) + 修正
focus-complaint/focus-reception 过时口径
2026-09-15 10:55:11 +08:00
bc93a9a0df
fix(ingest): 手动同步面板首次打开始终 0 勾选(BMS/SCRM 初始化竞态) ( #99 )
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Failing after 22m45s
backend-test / frontend-lint-test (push) Successful in 33s
## 问题
首次进入「数据采集 → 数据同步(统一入口)」,手动同步面板的勾选**恒为 0**,
主按钮灰显「开始同步(共 0 个 · BMS 0 + SCRM 0)」。连续刷新 5 次 100% 复现。
使用者被迫手工点 11 次才能发起第一次同步 —— 恰好是 #97 要消除的痛点。
## 根因
`onMounted` 用 Promise.all **并发**加载 BMS 与 SCRM,但两者**共用**一个
`typesInited` 标记,形成"谁先返回谁置真、后者跳过自己默认勾选"的竞态:
loadScrmStatus 先返回 → typesInited = true
loadBmsSources 后返回 → if (!typesInited) 为假 → **跳过 form.bmsTypes 赋值**
生产/本地实测 SCRM 恒先返回(响应体远小于 BMS 的 11 源状态聚合,后者还要查
各源 last_log),故为**必现**而非偶发。
**归属**:非 #97 引入 —— `loadBmsSources` 函数体与 `62c4234^` 逐字相同,
已用 `git show` 对比确认;共享标记由历史提交 `9bb9eed` 引入。
但它使 #97 的面板首屏不可用,故一并修复并在两单间交叉引用。
## 修复
- 拆为 `bmsInited` / `scrmInited` 两个**独立**标记(各自接口、各自默认值来源,
语义本就无关;合并只因"看起来像同一件事")。
- BMS 分支补 `bmsInited.value = true` —— ⚠️ 这是**必要动作**,不是可选优化:
修复共享标记后 BMS 分支才开始真正执行,若不置位,每 30 秒一次的轮询
(`loadBmsSources`) 会把用户已改的勾选**重置回默认值**。
即:不补这一行,修复会引入一个新的覆盖 bug。
- 加注释记录事故经过,防止后人重新合并标记。
## 验证
- 新增 `SyncDashboard.init.spec.js`(4 例):用「SCRM 立即返回 + BMS 延迟 120ms」
**精确复现原时序**,纯 DOM 断言。
- **反证测试有效性**:把源码还原为修复前版本重跑 → **3 例失败**,且失败信息
精确复现症状(`BMS 0 + SCRM 1`、`高频档=0 / 2`)。证明测试非空转。
- 浏览器实测:连续刷新 5 次稳定「已选 10 / 11」(修复前 5 次全为 0 / 11)。
- 验收项 3(轮询不覆盖用户改动):手改成「仅高频 4 个」后等 40 秒(≥1 次轮询),
勾选未被覆盖。
- eslint 0 error;vitest **90 passed**;vite build 通过。
## 工单
#99(本单)· 发现于 #97
2026-09-15 10:00:14 +08:00
3f1768b65e
perf(ai): 层3规则层秒回纯取数问句 ( #98 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
新增 ai_rule_engine.py: 纯取数问句(如"今日退款多少笔")直接从 feed 取数
+ 模板拼 Markdown 毫秒返回, 不入队 LLM, 不走 DeepSeek。
- try_rule_answer(db, question, eff): 命中 → Markdown; 未命中/取数空 → None 回退 LLM
- ai.py ai_analyze 短路入口: _require_enabled 后、submit 前判规则, 命中直接
返回 success(task_id=None), 返回结构与 LLM 路径一致(前端零改)
- 首批只实现 refund 规则(生产问句最多, 结构已确认); complaint/reception 留
接口框架 + TODO 后续补(避免猜结构出错)
- 推理词(分析/建议/策略/对比/为什么)→ 不命中规则层, 走 LLM
- 复用 ai_feed._build_feed(命中缓存秒取, 含坏缓存自愈), 不绕铁律
- 模板数字必须来自 feed, None→"暂无"不写 0(沿用 _SYSTEM_PROMPT 铁律精神)
测试: pytest test_ai_assistant.py 40 全过(5 新增 + 35 现有不回归);
前端 AssistantDrawer.spec.js 15 全过(不回归)。
工单: http://192.168.28.11:3000/tanzk/csctalon/issues/98
2026-09-15 09:56:55 +08:00
81316e9ea0
perf(ai): 层2问题路由+精确取数降输入token ( #98 )
...
复用 ai_focus_service._KEYWORD_TO_SCOPE(scope 值 refund/complaint/reception
与 feed 顶层段名逐字符对齐)做段切片, 不复用 GLOSSARY_ROUTE(粒度错位)。
- ai_focus_service 新增 keyword_to_scope(question) -> str|None 公开函数
- ai_feed 新增 _select_feed_sections(feed, scope): 恒量段(period/date/
date_start/end/generated_at/_spec/range)始终保留 + 命中 scope 对应业务段;
scope=None 回退全量(兼容, 零回归)
- build_messages 链路: _build_feed -> _select_feed_sections -> _trim_feed
问"今日退款"只喂 refund 段(含 a_route.litigation_*, 不漏涉诉率), 不喂
reception/complaint, 减 60-70% 输入 token。
缓存铁律: brief 仍走 _build_brief 命中 today:kpi/refund:do/complaint:* 既有
key, 段选取只在内存 dict 切片零成本, 不新建 key。
测试: pytest test_ai_assistant.py 35 全过(7 个新增 + 28 个现有不回归)。
工单: http://192.168.28.11:3000/tanzk/csctalon/issues/98
2026-09-15 09:52:23 +08:00
f53511e066
docs(ingest): 补声明档位展示与「数据源同步状态」的投影关系 ( #97 )
...
界面设计规范 §三-6 要求:同一指标若与别处重复展示,须声明「真身 / 投影」关系。
本组件的档位展示与「数据源同步状态」表的「定时同步」列,是同一份配置的
两种粒度投影(那里 = 纳不纳入定时,这里 = 按什么节拍跑):
- 真身:sync_source_config_service.list_scheduled_bms_sources()(后端单一判据)
- 两处均为其投影 —— 不新增配置、不产生第二个数字来源
同时把这条关系补进页面层 ⓘ(使用者可见),避免被问"这两处是不是两套配置"。
2026-09-15 09:51:06 +08:00
f079ff2a45
perf(ai): 层1参数调优降AI助手耗时 ( #98 )
...
生产实测(16条 ai task done 日志): llm_ms/total_ms=85~95%, 输出token
直接决定耗时(out_chars 2308→6484ms; 171→1479ms)。
- AI_MAX_TOKENS 1500→800: 实测 out_chars 多在 1200-2000, 800 token 足够
- AI_ANALYZE_MAX_CHARS 12000→6000: 降输入 token 推高首 token 延迟
- AI_SYNC_WAIT_S 3.0→8.0: 修订 #93(3.0 让几乎100%走轮询体感更差), 层1降
参后 total_ms~2.5s, p90<8 → 90%+ 一次返回不走轮询
- env.production.example 补 3 个示例值, 避免生产 .env 与代码默认脱节
测试: pytest test_ai_assistant.py 28 全过(不回归)。
生产已同步 /opt/csctalon/.env + up -d backend, env 生效。
工单: http://192.168.28.11:3000/tanzk/csctalon/issues/98
2026-09-15 09:48:30 +08:00
62c42343ce
feat(ingest): BMS 手动同步按高频/低频档分组 + 批量勾选 ( #97 )
...
## 问题
「数据同步统一入口」的手动同步面板把 BMS 11 个源平铺成一个无分组复选框列表,
使用者既看不出哪些是每小时跑的、哪些是每天才跑一次,也无法按组勾选,
"跑一遍所有高频源"要手点 4 次、低频源要手点 7 次。
## 改动
后端(只做加法,不改既有契约):
- 新增 `sync_source_config_service.get_schedule_tier_map()` —— 把
`list_scheduled_bms_sources()` 的 {high,low} 集合映射成 data_type → tier。
判据**唯一真源**仍是调度器消费的同一个函数,避免 UI 档位与调度实际漂移(#31 教训)。
- `bms_sync.get_sync_status()` 每源附 `schedule_tier: high|low|off`,
未纳入定时的源(default_sync=false / enabled=false)一律 off。
实现 A-20260907-001(LOCKED) §四「接口契约」预留项。
- `/fetch/schedule/status` 增补 `low_freq_hour`,供前端动态渲染低频档整点
(该值由 settings.LOW_FREQ_HOUR 控制,硬编码到前端会与实际调度脱节)。
前端:
- 新增 `BmsTierPicker.vue`:高频档 / 低频档 / 未纳入定时 三组卡片。
· 每组「本组全选 / 本组清空」;顶部「全选 / 清空 / 反选」
· 防误跑两个快捷:「只选已配凭证」(同时排除需验证码但登录态未保存的源)、
「恢复默认勾选」
· off 档默认折叠且不参与"手滑全选"—— 它们不进定时是有理由的(大表导出/需验证码)
· 档位不硬编码源名,一律读后端 schedule_tier;缺失时兜底 off(宁可不勾,不可误勾)
· 含页面层 ⓘ 设计说明(目的/分组依据/不做什么)
- `SyncDashboard.vue`:BMS 平铺复选框 → BmsTierPicker;提交链路不变
(仍是**单个** POST /fetch/task/submit)。
## 取舍(不做)
- 不做 BMS+SCRM 统一批量提交(SCRM 走独立端点,本次只改 BMS)
- 不给单源新增 cron/interval 字段(已有高/低频二分,再加一套会与调度器重复定义)
- 不改任何源的档位归属(那是 DB default_sync 用户态,本单只做展示 + 批量勾选)
- 不静默剔除"未配凭证"的源(静默剔除会让使用者误以为跑过了)
## 验证
- 后端:ruff 通过;`pytest` 1262 passed / 42 skipped / 0 failed
(新增 tests/test_schedule_tier_api.py 9 例:判据同源一致性、三个 off 兜底场景、
device_tag 派生源不得暴露、接口层向后兼容)
- 前端:`vite build` 通过;eslint 0 error;`vitest` 86 passed
(新增 BmsTierPicker.spec.js 15 例:分组归属、组内批量、总控、两个防误跑快捷、
节拍文案取自后端、运行中禁用)
## 设计说明(三层落地)
① 代码层:BmsTierPicker.vue 头部五要素注释块
② 页面层:面板内 ⓘ 气泡(含「不做什么」段)
③ 审查层:docs/standards/review-checklist.md 界面设计说明检查项
2026-09-15 09:46:33 +08:00
eb37d72c75
fix(ai,cache,org): 生产日志审计 8 项修复 (#89-#96)
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 4m2s
backend-test / frontend-lint-test (push) Successful in 31s
基于 2026-09-14 生产日志审计(app.log 1.4 万行 + nginx 10.7 万行),
修复 8 个确定性问题。核心结论:**AI 侧 LLM 与后端本身健康**
(11 次 DeepSeek 调用全 200、0 ERROR),用户感知的"AI 调用有问题"
是 UI 层两个独立缺陷叠加;另发现缓存预热静默失败导致接口实际退化。
#89 AI 轮询把 404 当瞬时错误 → 任务过期后永久卡「查询失败」
根因:AiTaskManager._tasks 是**进程内存**,后端重启即全丢 → /ai/tasks/{id}
返 404;前端 catch 分支不区分错误类型,重试耗尽后仍 `_savePending(taskId)`
保留 pending → 下次开抽屉再 404 → **死循环**。
生产实锤:同一 task_id 于 09-14 02:09 被重试 4 次(间隔 2.4s =
POLL_INTERVAL_MS×2,证实走 catch 分支),次日 09:38 仍 401 未清。
修:404/410 判为**终态** → 清 pending + 文案「任务已过期,请重新提问」;
网络错误仍保持重试 5 次(回归保护)。
#90 专项负责人硬编码 + 历史表 is_current 重复 → 「唐彩慧/董梦玲 是负责人」
用户问:"明明有清晰的负责人、组长划分,为什么会出现唐彩绘是负责人的说法?
是不是有硬编码?" —— **确实是硬编码**,三层叠加:
① init_groups_specialties.py 把负责人**写死人名**(唐彩慧/朱海洋/董梦玲/张蕾),
且脚本无幂等守卫 → 每次重跑覆盖用户决策;
② apply_org_change 的 SPECIALTY_LEAD(用户指定吴晓龙)**从未生效** ——
卸任查询多带 person_id 维度 → 查不到吴晓龙 → 旧 lead 从未卸任;
③ 历史表 (specialty_id, role) 出现**多条 is_current** 且 DB **无唯一约束**
(仅普通 btree 索引)→ 重复行可静默写入。
修:删除全部人名字面量,改为数据驱动(专项内唯一管理岗推断,歧义则留空
交人工指派);卸任改为专项维度(不按人过滤);新增部分唯一索引迁移
(specialty_id, role) WHERE is_current / (group_id, role) WHERE is_current。
#91 「负责人」双体系缺口径说明 → 对外/AI 输出混淆
本仓有**两套独立**体系:专项负责人(specialties.current_lead_id)vs
行政组长(groups.current_leader_id),但对外只给 {"role":"lead"} 英文枚举 →
读者无法区分,用户"说法不一致"部分源于此。
修:external 透出 role_label/scope_note;ai_feed 带 lead_role/lead_note;
glossary 新增「双体系」口径卡并挂到 负责人/组长/专项 关键词。
#92 缓存预热静默失败 → 投诉两接口 5 天 0 命中且完全不可见
`_warm_one` 的 except 降级为一条 WARNING,**无任何指标**暴露。
实测影响:complaints/subject-analysis 被调 115 次、**0 次命中**(0.19~0.25s),
对照组 analytics/today/kpi 全部命中(0.000s)。同型告警 6792 条占
app.log **28.9% 字节**。
修:预热结果结构化累加 + 每轮汇总(有失败升 WARNING 并列出失败项,限长 10);
cached_call 记录各 key 分组命中率,经 /system/cache/info 的 hit_stats
暴露(前端命中率表把 0% 标红 —— 即"静默退化"信号)。
注:代码修复(4a5d1ef/cd4afa3)已存在但**未部署**,本次随发布生效。
#93 AI 同步等待 6.0s 与模型延迟分布重合 → 33% 走 running 兜底(#66 未根治)
实测 p50=5.05s p90=6.13s,越线 26.7%、兜底 32.9%(与 09-10 记录一致 →
从未根治)。瓶颈定位:llm_ms 占 total_ms 85~95% → 是模型生成而非聚合。
修:AI_SYNC_WAIT_S 6.0 → 3.0(越线≠失败,只转轮询;配合 #89 后前端能正确
续查);顺带修 window_label 把变量名当文案的问题
(原 `label=period=daily` 不可读 → 改中文「今日(默认窗口)」)。
#94 半开探测无法恢复 ticket_finish
ticket_finish 是 ticket 的**派生别名**(不占 DB 配置行)→ 永远不在
list_scheduled_bms_sources 名单 → 熔断后报「未知 data_type」,
恢复延迟最长 +1h。
修:映射回宿主源 ticket 探测(二者抓同一份工单数据,gap_queue 已同此约定);
未知源告警改为**可行动**(含"请开启纳入周期采集"/"应加入 _PROBE_ALIAS")。
#95 前端硬编码旧组名 → 组改名后标签全部退化
RefundRecovery.vue 的 GRP_COLOR_MAP 写死「朱海洋组/董梦玲组/...」,
而 09-14 组织调整已改名(接待1/2/3组、专项1组、投诉专项组)→ 4/5 key 失效
→ 所有标签静默退化为灰色。
修:改为按**稳定标识**(组 id)取色板 —— 组名哈希实测 5 组对 5 色仍会碰撞,
出现顺序则会因翻页变色,组 id 是唯一稳定且无碰撞的方案。
#96 /ai/config 未登录也调用(271 次,是 analyze 的 3.3 倍,含 16 次 401)
修:改为「打开助手且已登录」时惰性加载;失败不置 cfgLoaded 以便重试。
验证:后端 ruff 全绿 + pytest 全量通过;前端 eslint 0 error、
vitest 71 passed(新增 15 项覆盖 #89/#96)、build 通过。
2026-09-15 09:09:34 +08:00
b496779cf6
fix(sync): cash_flow_wide 判据统一 + 补漏 external_complaint ( #88 )
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Successful in 31m38s
backend-test / frontend-lint-test (push) Successful in 39s
同单扩展(与上一提交同为「宽表触发条件过宽」这一个目标, 不另开单)。
1. 判据统一: cash_flow_wide 原先内联一套自己的元组判据, 且**不校验写入行数**
—— 相关源 success 但 0 行写入(如 BMS 查询结果为空)时仍全量重建(~12s)。
现统一走 CASH_FLOW_WIDE_RELEVANT_SOURCES 白名单 + _filter_relevant_sources,
与 refund_wide 判据完全一致(要求 inserted+updated > 0)。
2. ★补漏(既有 bug, 非本次引入): cash_flow_wide 白名单**缺少 external_complaint**。
builder 第 111/153/188/224 行确实读 external_complaints 的 SN 集来填
has_external_complaint 列, 但旧白名单没有它 → 外部投诉同步后该列陈旧(该刷不刷)。
现补入白名单。
3. 白名单集中定义 + 共用筛选函数 _filter_relevant_sources(), 消除重复代码;
上传通道 uploads.py 同步改用 is_cash_flow_wide_relevant(), 两处通道判据一致。
4. 两张宽表白名单**刻意不同**(职责不同), 测试锁死防止被误合并:
- refund_wide : refund/ticket/ticket_finish/wallet/external_complaint
- cash_flow_wide : refund/wallet/shanmao_refund/manual_refund/
refund_record_report/external_complaint
测试: test_refund_wide_trigger_scope.py 25 → 36 项
- cash_flow 白名单与 builder 消费表一一对应
- ★回归: external_complaint 必须在白名单(锁死漏刷不复发)
- 两张白名单必须不同(ticket 只在 refund_wide; shanmao/manual/E 只在 cash_flow)
- 上传通道: device_combo 不重建 cash_flow; 6 个相关源均重建
- 0 行写入 → 两张宽表都跳过
门禁: pytest 1221 passed / 42 skipped; ruff app/ tests/ alembic/ 全绿
2026-09-14 15:09:29 +08:00
927e42c1ff
fix(sync): 无关源同步不再触发退费宽表全量重建 ( #88 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
现象: 同步「设备套餐」(device_combo) 也会全量重建 refund_wide
(生产实测 66,451 行 / ~50s), 纯属白跑 —— 宽表行来自 refunds,
列来自 tickets/wallet_withdrawals/external_complaints, 与设备套餐无关。
根因(两处):
1. bms_sync.py post-sync: `use_incremental` 要求 len(all_affected) > 0,
于是「本次导入与宽表无关 → all_affected 为空」被误判为「降级全量」。
空集的正确语义是「宽表没有任何一行需要刷新」= 应跳过, 而非全量。
2. 两处通道都只看「有没有任一源成功」, 不判断该源是否与宽表相关:
- bms_sync.py `any_success`
- uploads.py `_run_post_import_tasks` 更是**无条件全量**, data_type 只用于打日志
修复:
- 新增 REFUND_WIDE_RELEVANT_SOURCES 白名单(refund/ticket/ticket_finish/
wallet/external_complaint), 与宽表实际消费的上游表一一对应
- 新增纯函数 decide_refund_wide_mode() 三态判定: skipped | incremental | full
· 无相关源写入 且 matcher 无新增 → skipped(本单核心)
· 否则维持原有 25%/2000 行阈值判定
- bms_sync.py 与 uploads.py **两处**共用同一判据(与 #41 cash_flow_wide 的
_cfw_relevant 写法对齐, 消除行为不一致)
- 日志补 mode/skip_reason/relevant_sources 便于审计
★ 保守优先(宁可多跑不可漏采): 只要有相关源写入过数据, 即使 affected 收集为空
也**不跳过**, 退回全量兜底 —— 避免"导入了但窗口过滤不到"造成静默漏采。
测试: 新增 tests/test_refund_wide_trigger_scope.py (25 项)
- 白名单与宽表消费表一一对应(含 device_combo 必须不在白名单)
- 案发现场复现: 只同步 device_combo → skipped
- 上传通道: device_combo/cs_record/bms_order 不重建; refund 仍重建
- 保守性: 无关源+matcher新增 → 仍重建; 相关源+affected空 → 全量兜底
门禁: pytest 1210 passed / 42 skipped; ruff app/ tests/ alembic/ 全绿
2026-09-14 14:57:42 +08:00
6f0a1d6fc5
feat(org): 刘景山 → 专项1组(吴晓龙组)+ 岗位「退款挽回专员」(2026-09-01 起)
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / backend-lint-test (push) Successful in 3m59s
backend-test / frontend-lint-test (push) Successful in 43s
## 用户决策
「刘景山 归为晓龙负责的那个组」
## 消歧(吴晓龙有两个"组"身份)
- **行政组组长**:专项1组(id=5)
- **专项负责人**:退款挽回 + 套餐专项 lead
用户指的是**行政组**(专项组长不对某人入组负责)→ 调入 **专项1组**。
且与上一批「刘景山是做退款挽回」**一致**:专项1组正是退款挽回+套餐的组。
## 变更(用户确认)
| 项 | 从 | 到 |
|---|---|---|
| 行政组 | 投诉专项组(洪佳组) | **专项1组**(吴晓龙组) |
| 岗位 | 客服 | **退款挽回专员** |
| 专项 | 退款挽回(上批已改) | 不变 |
⚠️ 岗位值含「专员」→ 命中 `EXEMPT_POSITION_PATTERNS` → **跨类全豁免**
(各专项按默认系数计,而非转接 0.2)→ **会抬高其工作量得分**,属预期。
## 实施方式(SCD 语义)
新增当前记录(effective_from=2026-09-01, is_current=true)
+ 结束旧记录(is_current=false, effective_to=2026-09-01)
+ 回写 `Person` 镜像(group_name/current_group_id)
**不覆盖历史** —— 历史区间仍按当时的组/岗位归因。
## 门禁
pytest 1185 passed / 42 skipped · ruff app/ tests/ alembic/ 全绿
2026-09-14 14:39:54 +08:00
cb0f945b9c
feat(org): 田兴月、陈玉红 岗位 → 投诉专员(2026-09-01 起)
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
## 用户决策
「田兴月、陈玉红 都是属于投诉专员;其中田兴月 从9.1开始;陈玉红 一直都是」
## 实施前的核实(重要)
查 `staff_assignments` 发现两人**当前 SCD 记录都从 2026-09-01 起**:
```
田兴月 | 2026-09-01 ~ 今 | 岗位=客服 | is_current=true
陈玉红 | 2026-09-01 ~ 今 | 岗位=客服 | is_current=true
```
→ 与生效日 EFF=2026-09-01 **正好吻合**,故直接按 EFF 新增当前记录即可。
⚠️ 特别说明「陈玉红 一直都是」:
其更早的 SCD 记录(07-01 / 07-28 / 08-02 各段)**未标注该岗位**,
但不虚构历史岗位值 —— 按 SCD 语义,**只从有据可依的日期起生效**,
不回溯改写不可考的历史(历史绩效不该被猜测性数据影响)。
## 变更
- 田兴月:客服 → **投诉专员**
- 陈玉红:客服 → **投诉专员**
两人专项(投诉专项)与组(投诉专项组)**已正确**,本次仅改岗位。
⚠️ 岗位值含「专员」→ 命中 `EXEMPT_POSITION_PATTERNS` → **跨类全豁免**
(各专项按默认系数计,而非转接 0.2)→ **会抬高其工作量得分**,属预期。
## 至此投诉专员共 4 人(岗位口径)
苏紫依、邵晨雨、田兴月、陈玉红
(洪佳为组长兼投诉专项负责人,按岗位分桶计入「组长」)
## 门禁
pytest 1185 passed / 42 skipped · ruff app/ tests/ alembic/ 全绿
2026-09-14 14:36:20 +08:00
aeff846f7f
feat(ai): AI 表列结构与效率值表对齐 + 始终渲染(用户要求)
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
## 用户需求
「AI 系统账户的表格样式可以仿照效率值的样式计算…会话数、专项接待数
接待合计 会话效率 接待效率 都可以计算出来的吧。
不需要等有数据的时候再来显示吧?」
## 1. AI 表列结构对齐真人表(两个看板都改)
| 之前 | 现在 |
|---|---|
| 账户 / 来源 / 会话数 / 接待数 / 占全站会话 / 说明 | 账户 / 来源 / 会话数 / **各专项接待数(个)** / **接待合计** / **会话效率** / **接待效率** / 占全站会话 |
- 列名与列序**刻意与真人表一致** → 上下两表可**对读**(读者不必自己做映射)
- 专项列**复用 `SPECIALTY_COLS`**(不另写一套)→ 将来加专项不会漏改
- 效率值列带**构成弹窗**(与真人同款),标次说明「与真人同权重表」
- 保留「占全站会话」—— AI 独有的接管率指标,真人表没有也不该有
## 2. ★ 始终渲染(用户追问的点)
原 `v-if="virtualStaffList.length"` → 无 AI 数据时**整块消失**,
读者无法区分「AI 本期零工作」与「这个功能没做」——
违反看板规范「空必须可解释」。
改为**始终渲染 + `#empty` 空态文案**:
「本窗口内**无 AI / 系统账户产生接待**(不是功能未启用)」
## 3. 后端字段确认(无需改动)
`_ai_scores` 已返回 `work_score` / `eff_score` / `breakdown`,
且 `sp_*` / `pp_*` 已由 breakdown 派生(上一提交完成)→ 前端可直接用。
## 新增测试 tests/test_ai_table_structure.py(12 项)
锁三条契约:
1. **列结构对齐** —— 5 个关键列名必须与真人表一致;专项列必须复用
`SPECIALTY_COLS` + `getSpecialtyPeople`
2. **始终渲染** —— 不得有 `v-if="virtualStaffList.length"`;必须有
`#empty` 且文案含「不是功能未启用」
3. **后端字段齐备** —— 前端每个列对应字段后端须真返回
(回归风险:前端加列后端没给 → 列恒空,不报错)
⚠️ 测试断言前**必须剥掉 HTML 注释** ——
注释里为说明原因会引用旧写法(如那行 v-if),连注释一起查会误报。
本人在本项目已因这个细节踩两次。
## 门禁
pytest 1185 passed / 42 skipped · ruff app/ tests/ alembic/ 全绿
· eslint 0 error · build ✓
2026-09-14 14:25:25 +08:00
e1d0d9d8e0
fix(ai): AI eff_score 恒 0 — 桶名用错(展示名 vs _category_case 输出值)
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
## 缺陷(同上一个提交,第二层)
`work_score` 已算出(5202.0),但 **`eff_score` 仍为 0.0**。
## 根因:桶名用错了一层
`_ai_cat_counts()` 里我写的是 `b.get("咨询接待")`(**展示用的专项名**),
但 `_category_case()` 的**输出值是另一套**:
| `_category_case` 输出 | 最终展示的专项名 |
|---|---|
| `咨询` / `其他问题` / `综合问题` | 咨询接待 |
| `退款` | 退款挽回 |
| `套餐问题` | 套餐专项 |
| `投诉` | 内部投诉 |
实测:AI 的 `problem_category` **全是「网络问题」**(5017 条)→
被 `_CATEGORY_BUCKETS` 归入 **"咨询"** 桶 → 我用 `"咨询接待"` 取不到值
→ `people=0` → `eff_score = 0`(**不报错,静默归零**)。
## 修复
与真人侧一致:用 `BUCK_FZ`(= 咨询/其他问题/综合问题)**同时累加**
sessions 与 people(原来是只累 sessions、people 拿错 key)。
## 测试补强(15 项)
新增 `test_ai_bucket_names_match_category_case_output`:
- 必须用 `BUCK_FZ` 同时汇总 sessions **与** people
- 不得把展示名(咨询接待/退款挽回/套餐专项)当桶 key
⚠️ 该测试只检查**代码行**、跳过 docstring ——
因为 docstring 里为说明原因会举例 `b.get("咨询接待")`,连注释一起查会误报。
## 门禁
pytest 1173 passed / 42 skipped · ruff app/ tests/ 全绿
2026-09-14 14:13:03 +08:00
58e14a61d8
fix(ai): AI 效率值恒 0 修复 — 真人 CTE 排除系统账户导致查不到自己
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
## 缺陷(我在上一个提交引入/未察觉)
AI 行的 `work_score`/`eff_score` 在**生产实测仍为 0**,桶构成全空。
## 根因:AI 去查了一个**明确排除 AI 自己**的数据源
`cs_cat_by_op` / `merged_pp` 都来自 `cs_cat_q` CTE,其 WHERE 含
`_sys_auto_filter()` —— **排除** WeChat/AI客服 等系统账户
(这是**对的**:真人统计不该混入 AI)→ AI 查它**恒为空**。
⚠️ 典型"静默失效":不报错、不崩溃,只是数字恒为 0。
## 为什么不改那条 CTE(走了弯路后的结论)
它是**真人榜的公共查询**,放开过滤会让 AI 混进真人排名(违反 #78)。
## 正解:另查一次,但判据逐项一致
`_ai_cat_counts()` 改为直接查 `cs_records`:
- 分类判据 = **同一个** `_category_case()`(不另写分桶逻辑)
- 去重判据 = 同 `count(distinct nullif(trim(device_sn),''))`
- 时间/非空条件同
- **唯一差别** = 不过滤系统账户(这正是保留 AI 的原因)
⚠️ 并在 docstring 显式写明"为何不复用真人 CTE" ——
否则后人会"顺手优化"改回去,AI 效率值又静默归零。
## 测试同步更新(14 项)
原断言"必须复用 cs_cat_by_op/merged_pp"是**错的**(那正是缺陷来源),
改为锁**新的一致性契约**:
- 必须复用 `_category_case()` + `distinct device_sn` 判据
- 必须**不**套系统账户过滤(否则排除掉自己)
- 必须有"为何不复用"的注释(防后人误改)
- `processing` 桶须为 `(0, 0)`(AI 不执行退款操作)
## 门禁
pytest 1172 passed / 42 skipped · ruff app/ tests/ alembic/ 全绿
2026-09-14 14:09:30 +08:00
14d7ed03af
feat: AI 纳入效率值核算 + 组长/管理角色标识 + 第4批组织调整
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
## 1. ★ AI 接待纳入效率值核算(用户要求)
「能不能 在效率值的核算表里面 把AI接待的也纳入统计,这样可以比对出 AI的效率值,
计算办法 和专项人员一致」
**缺陷**:AI 行此前只有量级(会话数/接待数),`work_score=eff_score=0`
→ 无法与真人横向比对,「AI 顶几个客服」这个问题答不了。
**修复**:提取 `_ai_cat_counts()` + `_ai_scores()`,用**与真人完全相同的**
`CAT_BUCKETS` / `CAT_DEFAULT_WEIGHT` / `TRANSFER_PENALTY` 计分:
- AI 无专项任职 → 按**接待实际构成**逐桶取该桶默认系数
(与真人「管理岗跨类全豁免」同构,保证可比)
- 接待量口径**复用真人侧同一 `cs_cat_by_op` / `merged_pp`**(同一 SQL 结果)
- `sp_*` / `pp_*` 字段由 breakdown 派生(原为写死 0)
- **仍不参与真人排名**(`rank=None`)—— AI 不是人
⚠️ 若给 AI 另写一套系数,两者效率值即不可比,需求就失去意义 →
`tests/test_ai_staff_efficiency.py`(11 项)锁死"同一张权重表"。
## 2. 组长/管理角色标识(用户要求)
「叶珍凤、董梦玲、吴晓龙、黄亚康、洪佳都是组长;希望无论是今日值班看板
还是运营分析 都能很明显的标识为组长。陈卓、朱海洋 都是主管级别的;
可以标识为 管理。」
- **今日值班看板**:值班条芯片加**前置角色徽标**(管理=红 / 组长=蓝),
文案用管理视角词(主管→「管理」)
- **运营分析看板**:人效表新增「岗位」列(紧邻姓名),管理岗**实底高亮**、
非管理岗浅色文字 → 长表中管理岗自然浮出
- ⚠️ 两处都**不用 `is_management`** 判断 —— 它含专员类跨类豁免岗
(投诉专员/退款处理专员),那些不是管理岗
## 3. 第三批组织调整(脚本)
- **唐彩慧** → 岗位「退款处理专员」;**卸去退款挽回 lead**
(用户:专职退款处理,不做退款挽回,不是 lead)
- **退款挽回 + 套餐专项** lead → 均改为**吴晓龙**
- **廖俊辉** → 专项「退款挽回」
- **陈卓** → 岗位「主管」(原无岗位 + inactive)
- **孙芳粤** → 部门改「研发部」(不纳入企微统计)+ **停用重复 Person**
(实测有 2 条记录 pid 264/377,别名 cat_月白/cat_yuebai)
- 叶珍凤 **不改**(用户澄清:就是组长)
## 门禁
pytest 1169 passed / 42 skipped · ruff app/ tests/ alembic/ 全绿
· eslint 0 error · build ✓
2026-09-14 14:06:20 +08:00
710db4ebfa
feat(org): 新增岗位「投诉专员」+ 苏紫依/邵晨雨任职 + 前端专项列补齐
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 4m11s
backend-test / frontend-lint-test (push) Successful in 34s
## 1. 新增岗位值「投诉专员」(后端 + 前端)
原 `position` 枚举只有 客服/组长/副主管/主管,投诉团队缺专职岗标识
→ 主管看不出「投诉专职」与「兼办投诉」的区别。
改动:
- `analytics.COMPLAINT_SPECIALIST_POSITION = "投诉专员"`(新常量)
- `_person_position_map._bucket()` 优先按**岗位**判定投诉专员
(避免仅靠专项 → 未指派专项的专职者落入「客服」)
- `Persons.vue` 岗位下拉新增该选项
⚠️ 两个易混概念(已在代码注释写明):
· `position`(岗位) = 客服/组长/副主管/主管/**投诉专员** → 驱动岗位负荷分桶
· `role`(角色/权限) = agent/specialist/specialty_lead/... → 「专员」早就在 role 里
⚠️ **有意副作用**:值中含「专员」→ 命中 `EXEMPT_POSITION_PATTERNS`
→ 获**跨类全豁免**(各专项按默认系数计,而非转接 0.2)。
正确(投诉专员的高难度工作不应按转接贬值),但**会抬高其工作量得分**。
## 2. 用户第二批决策(脚本)
- **苏紫依** → 岗位「投诉专员」(9月实际完成 548 单投诉)
- **邵晨雨** → 岗位「投诉专员」(9月实际 100 单)
- **叶珍凤 → 不改**:用户澄清「叶就是组长,不是投诉专员,
只是所有的组长都有可能处理投诉」→ 已从首批计划的改动中移除
- 苏紫依补入 MOVE_TO_GROUP:其组名实际未变(张蕾组已改名投诉专项组),
但 `Person.current_group_id=NULL` / `group_name='张蕾组'` **镜像陈旧** →
本次修复镜像至与真身一致
## 3. 前端落后项补齐(上轮改了后端漏改前端)
- `SPECIALTY_COLS` 补「投诉专项」列(7 列)——
列 key 必须与后端 `CAT_BUCKETS.cat_name` 逐字一致,否则恒显示「—」
- 系数说明行补「投诉专项×5.0」(用户选择保留系数行)
- 投诉健康度 ⓘ 文案更新(原写"专项∈(内部投诉,外部投诉)"已过时)
## 未做(用户自行处理)
赵苏惠、宋思雨:系统中无 Person 也无别名记录 → 用户选择自己新建
## 门禁
pytest 1158 passed / 42 skipped · ruff app/ tests/ alembic/ 全绿
· eslint 0 error · build ✓
2026-09-14 13:50:52 +08:00
c3cf3054da
fix(org): 修复「两套 SCD 表」写入错表导致人员变无专项(含刘景山→退款挽回)
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 4m0s
backend-test / frontend-lint-test (push) Successful in 44s
## ★ 事故:改了专项但人变成「无当前专项」
生产执行后发现刘景山**没有任何 is_current 的专项分配** ——
旧记录被正确关闭,但新专项根本没写进去。
## 根因:本仓库有**两套并存的 SCD 表**,互不同步
| 表 | 服务谁 | 谁在读 |
|---|---|---|
| `staff_assignments` | 任职历史(岗位/专项/分组 整体快照) | staff_assignment_service、HR 导入导出 |
| `person_specialty_assignments` + `specialties` | **专项**分配(is_primary) | `analytics._person_position_map`、投诉专员识别 |
我用了 `staff_assignment_service.create_assignment` 改专项 →
它写的是 `staff_assignments`,而**专项识别的真身是
`person_specialty_assignments`** → 结果两边都不对。
## 修复:新增 `_assign_specialty()` 一次写齐三处
1. `person_specialty_assignments`(**专项真身**,与 `specialties.assign_persons` 同路径)
2. `staff_assignments`(任职快照,保持与专项一致)
3. `Person.specialty` / `primary_specialty`(镜像)
⚠️ `Person.specialty` 是**镜像字段**,只写它不会生效(会被下次同步覆盖);
只写 `staff_assignments` 也不会被专项逻辑读到 —— 必须写第 1 项。
## 一并修复此前受影响的 5 人
`step_merge_complaint_specialty`(投诉专项合并)有**同样缺陷** →
洪佳/陈玉红/苏紫依/邵晨雨/田兴月的投诉专项也未真正写入,本次一并修复。
## 用户确认
「刘景山 是做退款挽回。9.1 开始」→ 确认项,与杨馄瑶(→咨询接待) 一起执行。
## 门禁
pytest 1158 passed / 42 skipped · ruff app/ tests/ alembic/ 全绿
2026-09-14 13:35:18 +08:00
8ce72ad99f
fix(org): 补执行遗漏的专项校正 — 刘景山→退款挽回、杨馄瑶→咨询接待
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
## 修复我的疏漏
上一步定义了 `SPECIALTY_FIX` 字典却**忘了写消费它的 step** →
该配置**从未被执行**(生产实测:杨馄瑶仍为「退款挽回」,
刘景山仍为「内部投诉」)。
⚠️ 教训:定义配置却无人消费,比"没写"更危险 —— 它让人以为已经改了。
## 用户确认
「刘景山 是做退款挽回。9.1 开始」→ 归入本次变更(生效日 2026-09-01),
从待复核项移到已确认项。
## 新增 step_fix_specialties(④.5)
- 刘景山: 内部投诉 → **退款挽回**
- 杨馄瑶: 退款挽回 → **咨询接待**(实测退款类接待 0%,纯咨询岗特征)
⚠️ 必须显式带上现岗位(`position=p.position`)——
`create_assignment` 语义是"整套任职",
岗位未变但专项变时若漏传 position 会把它**静默置空**。
⚠️ 旧专项分配只结束(is_current=False + effective_to),不物理删除 ——
历史区间仍需按当时的专项归因。
## 剩余待复核(脚本不自动执行)
唐彩慧(退款处理 + 兼任退款挽回 lead)、廖俊辉(退款类 23%,介于两者间)
## 门禁
pytest 1158 passed / 42 skipped · ruff app/ tests/ alembic/ 全绿
2026-09-14 13:32:17 +08:00
4a5d1efe17
fix(cache): 修复 #67 遗留的两处「预热静默失败」+ 加对齐守护测试
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
## 缺陷 1(#67 遗留,2026-09-11 引入):subject 预热每次都失败
#67 给 `complaint_subject_analysis` 加了 4 个 Query 参数
(`source_type`/`sort_by`/`top_n`/`compare`),但 `cache_warmer` 未同步 →
缺省值变成 `Query` 对象 → 内部报
`'>' not supported between instances of 'Query' and 'int'`
→ 预热**每次静默失败**(被 `_warm_one` 的 try/except 吞掉,日志仅一行 warning)
修复:预热 key 补全为 6 段 + kwargs 补齐 4 个默认值,与路由 `cached_json` 对齐。
## 缺陷 2(同类,由新测试抓出):detail 预热 key 缺 subject 段
路由 key = `complaint:detail:{start}:{end}:{source_type}:{subject}:{limit}:{bool}`,
预热写的是 `...:None:{limit}:1`(缺 `subject` 段)→
**预热写进的 key 与路由读的 key 不一致 → 缓存永不命中**(不报错,只是白预热)。
## 新增守护测试 tests/test_cache_warmer_alignment.py(7 项)
这类缺陷的共同特点:**升级健康检查通过、单测全绿**,
只有翻日志才能发现(预热失败不影响服务启动)。故用自动对齐检查替代人眼比对:
- 路由的每个 `Query` 参数必须出现在 cache_warmer 源码中(抓 #67 形态)
- subject/detail 预热 key 形状断言
- CAT_BUCKETS ↔ cat_data 桶 key 集合相等(抓我今天那次 KeyError)
## 门禁
pytest 1158 passed / 42 skipped · ruff app/ tests/ alembic/ 全绿
(scripts/ 有 188 项既有历史债,不在门禁范围;本人新增脚本 lint 干净)
2026-09-14 13:28:30 +08:00
cd4afa38de
fix(org): 修复我引入的生产事故 KeyError:'complaint' + 卸任步骤静默失败
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
生产执行组织变更时暴露两个我自己引入的缺陷,均已修复。
## ★ 缺陷 1(生产事故):KeyError: 'complaint'
`CAT_BUCKETS` 我加了第 7 桶 ("complaint", ...),但 `cat_data` 漏加对应键 →
`cat_data[bucket_key]` 是**无守卫直接索引** → 人效接口整体 500。
现象:缓存预热日志「预热 今日:today:staff 失败(不影响业务): 'complaint'」
(因预热有 try/except 兜底,故升级流程的"健康检查通过"**没能拦住它**)
修复:cat_data 补 `"complaint": (complaint_sess, complaint_peop)`,
并把 total_devices / reception_count 的求和也纳入新桶
(新桶当前恒为 0,不改变既有数值)。
⚠️ 教训:**单测全绿也不够** —— 该路径的取值是运行期动态的,
所以补了 `test_cat_buckets_keys_match_cat_data_keys`(集合相等断言)。
## ★ 缺陷 2:卸任步骤静默失败(数据不一致残留)
步骤⑤「卸任吴晓龙 原『吴晓龙组』deputy」用了旧组名查 —— 但步骤①已把组改名
→ 查不到 → **静默 continue** → 残留矛盾状态:
「吴晓龙 同时为 接待3组副主管 + 专项1组组长,且已不是接待3组成员」。
修复:
- END_LEADERSHIP 改用**新组名**(_g 自带新→旧回退,改名前后都能命中)
- 查不到时**抛异常**而非静默跳过(不可静默失败)
## ★ 补充:⑧ 离职人员清理(新增步骤)
生产核实发现张蕾离职后仍**在职**两处:
- 「内部投诉」专项 lead(is_current=true)→ 报表会把离职者算作在岗负责人
- 「投诉专项组」成员(is_current=true)→ 组人数虚高
新增 step_cleanup_departed:结束其专项 lead + 组关系
(只置 is_current=False + effective_to,**不物理删除**,保留历史可回溯)
## 门禁
pytest 1151 passed / 42 skipped · ruff app/ tests/ alembic/ scripts/ 全绿
(新增 1 项防回归测试:垃圾桶 key 集合一致性)
2026-09-14 13:25:23 +08:00
5c4b799985
feat(org): #88 组织架构调整(2026-09-14) — 组改名/组长轮岗/专项合并/离职登记
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
## 代码变更(投诉专项合并,只增不改)
新增「投诉专项」(系数 5.0 = 原外部投诉值,用户拍板),**保留**内部/外部投诉旧专项:
- 历史任职仍指向旧专项 → **历史绩效可追溯、不重算**
- 只**新增**新专项映射,保留旧映射作为历史归因通道
- 数据源 SCD 查询改用 COMPLAINT_SPECIALTIES_ALL(新旧都认,
否则合并后 5 人识别不出是投诉专员 → 岗位负荷表投诉专员人数变 0)
改动点(含 3 处 _hr_map 副本,其中 2 处是硬编码元组,极易漏改):
- 新增常量 LEGACY_COMPLAINT_SPECIALTIES / COMPLAINT_SPECIALTY /
COMPLAINT_SPECIALTY_WEIGHT / COMPLAINT_SPECIALTIES_ALL(前置于
_PERSON_CAT_NAMES,避免 NameError)
- WEIGHT / CAT_DEFAULT_WEIGHT / CAT_BUCKETS / EXEMPT_CATEGORIES /
_PERSON_CAT_NAMES 各加新专项
- _hr_map ×3 分支接入新专项
## ★ 同名陷阱(已在代码注释显式警告)
「内部投诉/外部投诉」既是**人员专项**又是**投诉来源桶**(complaint_scope),
字符串相同但语义无关。本次**只动人员专项**,未碰来源桶 155 处引用。
## 变更脚本 scripts/apply_org_change_20260914.py
用户 9 条决策逐条可追溯(脚本头部记录原文),7 步单事务 + 幂等 + dry-run:
① 5 组改名(接待1/2/3组、专项1组、投诉专项组)
② 组长轮岗(接待3组←黄亚康、专项1组←吴晓龙、投诉专项组←洪佳)
③ 成员迁组(邵晨雨、田兴月 → 投诉专项组)
④ 岗位职级(董梦玲副主管、吴晓龙组长、黄亚康组长、洪佳组长、朱海洋主管)
⑤ 卸任残留(吴晓龙 原「吴晓龙组/deputy」——
⚠️ 实测该组原本无 leader 只有 deputy,role 分属两个取值,必须分别清理)
⑥ 投诉专项合并(新建 + 洪佳 lead + 5 人迁入)
⑦ 张蕾离职(2026-09-01,resigned)
安全:默认 dry-run,须 --apply 才写;单事务失败整体回滚;不物理删历史。
## 测试 tests/test_complaint_specialty_merge.py(13 项)
锁定三条不变式:
1. **历史不可重算** —— 旧系数(3.0/5.0)不得改动、旧专项仍在豁免集合
2. **新专项完整接入** —— 漏接则映射 None → 得分静默归零
(含 _hr_map 副本数量锁 =3,防"只改一处")
3. **语义隔离** —— 人员专项逻辑不得引用来源桶常量
## 门禁
pytest 1150 passed / 42 skipped · ruff app/ tests/ alembic/ scripts/ 全绿
## 未自动执行(待业务复核)
刘景山(疑实际做退款挽回)、唐彩慧(退款处理+兼任挽回lead)、廖俊辉(介于咨询/挽回)
2026-09-14 13:20:08 +08:00
fdc03f17bc
docs: #87 补充职责交接精确时点与岗位标签偏差清单(含校正建议)
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Successful in 33m11s
backend-test / frontend-lint-test (push) Successful in 30s
## §3.6 职责交接精确时点(按周追踪,决定性证据)
逐周追踪每人投诉结案数,锁定 **w35→w36 为交接周**:
| 姓名 | 登记专项 | w34 | w35 | w36 | w37 |
|---|---:|---:|---:|---:|---:|
| 董梦玲 | 组长 | 479 | 533 | 308 | 0 |
| 叶珍凤 | 组长 | 586 | 146 | 84 | 0 |
| 吴晓龙 | 副主管 | 327 | 495 | 3 | 0 |
| 苏紫依 | 内部投诉 | 27 | 44 | 174 | 102 |
| 黄亚康 | 退款挽回 | 0 | 106 | 205 | 1 |
| 田兴月 | 咨询接待 | 0 | 0 | 143 | 30 |
三位组长/副主管 w36 集体放量退出,**同周**三位新人放量接手
→ 判定为**有组织的职责交接**,非零散行为。
## §3.7 岗位标签与实际工作偏差清单(6 处不符,含校正建议)
| 姓名 | 当前专项 | 实际画像 | 建议 |
|---|---|---|---|
| 黄亚康 | 退款挽回 | 退款挽回+投诉(312单) | 双职责 或 →投诉专员 |
| 田兴月 | 咨询接待 | 实际做投诉(173单) | → 投诉专员 |
| 邵晨雨 | 咨询接待 | 实际做投诉(139单) | → 投诉专员 |
| 刘景山 | 内部投诉 | 实际做退款挽回(98%) | → 退款挽回 |
| 杨馄瑶 | 退款挽回 | 实际做咨询接待(0%退款) | → 咨询接待 |
| 廖俊辉 | 咨询接待 | 偏退款挽回(23%) | 复核 |
相符无需改动: 苏紫依(专职投诉) / 洪佳(零接待纯投诉) / 陈玉红 /
卢宁 / 覃克泳(退款挽回 93-98%)
判据 = `problem_category='退款'` 占比(纯咨询 0% vs 退款挽回 93-98%,区分度高)
## 安全边界(重要)
⚠️ 清单**仅作业务校正依据,不自动改数据** —— 岗位/专项决定绩效权重系数
(`analytics.WEIGHT` 按专项取系数,投诉类高于咨询接待),**变更即影响绩效**。
必须人工确认后走 `staff_assignment_service.create_assignment` 收口。
2026-09-14 11:05:29 +08:00
68cf69549a
fix(dashboard): #87 兼办投诉卡显示修正 + 提示条渲染
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
生产验收发现两处显示问题(均为我上一步引入):
1. 「兼办投诉」大数字显示为「— 单/人」
—— 该行 headcount=null(设计上不算人均),但主数字仍走人均分支 →
看起来像"数据缺失"。改为:headcount 为 null 时主数字直接显示工作量
(如「5 单」),副行显示「5单(不计算人均)」。
2. 黄色提示条未出现 —— 上一步的插入锚点匹配失败(只成功了一部分),
提示条 HTML 实际未落入文件。本次补入并验证 DOM 实测可见。
## 生产验收(修复前)
pos: 组长 0单/4人 | 投诉专员 11单/4人 | 客服 15.2次/35人 | 兼办投诉「— 单/人」❌
notice: undefined ❌
归因合计 50 条(含未登记岗位 5 条)
## 验证
eslint 0 error · build ✓
2026-09-14 11:03:50 +08:00
fc749903ee
feat(dashboard): #87 补充「兼办投诉」归属 + 岗位迁移与标签偏差分析备注
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
## 补充归属(后端)
`by_position` 新增「兼办投诉」行 —— 完成投诉但岗位**不属于**组长/投诉专员的人
(实测 569 单 / 占 6.8%)。
为什么必须显式给出:若不单列,这部分工作量会"消失"在
`attribution.total_handled` 与三岗位之差的差额里,使用者会误判为数据缺失;
小比例会让人以为"临时调配无关紧要",实际它是**岗位制度被绕过**的信号。
⚠️ 该行**不给人均** —— 「其他」桶是所有未登记岗位/主管的集合(实测 239 人,
接近全员),拿它当团队规模会算出 2.4 单/人这种无意义数字。
## 前端
- 岗位负荷表兼容 `headcount: null`(显示"(不计算人均)"而非 NaN)
- 新增黄色提示条:显式告知"有 N 条投诉由非组长/专员完成(x%)"
## 文档(设计文档 §3.5 / §3.6 新增)
- §3.5 岗位分工迁移:按周统计,**约周36 投诉处理主体从「组长」迁移到
「投诉专员+客服」**,周37 组长归零。含 7 周明细表。
⚠️ 待业务确认是有意调整还是流程偏差
- §3.6 岗位标签与实际工作偏差:11 人交叉核对表(登记专项 vs 实际工作量)
发现 6 处不符,例如刘景山标「内部投诉」却零投诉完成、98% 接待是退款类;
田兴月/邵晨雨标「咨询接待」却分别完成 173/139 单投诉。
判据 = `problem_category='退款'` 占比(纯咨询 0% vs 退款挽回 93-98%)
⚠️ 仅作校正依据,不自动改数据 —— 岗位/专项影响绩效权重系数,
变更须经人工确认后走 staff_assignment_service 收口
## 验证
pytest 1137 passed / 42 skipped · ruff app/ tests/ alembic/ 全绿
· eslint 0 error · build ✓
2026-09-14 11:02:05 +08:00
26ca79b7f1
feat(dashboard): #87 第一批投诉健康度指标(活跃SN/投诉率三维/三岗位负荷/重复投诉率)
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 10m56s
backend-test / frontend-lint-test (push) Has been cancelled
按设计文档 A-20260914-001 §4.1 实现第一批 9 项指标,落点=运营分析看板。
## 新增指标
- 活跃SN基数(窗口内 cs_records 去重 device_sn)—— 所有投诉率的分母基底
- 投诉率三维:全站 / 对外 / 对内 = 投诉SN ÷ 活跃SN
- 三岗位负荷:组长 / 投诉专员 / 客服(按岗位归因)
- 重复投诉率 + 重复投诉SN数(30天内同SN投诉≥2次)
## 关键口径决策(用户 2026-09-14 拍板)
1. 投诉判据 = complaint_scope.is_complaint_ticket_cond(空白 OR '投诉')
⚠️ 不可用 LIKE '%投诉%' —— 实测只得 5 条,会得出"重复投诉=0"的假结论
2. 活跃SN = 窗口内有接待记录的去重设备SN
3. 岗位归属 = staff_assignments(SCD, is_current=true) + specialties 字典
· 投诉专员 = position=客服 + specialty∈(内部投诉, 外部投诉),实测在岗 4 人
· 组长含副主管(实测该层承担 51%,副主管单列会致低估)
4. 分子按岗位归因(关键修正):
· 组长/专员 = 该岗位完成的投诉结案数
· 客服 = 接待数(用户明确:客服职责是接待,非结案)
⚠️ 初版误用"全站结案总数"作共同分子 → 组长与专员人均都=1307,无管理意义
5. **「重复投诉率」禁止叫「升级率」** —— 前者是代理信号,后者需升级投诉表
(申报/化解字段),混用即口径漂移(#81/#83/#85 病根)
## 口径收敛(消除三处潜在漂移)
- 提取 handler_fallback_expr 到 analytics 作**单一来源**,
complaints._handler_fallback_cols 改为委派
- 提取 REAL_FINISH_EPOCH_SECS 常量(原 analytics/complaints 各写一份 180)
- 岗位/专项读 SCD 真身表而非 Person 镜像(镜像实测与真身不一致 144 条)
## 空值语义(规范强制)
- 分母为 0 → 比率返回 null,前端显示「—」**不是 0%**
(0 会被读成"零投诉",与"无接待记录"结论完全相反)
- 无在岗人员 → 人均返回 null,**不是 0**
## 可解释性(看板卡片规范双强制)
- 代码层:界面文件头部完整「设计说明(Design Rationale)」(五要素)
- 页面层:三个 ⓘ(总览/重复投诉/岗位负荷),均含「取舍」段
- 下钻:投诉率卡 → 投诉分析页(带当前时间窗口 + 来源筛选)
- 显式声明不可下钻者(岗位负荷表)及理由(岗位是处理人维度计算列,明细按日筛选无法透传)
## 缓存
- key `complaint:health:{date}:{start}:{end}` → 自动纳入 `complaint:*` 失效 pattern
- 已接入 cache_warmer 预热(key 与路由逐字符对齐)
## 验证
- pytest 1137 passed / 42 skipped(新增 25 项口径契约测试)
- ruff app/ tests/ alembic/ 全绿
- eslint 0 error;build 通过
设计文档: docs/arch/_pending/A-20260914-001-投诉升级链路指标体系设计.md
2026-09-14 10:50:47 +08:00
3aac9ee38b
chore: #86 清理废弃接口 + alembic 存量 lint
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 3m52s
backend-test / frontend-lint-test (push) Successful in 29s
用户 2026-09-14 决策:「废弃的接口 就清掉;lint 该清理清理」。
## 一、清理废弃接口 `GET /analytics/summary`(4 处)
**为什么删而不是继续标"废弃"**:
1. 标记已存在(`analytics.py` 路由 tags = 「已废弃-见analysis-views」),
但**仍在被周期性预热** → 白白消耗 DB/Redis(`cache_warmer.py:236`)
2. 前端 `web/src/api/analytics.js` 的 `getSummary()` **全仓零调用点**
(grep 仅命中定义处,无任何 import/使用)
3. ★ **最关键**:其旧公式 = 「工单投诉类 + 315 全表 + 卡商 全表」
(`analytics.py:1649`)—— **含卡商且不扣重叠**,与 #53/#81 已确立的口径
**直接冲突**。留着就是**错误口径的残留入口**:将来若有人误用,
会拿到与看板不一致的投诉数 → 属隐患,非无害死代码。
**删除清单**:
- `backend/app/services/analytics.py`:`dashboard_summary()` 函数本体(142 行)
- `backend/app/api/v1/analytics.py`:`GET /summary` 路由
- `backend/app/services/cache_warmer.py`:预热项 `("summary", ...)`
- `web/src/api/analytics.js`:`getSummary()`
(均在删除处留注释说明原因,便于后人检索)
## 二、alembic 存量 lint 清零(63 项 → 0)
```
清理前: 57 I001(import 未排序) + 5 F401(未使用) + 1 W291(尾随空格) = 63
清理后: All checks passed!
```
- 62 项 `ruff check --fix` 自动修复(**纯 import 排序,零逻辑变更**)
- 1 项 W291 手动修正(`d9b7f1b89c1d_...py:4` `Revises:` 尾随空格)
**项目门禁升级**:`ruff check app/ tests/` → **`ruff check app/ tests/ alembic/`**
(此前 alembic 不在门禁范围,故存量问题长期累积)
## 三、安全校验(删除操作必须的谨慎)
- **删除范围精确**:`diff` 备份确认**仅** `dashboard_summary` 被删
- 过程中**误删了紧随其后的 `_CATEGORY_BUCKETS` 常量**(初版删多了 8 行),
被 `ruff F821` 立即发现并已恢复 → 说明 lint 既是清理对象也是**安全网**
- `alembic heads` 仍为**单 head**(`c4d5e6f7a8b9`)→ 迁移链未被改坏
## 门禁
```
pytest 1112 passed, 42 skipped(与清理前一致,零回归)
ruff app/ tests/ alembic/ All checks passed!
eslint 0 error
build ✓
```
## 遗留(已在 #86 登记,非本单范围)
- `docs/审查行动项登记.md` / `docs/arch/*` 中仍有 `dashboard_summary` 的**历史记述**
(属文档沿革,保留不动;如需追溯可查本单)
工单 #86
2026-09-14 09:29:41 +08:00
c361e73a29
fix(web): #85 处理线配色撞车(#10b981 与「接待数」柱同色)
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 3m56s
backend-test / frontend-lint-test (push) Successful in 27s
## 问题(生产实测像素采样发现)
用 canvas 像素采样验证两序列是否真渲染时发现:
`#10b981` 被**同时**用于「接待数」柱与「投诉处理」线
→ 同屏出现两根同色"绿色",使用者无法区分是接待还是投诉处理
→ 与 #85「拆语义」的目的**自相矛盾**(分开了名字却撞了色)
像素计数:绿色 59027 像素,无法归属(既有接待柱又有处理线)。
## 修法
处理线改用 **teal-700 `#0f766e`** —— 迭代自检确认与全图现有色
(#3b82f6 会话 / #10b981 接待 / #f59e0b 登记 / 四路退款 / 三源投诉)**均不撞**。
两页统一(`COMPLAINT_COLORS` 常量 + OperationDashboard 字面量,注释互相指引)。
## 教训
「改配色」不能只靠肉眼 —— 本项靠**像素采样**才发现同色,
否则会带着"看起来也没啥问题"上线(实际上使用者分不清)。
门禁: eslint 0 error / build ✓
工单 #85
2026-09-14 08:28:20 +08:00
8e386878a4
fix(dashboard): #85 趋势投诉柱口径修正(含卡商) + 拆分登记/处理
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
用户反馈:「趋势分析的投诉堆砌柱 能不能区分登记和处理?现在混杂 感觉不实用」
## ★ 排查发现两个比"混杂"更严重的问题
### 问题 1:今日值班看板趋势柱**含卡商**(违反 #53)
原实现 `analytics.py:3686` 的 `cp_map` 叠加 `CardComplaint` 计数,
而用户 #53 明令「(外)卡商 不纳入总量的管理,全部单算」。
### 问题 2:这是 **#81 的漏改** —— 同页自相矛盾
#81 统一了**核心指标**(剥卡商、扣重叠),但**趋势柱未一起改**:
```
修复前 核心指标 1111 vs 趋势柱加总 1127 → 差 16(含卡商 + 未扣重叠)
修复后 核心指标 1111 == 趋势柱加总 1111 → 一致 ✓
```
且两页口径**本身不一致**:运营分析看板是纯登记(1111),今日值班看板是混合(1127)。
## 修复 1:趋势柱口径统一到 #81(登记口径)
- 按三源分桶逐桶统计(判据 `_complaint_source_type_case` 同源)
- **剥卡商**(#53)
- **315 补充表只加净新增**(总数 − 已 matched 重叠,不双算)
- ⚠️ 按分桶而非直接调 `_sources_reg_handled`:后者是**窗口汇总**,
而趋势需**按桶拆分** → 判据同源、粒度不同
## 修复 2:拆分「登记/处理」(用户选定 柱+叠线)
- **柱 = 登记**(`register_time` 落桶,与四卡主值同源)
- **线 = 处理**(`finish_time` 落桶 + 真实完成≥3min,与四卡副行同源)
- 两序列**同 Y 轴**(都是"条",同量级;用次轴会误导对比)
- 图例/ⓘ 明确写「投诉登记」「投诉处理」,**不再出现"投诉量"**(避免第三种语义)
### ★ 关键取舍:处理线**不限制** register_time 也在窗口内
实测(生产 09-01~09-13):若加上该限制,处理量从 **1044 掉到 961**
→ **丢掉 83 条「窗口外登记、本窗口办结」的历史单**。
而"清掉多少历史积压"**正是主管要看的数**(#82 账龄分档显示 284 条 >90 天,
办结它们才是有价值的信号)→ 故按 finish 落桶,含往期单办结。
读数语义:**柱 > 线 = 在累积;柱 < 线 = 在消化**。
## 两页都改(用户选定,保持口径一致)
- `Today.vue`:投诉序列 → 柱(登记) + 线(处理);移动端合并函数同步补 `complaint_done`
- `OperationDashboard.vue`:三源堆叠柱 + 处理线
## 回归防线(新增 4 用例)
`tests/test_complaint_overview.py::TestTrendComplaintSameSource`
① 趋势柱加总 == 核心指标投诉数(#85 核心验收)
② 必须返回 complaint_done 序列
③ 处理序列不得因 register 窗口限制而趋近 0(防误加限制)
④ 卡商不入趋势柱(#53 显式断言)
## 实测(本地,有数据窗口)
```
后端 08-01~08-31 柱(登记)=7455 核心指标=7455 ✓ 线(处理)=7550(在消化)
浏览器 今日值班看板 08-15 登记柱=210 处理线=168 图例=[…,投诉登记,投诉处理]
浏览器 运营分析看板 08-01~08-15
柱 = 内部2873 + 商户437 + 外部222 = 3532
核心指标卡 = 3,532条 ← 逐位一致 ✓
线 = 3576(> 3532,在消化)
图例 = [内部投诉, 商户号投诉, 外部投诉, 投诉处理] ← 无"投诉量"混语义
```
## 门禁
ruff ✓ / pytest **1112 passed, 42 skipped**(+4)/ eslint 0 error / build ✓
工单 #85(修 #81 漏改的趋势层 + #53 违反点)
2026-09-14 08:25:26 +08:00
c81334de72
fix(web): #83 跟进 — 退款笔数卡补接水位(后端已返回、前端漏传)
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 3m43s
backend-test / frontend-lint-test (push) Successful in 26s
## 问题
后端 `today_kpi.metric_levels` 已返回 `refund_count` 的 green 级,
但前端只有挽回卡传了 `metricLevel` → 退款笔数卡水位**静默不渲染**。
★ 与上次同一类坑:**props 传 computed/可选链写漏 = 静默失败**。
## 修法
抽 `metricLevelByCode(code)` 统一取值入口:
· 避免每张卡各写一遍可选链(写错即静默不渲染)
· #83 起接入多指标,统一入口便于扩展
KpiCard 的 `targetText` 同时修正:优先"优秀线",其次"告警线"
(lower_better 指标通常无优秀线,展示告警线更能回答"超没超线")。
## 实测(生产单日 2026-09-12)
```
退款笔数 267笔 A直退130 B取现76 C闪猫0 D手动61 ← 130+76+0+61=267 ✓ 括弧自洽
```
(水位徽标待本次部署后可见:267 < 400 → green 达标)
门禁: eslint 0 error / build ✓ / vitest 62 passed
工单 #83
2026-09-14 07:55:54 +08:00
830f328bf3
feat(dashboard): #83 跟进 — 水位扩展到 refund_count(阈值可信后)
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
#83 治理了阈值错配 + 修正 refund_count 口径后,该指标已满足**双重准入条件**:
① 口径同源:#83 已改为四路合计(`_cash_routes_total`),与看板「退款笔数」一致
② 阈值可信:400/460(依据近90天四路分布 p90=403 / p95=451)
故 `today_kpi.metric_levels` 由「仅 recovery_rate」扩展到 2 个指标。
## ⚠️ 刻意**不接**的三个指标(原因各不相同,已在代码注释固化)
- `ticket_backlog`(存量 585)—— 与看板「积压待处理」**窗口口径不同**
(看板=近30天窗口,指标=全量);存量指标在水位卡上意义有限
(看趋势应用 `ticket_backlog_net`)→ 不在核心指标条展示,避免口径混淆
- `internal_complaint_daily` —— 口径是**内部单源**,看板「投诉数」是**三源合计**;
同排会让人误以为应相等(语义不同,不并列)
- `external_complaint_7d` —— 是**滚动 7 天**窗口,与看板窗口语义不同
★ 这正是不接的理由:**放同一排的指标必须是同一语义**,
否则使用者会去对账、发现对不上、进而怀疑全部数据(#81 的教训)。
## 实测
```
recovery_rate level=null(多日窗口不判定) target=40 warn=30 crit=20
refund_count level=green warn=400 crit=460 desc="四路经营合计…与看板同源"
```
ruff ✓ / pytest 1108 passed, 42 skipped
工单 #83
2026-09-14 07:53:32 +08:00
022a2cd686
fix(metrics): #83 阈值错配治理 + refund_count 口径同源 + 新增积压净增
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
## 背景:3/5 指标「每天恒报 critical」→ 告警机制实际已失效
生产实测(近90天分布):
refund_count(旧口径 refunds 单表) p50=673 旧阈值 30/50 → 13.5x
internal_complaint_daily p50=135 旧阈值 8/15 → 9.0x
ticket_backlog p50=330 旧阈值 20/50 → 6.6x
→ 使用者会学会忽略告警,比没有告警更糟。
## ★ 先更正我自己的错误判断(已在工单 #83 记录)
建单时我用**单日值**算倍数(不严谨),且有两处归因**说错了**:
❌ internal_complaint_daily「口径不同源」→ 它叫「**内部**投诉量」,本就该是内部单源
❌ ticket_backlog「口径不同源」→ 全量口径自洽(585 ≈ #82 的 aging.total 586)
✅ refund_count「口径不同源」→ **成立**,是真 bug
## 一、refund_count 口径修正(真 bug)
```
旧:refunds 全表 count(含 无需退款/驳回 全部状态)→ 673/日,本质是「退费单登记数」
新:四路经营合计 A+B+C+D(复用 _cash_routes_total)→ 277/日,与看板「退款笔数」同源
```
★ 同源而非"各修各的"—— #81/#65 的教训就是口径漂移。
## 二、3 个阈值重设 → 异常检测语义(用户 2026-09-14 拍板)
**预警=p90 / 告警=p95**(显著高于历史常态才报):
refund_count 400 / 460 (四路 p90=403 / p95=451)
internal_complaint_daily 220 / 290 (p90=216 / p95=282)
ticket_backlog 420 / 450 (p90=419 / p95=431)
recovery_rate(30/20/优秀40) 与 external_complaint_7d(50/70) 经复核**合理,不动**。
## 三、新增 ticket_backlog_net(用户决策:存量看趋势)
「存量指标看『是否在恶化』比看『绝对水位』有意义」
口径 = 滚动7天 新增投诉工单 − 真实结案(finish−register≥3min)
★ 滚动7天而非自然周:周初必然接近0,日间不可比(会误判)
实测分布 p50=6.5 / p90=149 / p95=180;当前值 **-27(在消化)** → green
阈值 149 / 180(另设优秀线 -50 = 明显在消化)
⚠️ 与 ticket_backlog **互补不替代**:前者=趋势,后者=水位(结构见 #82 账龄分档)
## 四、迁移(c4d5e6f7a8b9)
- 重设 3 阈值(关闭旧版本留痕 valid_to,可回溯,与 PUT /metrics/{code}/thresholds 同行为)
- seed 新指标 + 阈值 + alert_rule(漏 alert_rule 则引擎空转 —— 历史缺陷)
- 同步 alert_rules 阈值副本(不同步会导致预警中心显示旧数字)
- ★ 旧 refund_count 快照 detail 标注「不可比」(口径已变,防回溯误读)
## ★★ 迁移踩坑(重要)
首次用了 `b8c9d0e1f2a3` 作 revision ID —— **与既有 create_shanmao_refunds 撞号**
→ alembic 报 `Cycle is detected in revisions`,**整个迁移链全部无法执行**
(不只是本次)。已改为 `c4d5e6f7a8b9` 并验证 `alembic heads` 恢复单一 head。
**教训:新建 migration 前必须 `ls alembic/versions/ | grep <新ID>` 查重。**
## 回归防线(新增 5 用例 tests/test_metric_same_source.py)
① refund_count 值 == 四路合计(逐位一致)
② detail 必须声明口径来源
③ 指标描述必须标注 #83(防回退到单表)
④ 阈值不得与真实量级差数量级(比值 ∈ [0.1,10],相对判据不硬编码数字)
⑤ get_threshold 必须取当前生效版本
★ 用**相对判据**而非硬编码阈值 —— 阈值是业务决策,调整后测试不应失败。
## 验证
本地实测(migration 生效后):
```
refund_count 0 green warn=400 crit=460
ticket_backlog 441 warning warn=420 crit=450 ← 441 略超 p90,温和预警(合理)
ticket_backlog_net -27 green warn=149 crit=180 ← 在消化
internal_complaint_daily 0 green warn=220 crit=290
```
从「3 个恒 critical」→「常态 green + 1 个温和预警」。
门禁: ruff ✓ / pytest **1108 passed, 42 skipped** / vitest 62 / eslint 0 error
(`ruff check alembic/` 有 63 个存量 I001/F401,非本次引入、项目门禁不含该目录,未顺手改)
工单 #83
2026-09-14 07:50:38 +08:00
2c7d82dfbb
feat(dashboard): #82 运营看板补可控指标 — 目标达成水位 + 积压老化分档
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Successful in 3m40s
backend-test / frontend-lint-test (push) Successful in 1m3s
从管理角度梳理时发现的核心缺口:该页几乎全是「后果指标」(多少笔、多少钱),
缺「可控指标」—— 只能说明"发生了什么",无法回答"够不够 / 该不该管"。
违反 AGENTS.md「可控指标前置」铁律。
## ① 积压老化:现有卡片**漏掉 89% 的积压**(不只是"加个图")
现「积压待处理」只统计**近30天窗口内**登记的未完成单。
实测全量:
0-7天 17 | 8-30天 29 | 31-90天 104 | >90天 286(最老 910 天)= 436
而卡片只显示 46 条 → 只看窗口会让人误判「积压可控」,
实际 286 条陈年单在烂 —— **那才是真正需要决策的**。
实现:`/complaints/summary` 新增 `backlog.aging`(四档 + oldest + max_age_days)。
★ 取"新增"而非"改现有卡口径":两者含义确实不同
(近30天=近期消化压力;全量分档=历史包袱),并列保留 + 互相声明,
避免改口径造成数字变动争议。新增 `backlog._spec` 解释「为什么两个数不一样」。
⚠️ 口径声明:**不含 (外)卡商**(#53)。
## ② 目标达成率水位
`today_kpi` 新增 `metric_levels` —— 复用既有 metric_thresholds 阈值引擎
(不新建机制)。前端 KpiCard 新增徽标(达标/预警/告警 + 目标线文字)。
★★ 关键取舍:**只接了 recovery_rate 一个指标**
为确认"阈值能不能接",在生产上跑 `evaluate_metric()` 全量实测,发现:
refund_count 实际 314 / 阈值 30-50 → **差 10 倍**
ticket_backlog 实际 599 / 阈值 20-50 → **差 30 倍**
internal_complaint_daily 实际 123 / 阈值 8-15 → **差 15 倍**
且三者口径**与看板卡不同源**(refund_count 只算 A 路单表,看板是四路合计)。
→ 若强行接上,看板会显示「退款笔数严重告警」(其实 314 很正常),
**比不显示更糟** —— 会训练使用者忽略告警。
故只接双重验证过的 recovery_rate(口径同源 ✅ 阈值 40/30/20 与
Today.vue 既有业务规则吻合 ✅ ),其余明确不接(宁缺勿错)。
已开 **#83** 治理该阈值错配 + 口径对齐。
另一个保守取舍:**只在单日窗口判定达标** —— 阈值按【日】配置,
拿日目标判周/月数据会持续虚警 → 多日窗口显示「不判定」并说明原因
(而非默默当作达标)。未配阈值则不渲染徽标(不编造目标线)。
## 回归防线(新增 2 用例)
tests/test_complaint_overview.py::TestBacklogAging
① 四档之和 == 未完成总数(完备性)
② summary 必须返回 aging + _spec(且四档和 == total)
★ 踩坑记录:complaint_summary 带 @cached_json,测试内多用例同窗口会**串数据**
→ 必须 `cache_invalidate_pattern("complaint:summary:*")`(照 test_issue75 做法)。
另一坑:本地曾只清 `today:kpi:*`,`complaint:summary:*` 旧缓存导致前端不渲染 ——
口径变更后**两类缓存都要清**。
## 门禁
ruff ✓ / pytest **1103 passed, 41 skipped** / vitest 62 / build ✓ / eslint 0 error
## 浏览器实测(单日 2026-09-01 窗口)
```
挽回率卡 25.13% [预警] ≥40%
水位色 rgb(202,138,4) ← 黄色(预警级,与阈值 30-20 一致)
积压账龄 共 423 条未结 · 最老 910 天
0-7天 17 | 8-30天 29 | 31-90天 93 | >90天 284
提示「C/D 档(>30天)共 377 条,需决策:清理/关单/转专项」
周视图 显示「不判定 · 目标线按【日】配置,当前窗口为多日」✓
```
工单 #82(#83 为发现并独立的阈值错配治理单)
2026-09-13 17:17:40 +08:00
63cf1f9f53
fix(complaint): #81 投诉数口径统一为单一出口 — 核心指标 ≡ 三源合计
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
诉求 2(#80「投诉括弧三源」)的前提。修正前同页两个投诉数**必然对不上**,
使用者无法判断该信哪个 → 直接损伤数据可信度。
## 范围更正:原第 3 项已不存在
建单时列的「#65 黑洞(188 条工信部工单消失)」**已修**(#65 已 closed)。
实测近3月工单侧分桶:内部 11093 + 商户号 1803 + 外部 355 + 卡商 23 = 13274
投诉类工单总数 = 13274 → 差 0,完备无遗漏。
软纠正已下沉到 complaint_scope.complaint_bucket_case(..., sn_col),
统计层与对账层共用同一段 CASE,不可能再漂移。→ 该子项作废。
## 根因(实测每月差 37 条)
`analytics.today_kpi` 原先**另写一套**:
工单投诉类 + external_complaints 全表 + card_complaints 全表
两个缺陷:
① **违反 #53**:卡商被并入投诉总量(用户明令「卡商不纳入总量,全部单算」)
② **重复计数**:315/卡商表里已 matched_ticket_no 关联到工单的记录,
工单侧已算过,表侧又加一次(本月实测 315 重复 22 条)
而投诉专项四卡走 `_sources_reg_handled`(分桶 + 表侧扣重 + 卡商剥离)
→ 两套实现,数字必然不等,且**核心指标口径是错的一方**。
## 修复
`today_kpi` 改为**直接调用** `complaints._sources_reg_handled`(单一出口):
· 判据统一 = complaint_scope 分桶(含 channel 回退 + #58 软纠正)
· 表侧扣重 = 315 只加「净新增」
· 卡商剥离 = 不入总量(独立区块承接)
· 惰性导入(complaints 已模块级 import analytics → 避免循环依赖)
`_sources_reg_handled` 新增 `include_suppl_external` 参数(对齐
analytics._INCLUDE_COMPLAINT_FILES 开关语义,改一处不影响另一处)。
`_spec.complaint` 补口径说明(前端 ⓘ 直接引用,禁止前端另编)。
★ 为什么用「同源」而非「修一遍两套」:#65 的教训正是口径漂移 ——
软纠正只落在对账侧,统计侧漏了 160 条真实投诉。同源是唯一可靠做法。
## 前端(#80 诉求 2 落地)
- 投诉数卡解锁「括弧三源」chips(内部/商户/外部)+ 完整 ⓘ
实测 92+6+16 = 114 = 主值 ✓,与四卡一致 ✓
- 三源合计提示条从「不一致告警(黄)」改为「一致确认(绿)」
- ★ 踩坑:props 传 computed 必须显式 `.value` —— 在另一个 computed 内
Vue **不会**自动解包 → `chips: complaintChips` 恒为 undefined → 静默不渲染
(实测发现,已注释固化防复发)
## 回归防线(新增 4 用例)
tests/test_complaint_overview.py::TestComplaintTotalSingleSource
① today_kpi.complaint == _sources_reg_handled.total(逐位一致)
② 三源之和 == total(保证括弧可加)
③ 卡商不得进入投诉总量(#53)
④ _spec.complaint 必须写明卡商不含在内
★ 把「相等」固化成测试 —— 若将来有人再另写一套,立刻失败。
## 门禁
ruff ✓ / pytest **1101 passed, 41 skipped** / vitest 62 / build ✓ / eslint 0 error
## 实测(重建镜像后,三期满一致)
```
本周 核心= 114 三源= 114 一致 {内部92, 商户6, 外部16}
本月 核心= 1111 三源= 1111 一致 {内部888, 商户135, 外部88}
近3月 核心=13678 三源=13678 一致 {内部11096, 商户1804, 外部778}
```
浏览器实测: complaintChips=[内部 92, 商户 6, 外部 16] chipSum=114 matched=true
工单 #81
2026-09-13 16:41:41 +08:00
7bfc951ca7
fix(web): #80 运营看板梳理 — 命名正名 + 布局硬伤 + 四卡/趋势/挽回改造
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
用户反馈两页命名疑似反了 + 5 条梳理诉求。核查后确认:命名确实错位,
且「3个数字太占空间」「4个指标占4大行」不是审美问题,是 2 个真 bug。
## 命名正名(仅显示名,path 全不动)
- /dashboards/today → 「今日值班看板」(原「决策分析看板」,属过度拔高:
该页无归因/目标达成/异常分解能力,实质是值班台 + 实时数据)
- /dashboards/operation → 「运营分析看板」(原「运营看板」,才是真正的分析页)
- 同步 6 处:menu.js / router meta.title / 两页 pageTitle / Dashboard.vue 4 处快捷入口
- 修 Dashboard.vue 存量错位:入口名「运营看板」+描述「日/周/月/季度」+跳转 today(三者打架)
## 硬伤 A: el-col 缺响应式断点 → 桌面端每列占满整行
OperationDashboard 退款挽回 3 卡、投诉源 4 卡用 `:xs="8"` 缺 `:sm/:md`
→ Element Plus 默认 span=24 → 桌面端一列一行。这才是用户所说
「3个数字太占空间」「4个指标占4大行」的真实根因。实测修复后 4 卡同行。
## 硬伤 B: 明细硬截断无提示
complaintDetail 固定 limit=500,超出静默截断,使用者无法分辨
「业务就这么多」还是「被截了」→ 违反看板规范「空必须可解释」。已加显式警告。
## ★ 硬伤 C(排查中新发现,影响最大): SectionHeader 插槽名不匹配
SectionHeader 只支持 `suffix` 插槽,但 OperationDashboard 有 6 处写 `#extra`
→ **全部静默不渲染**,实测确认 5 个控件完全失效:
· 核心指标的 ⓘ 数据说明
· 趋势分析的「接待/投诉/退款」勾选框 ← 这正是「趋势图看不到退款/投诉」的原因
· 人效表部门/分组/按人筛选
· 退款挽回 / 投诉专项 跳转按钮
已给 SectionHeader 新增 `extra` 插槽(右对齐,不改 suffix 语义)。
## 诉求 1: 退款笔数括弧 ABCD
KpiCard 新增 chips/subRows 两个能力(复用现有紧凑布局,主值字号不变)。
退款笔数卡 = 四路笔数 chips;退款金额卡 = 四路金额 + 实际现金流 E 对照。
口径:ABCD 是经营记账(含退入钱包非现金退出),E 才是真实现金流出,**
两者不等属正常**,已在 tip 说明。
## 诉求 3: 趋势图堆叠
- 退款 → A/B/C/D 四路堆叠柱(复用现成 refund_count_a~d,无后端改动)
- 投诉 → 内部/商户号/外部 三源堆叠柱(复用 summary.*.trend)
- 金额由面积线改为**半透明堆叠柱**(原与笔数柱互相遮挡)
- 取舍:金额不参与"合计"语义;投诉三源仅按日粒度可拆(补充表 trend 只到日),
周/月自动退化为单一柱 —— 宁可少拆,不可拆错
## 诉求 4: 退款挽回
- 3 个大卡 → 1 行内联条 + 成功/失败 100% 堆叠比例条(原完全没有占比信息)
- 「为 0 不显示」做**区分处理**:隐藏 total=0(本期无任务=噪声),
但**保留 total>0 且成功=0**(接了活全失败 —— 正是最该被看见的),
并标红「全失败」+ 置顶;标题显式告知「已隐藏 N 名」
- 取舍:不做按原因堆叠(staff_details 无 reason 维度,强拼会算错)
## 诉求 5: 投诉四卡紧凑化 + 防误加
- 四卡改 4 列(:xs=12 :sm=6 :md=6),桌面同行
- 新增「三源合计(不含卡商)」提示条 + ⓘ 解释**为什么与核心指标投诉数不等**
## 诉求 2 延后(阻塞)
核心指标「投诉数」含卡商(违反 #53)+ 与投诉专项判据不同源 + #65 黑洞
→ 单独开 #81 完整修。修正前**不**加「括弧三源」,否则括弧之和≠总数会误导。
## 三层落地(界面设计说明规范)
① 代码层:OperationDashboard.vue 文件头五要素设计说明注释块
② 页面层:核心指标/趋势/挽回 ⓘ 含来源·口径·时点·「空」的含义·**取舍**
③ 审查层:满足 review-checklist / 看板卡片规范
## 门禁
eslint 0 error(5 个存量 warning 非本次引入)/ vitest 62 passed / build ✓
## 浏览器实测(生产级本地,注入签名 token)
refundChips=[A直退0,B取现67,C闪猫0,D手动0]
refundSubRows=[...4路金额+实际现金流E]
srcCards 四卡 top 全等 = 2544(同行 ✓)
recCells 三格 top 全等 + stackSegs=31%/69% ✓
recHeader「4 人,已隐藏 6 名本期无挽回任务者」✓(全失败者未被误藏)
trendLegend=[会话数,接待数,内部投诉,商户号投诉,外部投诉,A线上直退,B钱包取现,C闪猫退款,D手动退款]
pageTitle=运营分析看板 / 今日值班看板 ✓
工单 #80
2026-09-13 16:23:43 +08:00
c41a0c889a
feat(shared-doc): 版本判据改用内容哈希 digest + 新增判据对照验证脚本 ( #79 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 32m51s
backend-test / frontend-lint-test (push) Successful in 1m32s
对照实验已发现 version 判据会**虚警**:
version 6808→6811→6812 期间, 导出内容指纹完全一致(4201/4202 行, sha256 相同)
→ 说明 version 是计数器, 会被非内容变更(打开/预览/权限等) bump。
改用金山 API 返回的 file.digest (digest_type=sha1):
哈希特性保证「内容变 → digest 必变」, 更贴合「内容是否变化」语义;
实测字段: file.digest=86552f22… file.size=120540267
digest 缺失时回退 modify_time/version(仍可用, 只可能虚警, 不会漏报)。
新增 scripts/probe_verdict_check.py: 判据对照验证工具。
⚠️ 关键设计: 默认模式「版本未变则不导出」**只能发现虚警, 发现不了漏报**;
必须用 --always-export 才能测到最危险的「内容变了但版本没变」。
故开启开关前的验证必须用 --always-export。
新增 2 个用例(digest 优先 / 无 digest 回退 version)。
2026-09-13 15:07:15 +08:00
7a8689d13a
refactor(shared-doc): 版本探测优先走金山官方 JSON 接口(0.21s), HTML 降为兜底 ( #79 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
实测发现官方接口比解析文档 HTML 更可靠:
GET https://www.kdocs.cn/api/v3/office/file/ <file_id> (带 cookie)
→ 0.21s 返回结构化 JSON: file.modify_time / file.version / file.modifier.name
原实现解析文档 HTML 页内嵌 JSON(0.6s) —— 那是前端数据, 易随改版失效。
现改为: 官方接口为主 → HTML 解析兜底 → 两者皆失败返回 None(照常导出)。
并记录最后修改人姓名, 便于排查「谁改了导致重采」。
新增 3 个用例(优先接口 / 接口失败回退 HTML / 非 /l/<id> 链接形态)。
2026-09-13 15:01:34 +08:00
ab80623a44
perf(shared-doc): 新增文档版本探测闸门(默认关闭)+ 跳过状态显式标注 ( #79 )
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Successful in 42m28s
backend-test / frontend-lint-test (push) Successful in 1m44s
动机(实测): 一次共享文档同步共 67.3s, 其中**导出占 65s(97%)** ——
playwright 打开文档+走菜单+下载整本 xlsx(103.7MB, 其中嵌入图片 113MB,
而入库完全不用图片列); 解析仅 0.5s、入库仅 1.8s。
即瓶颈是「每次都盲目整表导出」, 不是去重(去重侧 9-08 已优化为内容未变不写库)。
方案: 先用登录态做轻量 HTTP 请求取文档版本标识, 与上次相同 → 跳过导出。
实测(金山): 带 cookie GET 文档页 0.6s 返回, 响应含 modify_time/version,
连续探测稳定; 对比 65s 导出省 99%。
⚠️ 开关 SKIP_EXPORT_IF_UNCHANGED 默认 **false**(保守):
需先验证「文档被编辑后版本号一定变化」才可开启, 否则会静默漏采(比不优化更糟)。
已实测未编辑时版本恒定; 编辑后变化待人工验证。
开关关闭时仍会探测并记录版本号到 summary, 为开启后比做准备。
安全设计(防漏采, 均有回归用例):
- 探测失败/不支持平台/页面结构变化 → 返回 None → **照常导出**(None ≠ 未变化);
- 首次同步无历史版本 → 必须导出;
- 跳过记为 status=(不是 success/partial), 并在 summary 写
skip_reason/doc_version —— 使「文档确实没变」与「没采集到」可区分
(工单 #79 教训: 日志恒 inserted=0 曾被误读为正常无变化, 实为漏采);
- 腾讯平台暂无效信号 → 恒不跳过。
前端: 共享文档采集页新增「未变化·跳过」状态标签与跳过原因展示
(不再显示为 0 行 —— 0 行看起来像故障)。
新增 11 个回归用例。
2026-09-13 14:17:09 +08:00
7c562e5309
fix(shared-doc): 退款登记合并全部「登记明细」sheet,修复 D 路漏采 ( #77 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
现象: D路(manual_refunds) 最近几天没有数据。实际不是没采到 —— 文档里有数据,
采集器读错了页签。
根因(生产实测):
该文档有两张 sheet, 名字都含「登记明细」:
3333 行, 2026-07-01 ~ **2026-09-11**
865 行, 2026-09-01 ~ **2026-09-13** ← 09-12/09-13 在这里
旧实现 _pick_manual_refund_sheet 是「取第一个名字含『登记明细』的 sheet 就 return」
→ 只读到历史表, 月度表**完全采不到**; 且遍历按 Excel 页签顺序(非最新优先),
该源 sheet_rules=[] 为空无法配置 → 同步日志恒为 total=3333/inserted=0/updated=0。
为什么是「合并」而不是「改读月度表」(实测依据):
① 两表并非子集关系 —— 「仅历史表有」9月行 41 行、「仅月度表有」133 行,
二选一必然漏数据, 必须取并集;
② 去重键是规范 §2.2 复合键(登记日期|订单号|设备号|登记人), 入库层
manual_refund_sync._dedup_in_batch 按该键去重(同批次后者覆盖前者);
③ 实测两表同键行(交集 735 行)**逐字段完全一致** → 合并既不产生重复也不改变取值。
影响量化(用留存导出实测):
仅月度表独有的行 133 行 / 12,166.56 元; 其中 09-12 起 66 行 / 7,061.43 元。
注: 09-11 也不完整(库 24 行 vs 文档 62 行), 即从 09-11 就开始漏。
修复: 遍历并合并所有名字含「登记明细」的 sheet(月度表靠后 → 后覆盖前, 与入库层一致);
保留按名字匹配而非位置, 以便自动跟随上游新增的 等。
多 sheet 时打 info 日志(合并可见化, 便于排查同类只读了一张问题)。
新增 4 个回归用例(合并/单表不变/尊重 sheet_rules/复合键幂等)。
2026-09-13 13:53:37 +08:00
dcb014667b
fix(web): 周区间计算抽为单一来源,修复「周日当天本周算成下周」
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
用户反馈「部署了没有」定位到的根因之一:运营看板「本周」指标全为 0。
根因(存量 bug,与 #78 无关,编辑过程中一并发现):
dayjs 的 startOf('week') 周起始是**周日**,旧写法再 .add(1,'day')
会得到**下周一** → 周日当天「本周」被算成 09-14~09-20(未来)→ 指标全 0。
实测:today=2026-09-13(周日)
startOf('week') = 09-13
.add(1,'day') = 09-14 ← 错,本应 09-07
改动:
- 新增 web/src/utils/weekRange.js(周一起始,中国习惯)作为**单一来源**
- OperationDashboard 两处改用(weekOptions + getDateRange),
并 clamp 到今天避免拉取未来日期
- Today.vue 原有正确实现改为复用该模块,消除两份各写一份的漂移风险
- 新增 10 个单测锁死该 bug(含「周日 → 本周仍应是刚过去的周一」回归用例)
验证: vitest 62 passed(新增 10 个)/ eslint 0 error / vite build ✓
本地浏览器实测:运营看板接待数 2,746、会话数 5,628(修复前均为 0)
Refs #78
2026-09-13 13:18:43 +08:00
450059819f
fix(operation): 运营看板补齐 AI 接待分层(#78 遗漏页)
...
#78 只改了「决策分析看板」,遗漏了同样展示「会话数/接待数」+ 效率表的
「运营看板」—— 用户在该页看到的仍是老样子。
改动:
- 新增 web/src/utils/receptionSource.js 作为前端**唯一来源**:来源配色、
堆叠段构造、ⓘ 统一文案。两个看板共用,避免各写一份导致配色/口径漂移
(规范 §五 禁止重复展示)
- KpiCard 组件新增 stack 能力(堆叠比例条 + 图例),运营看板直接复用
- OperationDashboard.vue:
· KPI「接待数/会话数」接入堆叠条 + SectionHeader ⓘ(含「取舍」段)
· 工作效率表过滤 is_virtual(AI 不再混入真人排名)
· 表下新增「AI / 系统账户」单列表(含 ⓘ 与占全站会话占比)
- Today.vue 改为复用 utils/receptionSource,删除本地重复定义
顺带修复:编辑中误删的 `const grain = ref('week')`(本地实测
ReferenceError: grain is not defined)已恢复。
验证: eslint 0 error / vite build ✓ / vitest 52 passed
本地浏览器实测:KPI stackSegs=4、legend 正常、AI 单列表渲染
Refs #78
2026-09-13 13:17:15 +08:00
3b92b91d8c
feat(analytics): AI 接待口径分层 + 系统账户单一来源 ( #78 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
客服系统增设 AI 客服后,cs_records 中出现大量非人工流水,但统计口径
全部按人工接待计算,导致人效榜被 AI 账户占据、会话数/接待数双双失真。
根因(生产实测 2026-09-13):
- analytics.py 内并存两份系统账户清单且互相不一致,两份都漏 WeChat
- _EMPLOYEE_ID_PATTERN ^[A-Za-z][A-Za-z0-9_-]{1,11}$ 把 WeChat 判成
「员工工号」→ is_virtual=false → 人效榜 rank=1、work_score 2764.6
- today_kpi 的接待量/在岗人数无任何 operator 过滤
改动:
- 新增 app/services/system_accounts.py 作为系统账户**唯一来源**
(合并原 SYS_OP_BLACKLIST 与 SYSTEM_VIRTUAL_EXACT),并新增
SourceKind 四分类器:human / ai_auto / ai_system / customer_self
- 修 _EMPLOYEE_ID_PATTERN 误判:在「工号格式」判定之前加词干否决
(wechat/sys/ai/bot...),WeChat 现判为虚拟账户
- today_kpi 新增 reception.breakdown(四类分层,含不变式
各层之和 == 会话数)与 ai_takeover(AI 接管率,可控指标前置)
- today_staff 新增 source_kind/is_virtual,并把 AI/系统账户**单列**
(此前被 SQL 黑名单直接排除,AI 工作量完全不可见)
- 7 处 SQL 内嵌黑名单统一为含 AI 账户的完整清单
- 前端 KPI「接待数/会话数」加堆叠配色条 + 图例 + ⓘ 数据说明
(含「取舍」段);效率表下方单列 AI/系统账户表
取舍(已写入代码注释 + 页面 ⓘ + 架构文档):
- 不做「AI 转人工」细分 —— 平台无稳定标记列,BMS/智能客服仍在迭代,
现在做会返工(用户明确本期不做)
- 不删 AI 记录,只分层 —— AI 接管率本身是可控指标
- 真人名下的「系统标记」行仍计入真人(实测过半与真人操作同秒,属连带
记录;拆出会虚减人工工作量 18%)
架构: docs/arch/A-20260913-001-AI接待口径分层.md
验证: pytest 1078 passed / ruff 0 error / eslint 0 error /
vitest 52 passed / vite build 通过
浏览器实测: 堆叠条真人76.1% AI0.2% 系统23.2% 自助0.4%;
AI 单列表 WeChat|AI全自动|3次|1个|0.3%
Refs #78
2026-09-13 12:54:37 +08:00
9bb00e879a
fix(shared-doc): 二维码判据要求图片真加载,修正「容器在但图空」假阳性 ( #49 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 4m9s
backend-test / frontend-lint-test (push) Successful in 48s
生产验证发现上一版修复仍不够: start() 返回的截图里二维码区域是空白。
实测定位: 点击 Sign in 后 (180x180) 容器**立刻存在**,
但内部 <img id='miniprogramImportImg'> 此时**还没有 src / 未加载完** ——
该区域像素 100% 纯白(灰度种类=1)。约 1~3s 后才加载成功(src=qrcode.qwps.cn,
naturalWidth=430)。
上一版判据只看容器尺寸 ≥80px → 等待提前通过 → 截图仍是空白二维码。
修正: 判据必须要求 img.naturalWidth > 0(图片真加载成功的标志);
canvas 型(腾讯文档)保持按尺寸判定。
教训: 「元素在 DOM 里」≠「内容已渲染」。尺寸判据对异步图片天然不可靠。
新增 2 个回归用例锁定该判据。
2026-09-13 12:36:30 +08:00
a46dc82dc5
fix(shared-doc): 修复 kdocs 扫码「空框」— 等渲染 + 自动展开二维码 + 判态修正 ( #49 )
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
生产现象: kdocs 弹窗显示一个空白框, 状态却显示「已扫码,待确认」。
生产容器实测时间线定位根因:
goto(wait_until=domcontentloaded) 0.68s 就返回
t+0.8s popup=0 非白像素=0.00% ← start() 正在此截图 ⇒ 纯白空图(即「空框」)
t+3.0s popup=1 非白像素=100%
t+6.1s 弹窗内容才渲染出来
→ 截图拍得太早。
第二个独立问题(更实质): kdocs 弹窗**默认没有二维码**。
初始只有 'Open the document with WPS' / 'Sign in' 两个按钮, qrcode 节点数 = 0;
必须点击 Sign in 才出现 .qrcode_img_block 180x180 真实二维码。
前端弹窗是静态截图, 用户点不到该按钮 → 不代点就永远扫不到码。
第三个问题: 判态错误。_SESSION_COOKIE_HINTS['kdocs'] 含 'csrf', 而 csrf
在登录前就存在(实测 cookie 13 个即命中) → 页面仍停在登录弹窗却判 authorized,
前端显示「已检测到登录」误导用户。
修复:
- goto 后等待 3.5s 让 SPA/弹窗真正渲染, 再截图;
- 新增 _open_qrcode_if_needed(): kdocs 无二维码时自动点击登录按钮并等二维码就位
(选择器 .wlcp-btn:not(.is-highlight); 实测 button:has-text 会超时无效);
仅 kdocs 执行, 腾讯默认展示二维码不受影响;
- 判据改为「看得到二维码 == 一定还没登录」, 并剔除 cookie 白名单里的 'csrf';
- verify_logged_in 同样等渲染并新增二维码判定(用户没扫成功就点确认 → 不覆盖旧态)。
新增 6 个回归用例(含 3 个单测全绿但线上仍坏的场景)。
2026-09-13 12:33:53 +08:00
ad7eea47e1
fix(db): cs_records 唯一约束补 NULLS NOT DISTINCT + 清理历史重复行
...
唯一键 8 列中 problem_create_time 大量为 NULL,而 PostgreSQL 唯一约束默认
NULLS DISTINCT —— 任一列为 NULL 该行就不参与去重,重复行可无限插入。
实测 2026-09 重复率 47%,单秒同一 SN 最多 64 行。
- 迁移 a1c2e3f4d5b6: 先幂等清理历史重复行(保留每组 id 最小),再重建约束为
UNIQUE NULLS NOT DISTINCT(PG 15+),NULL 视为相等值参与去重
- 模型/upsert 补注释,说明 on_conflict_do_nothing 依赖该约束语义,
若改回默认会静默失效
生产已于 2026-09-13 手工清理 65328 行(备份 /tmp/cs_records_pre_dedup_*.sql):
375315 -> 309987 行,业务键差异归零;7月数据未受影响(91914 行不变)。
Refs #77
2026-09-13 12:32:51 +08:00
de48401bdc
fix(complaint): 卡商分类 limit 截断修复 + 前端长尾聚合 + 数据说明 ( #75 #76 )
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
核验中发现并修复的真实缺陷(自检发现,非用户报告):
- 后端 limit=20 沿用了三源写法, 但卡商分类实测 **25 种**(自由文本长尾)
→ 截断 5 类, 造成「各分类之和 < 总数」的假象(实测 65 vs 70)
→ **静默丢数据**, 且使用者无法察觉
- 修复: limit 提到 50 覆盖全集; 长尾聚合改在**展示层**做
(Top12 + 「其他(N 类)」保留真实合计) —— 后端截断无法察觉, 展示层聚合可见
其他:
- 分类卡补 ⓘ 数据说明(来源/口径/时点 + "空"的含义), 落实 #76 规范 §二
- tooltip 显示占比(仅数量看不出集中度)
- 新增回归 test_more_than_20_categories_not_truncated
验证: 本地 sum(83)==窗口总数(83) ✓; 生产 DB 校验 71 行/25 类
测试: 后端 1026 passed/41 skipped/0 failed (新增 1); 前端 52 passed; eslint 0
2026-09-13 12:21:16 +08:00
3ba79bb34f
fix(dashboard): 卡商分类空白修复 + 决策分析看板下钻 + 看板卡片规范 ( #75 #76 )
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
#75 卡商渠道「卡商投诉分类」图永久空白
- 根因: complaint_summary 的 card.by_category 硬编码 [], 注释指向的
complaint_subs 接口**从未存在**(死引用); 前端只读 by_category → 恒空
- 修复: 聚合 card_complaints.problem_category (按 complaint_date 落窗口,
NULL→未标注); by_handler **保持空**(有意设计: handler 混合自由文本,
画排行即误导) 但注释改为显式声明, 防后人"顺手补上"
- 验证: by_category 0→20 项; 浏览器 canvas=true,empty=false
- 测试: tests/test_issue75_card_by_category.py (6 passed)
#76 首页看板改名「决策分析看板」+ 卡片补齐下钻 + 新规范
- 改名: menu.js/router meta.title/Today.vue pageTitle 统一为「决策分析看板」
(仅显示名, path /dashboards/today 不动)
- 下钻: KPI 卡(7) + 投诉来源卡 + 卡商卡 + 积压水位 + 退款挽回(4格) 全部可点,
**带时间窗口与筛选参数**(规范禁止落到默认窗口)
- 落点: ComplaintAnalysis.vue 新增 query 接收(start/end/tab/view/source),
并把时间条同步切到「自定义」回填区间 —— 否则控件仍显示近30天,
数字与标签对不上比不做更糟
- 趋势图: 显式声明**不**做点内下钻(明细接口按日筛选, 无小时级), 落实规范 §3.4
- 新规范: docs/standards/看板卡片数据说明与下钻规范.md (META, 强)
已挂 AGENTS.md 引用链 + docs/README.md §六 + review-checklist
顺带: 修复 2 个过期测试 (test_ai_time_parser.py) —— _AiWindow 走真实时钟,
断言却写死 2026-09-09~11, 到 09-13 必然失败; 改为动态断言。
git stash 验证已确证与本次改动无关。
测试: 后端 1024 passed/41 skipped/0 failed; 前端 52 passed; build+eslint 0 error
2026-09-13 12:16:26 +08:00
3f821560a1
fix(shared-doc): 用 Xvfb -ac 直启替代 xvfb-run 包裹,修正 xauth 阻塞 ( #49 )
...
gitleaks / gitleaks (push) Successful in 7s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
生产端到端验证暴露首版实现的真实缺陷: 首版用 `xvfb-run -n 99 sleep infinity` 包裹,
xvfb-run 默认启用 xauth 保护, 而 chromium 是另一个进程拿不到该 cookie →
实测仍报 "Authorization required, but no authorization protocol specified"
+ "Missing X server or $DISPLAY"。
修正:
- 改为直接拉起 `Xvfb :99 -screen 0 1280x800x24 -ac -nolisten tcp`
(-ac 关闭访问控制; 同容器内使用且不开 TCP, 不扩大暴露面);
- 判据由「socket 文件存在」改为「socket 可连接」—— X server 被杀后 socket 文件
会残留(内核不自动删), 只看存在会把死服务当活的;
- 导出 XAUTHORITY 占位文件, 消除 X 客户端 "~/.Xauthority does not exist" 告警;
- 等待上限 2s → 5s(容器冷启动更稳)。
新增回归用例 test_xvfb_started_with_ac_to_avoid_xauth_error 锁定启动参数防回退。
文档同步更新 ops 手册 §七 与架构提案的取舍/风险/测试三节。
2026-09-13 12:15:33 +08:00
0242508438
fix(shared-doc): 采集页补「命令行」降级入口,消除 501 提示死路 ( #49 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 3m44s
backend-test / frontend-lint-test (push) Successful in 36s
SharedDocSources 页原先没有「命令行」按钮,而 501 提示指向的就是该按钮 →
提示成了死路。本页补上入口,命令统一从后端 /cli-fallback 取。
同时把 501 的 ElMessage 改为可读的引导文案(时长 8s), 不再把长命令塞进消息条。
2026-09-13 12:02:33 +08:00
42a555f3b8
fix(shared-doc): 修复扫码登录显示服务判据与按需拉起 Xvfb ( #49 )
...
生产报 BrowserType.launch: Missing X server or $DISPLAY —— 容器装了 xvfb 包但
没有任何进程启动 Xvfb,$DISPLAY 为空,有头浏览器必然失败。
根因是两层叠加:
1. 部署层只做了「装包」(Dockerfile 加 xvfb+xauth),编排/启动命令没有任何进程
启动 Xvfb,A-20260910-001 §四 的两种方案一条都没落地;
2. 探测判据把「装了没」当成「跑着没」—— 旧判据 $DISPLAY 存在 or which(xvfb-run),
容器装了包必然命中 → 判为可用 → 跳过 501 降级,直接 launch(headless=False)
炸成 502 + Playwright 原始日志。501「强制降级」从未被触发过。
修复:
- 判据由安装态改为运行态(X socket 是否真存在),并区分只读 check_* 与可自愈 ensure_*;
- 真正 launch 前按需拉起 Xvfb(xvfb-run -n 99 ... sleep infinity,幂等,上限 2s),
零常驻开销且不改编排,对旧镜像同样生效;拉不起来才 501;
- 降级命令改为两段式(本机扫码 → scp 上传 /opt/csctalon/browser_states/),
旧命令指向生产容器里不存在的 scripts/ 与 .venv,属假降级路径;
- 命令文本改由 GET /sync/shared-docs/cli-fallback 下发,消除前端硬编码的第二份副本;
- 501 detail 不再内联长命令(消息条读不了),改为指向「命令行」按钮。
测试:按真实生产形态(装了包但起不来)构造 501 用例 —— 旧用例 monkeypatch 掉
which 构造了生产不存在的状态,是 501 长期未被发现的直接原因。新增 9 个回归用例。
前端:AccessCenter 登录态区块与 SharedDocSources 补齐三层设计说明(含取舍段)。
文档:ops 手册新增 §七 共享文档扫码登录与 Xvfb 排查;LOCKED 架构 A-20260910-001
未擅改,按流程提交待审提案 docs/arch/_pending/A-20260913-001。
2026-09-13 12:01:49 +08:00
761d1a0645
fix(data): #64 收尾 — 历史脏 SN 数据清洗脚本(补齐代码已修但数据未清) ( #74 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Failing after 13m8s
backend-test / frontend-lint-test (push) Successful in 33s
## 背景
#64 的**代码修复已完成**(_clean_sn 补 `/`、`|` 分隔符 + 引号剥离),
但**历史 7 行脏 SN 仍在库中** → 永不匹配工单 → 虚假「文档有、工单无」空悬。
## 新增一次性清洗脚本 backend/scripts/fix64_dirty_sn.py
安全性设计(6 层防护):
1. 默认只预览,需显式 --apply
2. 只清洗确定的脏值(正则 ^['"]|/ 命中),不全表扫描
3. **跳过残影行**(dup_sn_backfill)—— #62 要求其 SN 保持空缺,回填会洗白
4. 幂等(二次运行命中 0 行)
5. 原值保留到 quality_note,可溯源回滚
6. 打印实际连接库名,供人工确认(防连错库)
复用 `complaint_sync._clean_sn`(单一事实来源),保证与入库侧口径一致。
## 清洗结果(7 行)
| id | 原值 | 清洗后 | 工单命中 |
|---|---|---|---|
| 2129-2133 | `'70032611013040` 等 5 行 | 去引号 | **1** ← 原为漏匹配 |
| 1161/1405 | `SN1/SN2` 2 行 | 取第一个 | 0 |
★ 关键证据: 5 行清洗后**都能命中工单** → 证明此前是**真实漏匹配**(非工单确实没有)
## 验证
- 315 侧脏 SN: 7 → **0**
- 工单侧脏 SN: **0**(未受影响)
- 残影未被误改: 违规 **0** 行
- 可溯源: 7 行 quality_note 含原值
- 幂等自检: 剩余 **0** 行
关联工单: #74(收尾)· #64(代码修复)
2026-09-13 11:40:33 +08:00
2d1e21dadf
fix(complaint): A5/A2 显式声明统计范围,消除与 KPI 总量的口径误会 ( #73 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 3m42s
backend-test / frontend-lint-test (push) Successful in 1m8s
用户审计提问: 「同一个表单里面尽量时间窗口保持一致更合理一点?不然容易误会」
## 审计结论: 发现 1 处真实不一致
同窗口(近30天)实测:
KPI 总量 5,795 / 堆叠之和 5,795 / 水位登记 5,795 → 三者一致 ✅
A5 热力 total 5,613 → 差 −182 ❌
根因: A5/A2 只统计**工单侧**,而 KPI 总量 = 工单侧 + 315 补充表净额(182)
## ★ 关键判断: A5 **不能**纳入 315(已查证)
| 315 字段 | 内容 | 可用于"几点投诉" |
|---|---|---|
| complaint_date | **全为 00:00**(无小时) | ❌ |
| handle_time | 处理时间 | ❌ |
| created_at | **采集入库时刻**(06时 2456 条=批量采集) | ❌ |
→ 强行纳入会把"采集批次时刻"当作"客户投诉时刻" → 造出假凌晨高峰, **排班结论方向性错误**
## 修复方式: 显式声明范围(而非假装一致)
- 后端: A5/A2 响应新增 `scope: "ticket_only"` + `scope_note` 说明
- 前端: 两区块加「仅工单侧 N 条」标签 + ⓘ 增「为什么不含 315」段
- 测试: 新增 TestScopeDeclaration(4 例)
★ **可解释性断言**: A5.total + 315净额 == KPI.total
实测 5613 + 182 == 5795 ✅ —— 证明差异完全可解释, 非漏统
· scope 声明存在 / 0 时未异常高(防把 315 的 00:00 纳入) / 其余块仍一致
## 验证
- 后端 pytest 全绿 · 前端 eslint 0 error · vitest 52 例
- 浏览器实测: 「仅工单侧 5,613 条」标签正常显示
- 只读脚本验证 8 项断言全通过
关联工单: #73
2026-09-11 19:32:36 +08:00
08d755d5c7
feat(complaint): 概览增强 A2 反复投诉(事件口径) + A5 时段热力(排班决策) ( #72 )
...
用户: 「其他的你发挥一下」→ 实施 #67 设计文档 §四点五 的 A2/A5 两项。
## A5 · 投诉登记时段热力(服务排班决策)
- 后端 `_hour_heatmap()`:按「北京时间 小时 × 星期」聚合登记量
· 边际分布补齐 0(by_hour 24 项 / by_weekday 7 项,前端热力图需完整网格)
· 峰谷按 **P75/P25 分位**判定(非固定阈值 → 自适应业务量级)
· 用 `register_time` 而非 complaint_date:排班关心"何时有人来投诉"
- 前端热力图 + **峰谷结论文案**(把色块翻译成"哪些时段需值守/可减配")
实测量级:高峰 10-12 时 & 19-21 时(350~428 条)· 谷底 01-06 时(12~58 条)
## A2 · 反复投诉(「第一次没解决好」的直接证据)
- 后端 `_repeat_complaints()`:
★★ **计数单位是「投诉事件(SN + 北京日期去重)」,不是原始记录数**
实测近 30 天:事件 5035 vs 原始记录 29546 —— 同日多渠道被正确折叠
(若用记录数,会把"一天内多渠道登记"误判为反复,方向性错误)
· 升级信号:同时含「内部/商户号」与「外部」的设备 → 最高优先级
· 阈值可配(默认 3)+ 响应回显;清单按事件数降序、升级优先
- 前端:反复投诉摘要 + Top6 清单(SN 可点进设备审计 + 「已升级」红标)
- 结论条新增条目:「反复投诉 N 台设备…其中 M 台已升级到外部」
## 前端设计说明(遵循界面设计说明规范 · 三层落地)
两个区块均含:① 代码层 Design Rationale 注释块(五要素)
② 页面层 ⓘ 气泡(**含「取舍」段**:不做什么及原因)
取舍说明:不做"建议排班人数"(缺人均产能数据)· 不做周/月切换(日周期会被抹平)
· 不自动判定"是否同一问题"(remark 质量不足)· 不做阈值自定义 UI
## 附带:Element Plus deprecated API 全量清零
项目装 element-plus **2.14.4**(package.json 写 ^2.6.2)→ 三类运行时弃用告警:
- `[el-radio]` label → value:28 文件 / 158 处(前次已修)
- `[el-link]` :underline="false" → underline="never":27 处
- `[el-pagination]` 裸 small → size="small":9 处(含跨行属性)
新增可复现脚本 scripts/migrate_ep_deprecations.py(幂等,见下)
**浏览器实测:弃用告警 93+ → 0**(遍历分析/渠道/主体/执行跟踪各页签)
## 测试
- 新增 tests/test_complaint_overview_a2a5.py(13 例)
· A5:聚合自洽(格子==by_hour==by_weekday==total) / 网格补齐 / 峰谷合法不重叠
/ 高峰量级≥谷底(方向性检查)
· A2:★事件口径(事件数≤原始记录数) / 阈值回显 / 清单≥阈值 / 降序
/ 升级语义 / rate 界内 / 阈值单调性
· 集成:两个块在 /overview 中存在 / 不含卡商 / 反复投诉进结论条
- 空数据自动跳过(避免 "0==0 恒真" 的假绿)
- 真实数据只读验证:13 项断言全通过
## 验证
- ruff 全绿 · 后端 **1010 passed / 37 skipped / 0 failed**
- 前端 eslint 0 error · vitest 52 例全绿 · vite build 成功
- 脚本幂等(二次运行 0 处变更)
- 附带清理:删除锁定后遗留的 _pending 重复副本 + 修复 2 处引用断链
关联工单: #72(来源 #67 §四点五)
2026-09-11 19:28:47 +08:00
cab6599ee2
docs(governance): 文档性质标记规范 — 区分「需求」与「需求的需求」+ 全量打标 ( #71 )
...
用户: 「有的是需求,有的是需求的需求,都需要打上不同的标记」
## 核心洞察
三类文档(META/REQ/REF)长期混用同一套审查标准 → 用错标准 = 审查失效。
## 新增规范
docs/standards/文档性质标记规范.md (v1.0)
- 三分类定义 + 判定判据(判据是「约束范围」,不是文件名)
- ★ 三类规则差异表:
| | META | REQ | REF |
|审查标准| 能否约束住别人 | 验收标准达成 | 与代码一致 |
|变更成本| 高(全仓) | 中(局部) | 低(同步) |
|架构锁定| ❌ | ✅ | ❌ |
- 三层落地:① 文档头标性质 ② README §六 索引 ③ META 强制类挂 AGENTS
## 全量打标 25/25
META 13 · REQ 5 · REF 7
## ★ 盘点发现(已修复)
- **11 份 META 中曾仅 4 份有强制入口** → 7 份「标记了但约束不住」
- docs/standards/ 目录名误导:18 份混着 4 REF + 3 REQ
- 易错点:名为"XX接入规范"但只约束一个集成 → 是 REQ 不是 META
## 强制链补齐(AGENTS.md 绝对禁止 8~11)
新增 4 条,把"待挂"META 接入强制链:
- 8. error-code.md 响应格式(成功 {code,data,message} / 错误走 HTTPException)
- 9. 数据采集合并去重机制 所有采集通道的去重键
- 10. 投诉渠道口径与对账 渠道口径权威定义(改动影响全部投诉数字)
- 11. 数据源接入与缓存加速 缓存铁律(写入后必须失效 pattern)
## 其它
- docs/README.md 新增 §六 文档性质索引(单一事实来源);修复历史表格粘连缺陷
- AGENTS.md 新增「文档性质纪律(强制)」章
- review-checklist.md 新增「文档性质」检查项(6 条子项)
- .github/instructions/doc-class-rationale.instructions.md(applyTo: docs/**)
---
## 附带修复:前端构建/编辑器报错(真实问题)
### ① Vue 3 模板被误报 compileError(根因:仓库根无 ESLint 配置)
现象:`v-model:preset="..."` 被编辑器标 [vue/no-v-model-argument] compileError
实测澄清:vite build / docker build / 命令行 eslint **全部通过**
根因:VS Code ESLint 扩展以**工作区根**为 cwd 启动;根目录无任何 ESLint 配置
→ 回退内置默认规则集(含 Vue 2 遗留规则 vue/no-v-model-argument)
修复:
- 新增 eslint.config.mjs(根级**桥接**文件,转发 web/eslint.config.js,单一事实来源)
- 新增 web/jsconfig.json(声明 vueCompilerOptions.target=3,Vue 语言服务)
- 新增 .vscode/settings.json(eslint.useFlatConfig=true 等)
- web/eslint.config.js 显式关闭 vue/no-v-model-argument 并注明原因
验证:两个 cwd 均解析出 118 条规则、该规则 = [0](一致)
### ② el-radio-button label → value 迁移(Element Plus 2.14 弃用)
实测告警:[el-radio] label act as value is about to be deprecated (3.0.0 移除)
项目实际装 element-plus 2.14.4(package.json 写 ^2.6.2)→ 告警真实存在
迁移 28 文件 / 158 处(脚本 scripts/migrate_radio_label_to_value.py,可复现)
验证:浏览器实测 el-radio 弃用告警**归零**
## 验证
- 前端 eslint 0 error(5 warning 均为既有 v-html)· vitest 52 例全绿
- vite build 成功 · docker compose build --no-cache 成功
- 文档性质标记覆盖 25/25 · AGENTS 强制链 9 份 META
关联工单: #71
2026-09-11 19:11:56 +08:00
60b45ef325
docs(arch): #66 架构终审通过 — A-20260911-002 锁定 + 两份 LOCKED 文档升 v1.3
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Successful in 20m23s
backend-test / frontend-lint-test (push) Successful in 59s
用户授权审核通过。本提交完成架构文档闭环:
- 新建并锁定 A-20260911-002(只读分钟级窗口 + AI 自然语言时间识别)
→ 标 LOCKED v1.0 → 打 tag arch/A-20260911-002
- 修订 A-20260902-021 → v1.3: 单日端点放开分钟精度, brief 的 start/end 保持纯日期
- 修订 A-20260904-003 → v1.3: 废除 v1.2「前端本期不暴露手填日期」;
输入窗口增「问句自然语言识别」为第 3 优先级; /ai/analyze 契约增
start?/end? 与 window_source/window_label
- _pending/README.md: 登记本次锁定 + 明确剩余待审(001 投诉重构 / 005 投诉口径)
审核结论:
- 5 处 LOCKED 修订点全部批准并并入原文
- 分钟精度**不扩展到 brief**(区间语义混入分钟会使 span_days 失真), 由单日端点满足
- 不另立《只读接入规范》, 就地补 API接口文档 §2.13, 避免同一契约两处漂移
遗留(转独立跟踪): AI_SYNC_WAIT_S=6.0 未调整, 生产实测 rt=5.1~6.3s 越线仍存在;
本单以「越线不丢结果」应对, 降越线率方案见 A-20260911-002 §六.2
工单: #66
2026-09-11 19:00:59 +08:00
9b175861f8
feat(complaint): 投诉分析整体重构 P1-P3 + 界面设计说明规范 ( #67 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
概览瘦身 / 渠道同构 / 主体可筛 / 治理外迁,并立规「界面设计必须说明逻辑与目的」。
## 后端 (#67 )
- 新增 GET /complaints/overview:一次请求出一屏(结论条/KPI/四趋势/积压)
· 修掉「同屏数字打架」:/analysis 只看 register_time,本接口与三源 KPI 同源
· 卡商完全独立:总量/堆叠/水位/涉诉/积压 全部剔除 (外)卡商 (#53 口径)
· 口径自洽已固化为断言:堆叠之和≡KPI总量、累计净水位≡总积压、完成率≤100%
· 粒度自动推导 ≤31天=日 / 32~120天=周 / >120天=月;对比期=紧邻等长前段
· 结论条后端生成(最小基数护栏5条 + 可信上限300%),避免小基数伪信号
· ratios 预留 null 占位(比率指标体系另立 #68 专项研究)
- /complaints/subject-analysis 新增 4 个可选参数(**向后兼容**):
source_type(all/merchant/external/both=升级主体) / sort_by(总量/外部占比/涉诉金额/环比)
/ top_n(0=全部) / compare(prev环比)
· 不传参行为与改造前逐项一致(有单测守护)
· 筛选只作用于主体榜,不影响严重性分布/升级链(避免误读)
## 前端 (#67 )
- 新增 ComplaintTimeBar:全页唯一时间控件
· 替换原 5 个散落的 *Grain 开关(同屏可能 3 种粒度 → 无法横向比较)
· 周期(日/周/月/自定义) + 快捷(本周/上周/本月/上月/近7/30/90) + 环比
· ⓘ 说明含「取舍」:为什么没有昨日/同比
- 新增 ComplaintConclusionBar:把数字翻译成"该干什么",可点击下钻
- 新增 ComplaintChannelPanel:内/商/外/卡 **同构**渲染
· 改造前 外/商 各写一套、内/卡 缺失 → 口径与配色不一致、修 bug 要改四处
· 卡商渠道不画处理人排行(result 是混合语义,画了即误导)
- 概览瘦身 12+图 → 4趋势块+积压明细(一屏),删除三个功能重复的历史区块
- ComplaintAnalysis.vue 2202 → ~1700 行
## 数据治理外迁 (P3)
- 「人工审核(#55 )」「采集质量(#60 )」→ 系统管理 → 投诉采集质量
· 用户决策:所有的数据治理都转到系统管理去做
· 新建 /system/complaint-quality + 菜单项;仍保持**只读**,无删除入口
## 规范(新增,强制)
- docs/standards/界面设计说明规范.md v1.0
· 五要素必填:目的/逻辑/口径/**取舍**/工单
· 三层落地:①代码注释块 ②页面可见 ⓘ(含取舍段) ③审查清单
· 判定6条(含"删掉后没人决定会改变 → 属秀好看,应删")
· 存量不追溯,改动即补齐
- 接入 AGENTS.md(绝对禁止第7条 + 「界面设计纪律」章)
- 接入 review-checklist.md(前端改动必查,缺则 CRITICAL)
- 新增 .github/instructions/ui-design-rationale.instructions.md(自动加载)
- 三角色 agent 同步注入(架构师/开发者/审查员)
## 测试
- tests/test_complaint_overview.py(44 例:粒度/聚合/环比/口径自洽/卡片契约)
- tests/test_complaint_subject_filters.py(15 例:筛选契约 + 向后兼容)
- 空数据自动跳过而非假绿(空窗口上 "0==0" 恒真,无验证意义)
- ruff 全绿;后端 pytest 全绿;前端 eslint 0 error、vitest 52 例全绿
## 设计文档
- docs/arch/_pending/A-20260911-001-投诉分析整体重构.md(PENDING 待审核)
- 待修订 LOCKED:A-20260902-016(三并列饼图 → 渠道同构)
关联工单: #67 #68 #69 #70
2026-09-11 18:52:45 +08:00
4b6115c35c
docs(arch): #66 架构修订草案 — 只读分钟级窗口 + AI 自然语言时间识别
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Successful in 3m41s
backend-test / frontend-lint-test (push) Successful in 30s
按 AGENTS.md「禁止擅自修改已锁定架构文档」: 本次改动触及两份 LOCKED 文档,
故以 PENDING 草案提请人工终审, 不直接改动 LOCKED 原文。
需修订的 5 处:
- A-20260902-021 v1.2 L144「时间精度: 仅纯日期 YYYY-MM-DD」
→ 单日端点放开至分钟; brief 的 start/end 保持纯日期
- A-20260902-021 §二.1 单日端点 → 补 YYYY-MM-DDTHH:MM 与窗口元信息字段
- A-20260904-003 L14 v1.1 注「前端本期不暴露手填日期」
→ 改为已暴露(周期下拉含「指定区间」+ 自然语言自动识别)
- A-20260904-003 L98 输入窗口 → 增加「问句自然语言」为第 3 优先级
- A-20260904-003 L170 POST /ai/analyze 契约
→ 入参增 start?/end?; 响应增 window_source/window_label
另记录: AI_SYNC_WAIT_S 保持 6.0 的取舍(生产实测 rt=5.1~6.3s, 越线率仍存在),
本单选择「越线不丢结果」而非调阈值, 并给出后续降越线率的 3 个可选项。
工单: #66
2026-09-11 18:49:13 +08:00
088ef5267e
feat(ai): AI 助手自然语言时间识别 + 只读API分钟级单日窗口 + 轮询不丢结果 ( #66 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 3m36s
backend-test / frontend-lint-test (push) Successful in 41s
生产日志审计(2026-09-10)发现 AI 助手两类缺陷, 本提交一并修复:
① LLM 侧零故障, 失败在等待与传递胶水层
- `api.deepseek.com` 50 次调用全部 200, AI 任务 ERROR/Traceback 0 条
- 但 analyze 有 36% 请求 6s 内跑不完(响应体恰为 running 兜底)
- 轮询仅发生 1.25 次/任务且从未拿到终态(响应体全部=running) → 答案静默丢失
② "不够智能": 前端从不传时间窗口
- submit() 只发 {question, period}, feed 永远取当前窗口
- 用户问"昨天"时模型手里只有 yoy 百分比 → 答"昨日暂无数据"或硬凑
改动:
- 新增 ai_time_parser.py: 解析 相对单日/N天前/近N天/这两三天/本周上周上上周/
本月上月/绝对日期/绝对与相对区间; 保守优先(无把握→none 退回 period)、
丢弃未来与非法日历日、支持「本月1日到5日」省略终点月份;
优先级 明确日期点 > 区间表达
- ai.py: _AiWindow.resolve() 接入自然语言兜底(显式参数永远优先);
跨天区间切 grain=week(与 external brief 同映射); 响应回显
window_source/window_label; 校验收紧为字面 YYYY-MM-DD(原 fromisoformat
会接受 20260910/2026-W01-1 这类契约外格式)
- external.py: 单日端点 date 放开到分钟(YYYY-MM-DDTHH:MM), 供下游同刻日比对;
新增 window_kind/precision/start/end + compare_* 元信息;
分钟窗口走日期感知 TTL(已终结→可长缓存); brief 的 start/end 仍只收整天
- analytics.py: _resolve_windows 新增分钟分支(当日00:00~该分钟, 同比前一日同刻);
纯日期路径逐行未变(回归安全)
- redis_client.py: _CACHE_DATE_RE 容忍「日期+分钟」形态, 否则分钟 key 被当
无日期静态 key 全量删除, 使长 TTL 形同虚设
- ai_task_manager.py: _humanize_error 保证 error 非空(原出现 error=None →
前端只能显示"未知错误")且不透出内部异常原文(判据用 isinstance(LLMError),
不能用 hasattr(status_code) —— HTTPException 也带该属性); 任务持久化窗口元信息
- ai_feed.py: System 铁律新增"必须回显统计窗口"与"yoy 不是另一天独立明细";
build_messages 接受 window_label/window_source
- ai_glossary.py: timezone 卡补对比期语义; 对比类/含具体日期的问句强制注入该卡
- AssistantDrawer.vue: 窗口下拉(自动识别/指定区间)+日期选择器; 窗口回显条;
轮询追到终态、单次失败重试(上限5次)、关闭后 sessionStorage 续查(不丢结果);
补齐三层设计说明(代码注释块 + 页面 ⓘ 含取舍段), 依据界面设计说明规范
- auth.js: 登出时清理 AI 会话缓存(原只清 localStorage, 会把上一账号的
经营数据留给同一标签页的下一位使用者)
- nginx.conf: access_log 追加 rt=$request_time(排障时缺该字段只能手工计时)
- 文档: API接口文档补 /ai 章节与单日分钟精度说明
验证:
- 后端 ruff 全绿; pytest 1010 passed, 8 skipped(基线 757 + 新增)
- 前端 eslint 0 error; vitest 52 passed(含新增 6 项轮询/续查用例); build 通过
- 新增测试: test_ai_time_parser.py(52) / test_external_minute_window.py(28)
/ test_ai_assistant.py 增补(窗口贯通/错误可读/内部信息不外泄)
- 真实数据等价验证: 改前模型只有 yoy_pct(无独立明细), 改后取到完整昨日
明细(接待 8028 / 设备 2368 / 退款 7967.2)
工单: #66
2026-09-11 18:32:20 +08:00
7373ace3c4
feat(pure-card): 纯工信部投诉单列页(工单+文档 合并去重)( #63 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 9m12s
backend-test / frontend-lint-test (push) Successful in 1m10s
## 用户需求
> 「纯粹工信部的**优先级不高**,有统计记录就好了。回头**单列一页**展示即可。
> 工单+文档 **合并去重** 统计展示。」
## ★★★ 核心: 判据必须用「SN 或 电话」
纯工信部 = 工信部工单 ∧ NOT (卡商表 SN 或 电话 命中)
∧ NOT (315表 SN 或 电话 命中)
### 为什么 SN 精确匹配不够(用户人工核对后发现的盲区)
| 现实情况 | 实例 |
|---|---|
| 同一客户**多台设备**:工单管 A 机,卡商单登记 B 机 | ① |
| 同客户**换机**,SN 变了 | ② |
| **处理意见里提到另一台** SN | ⑥ |
上述三条的 `phone` **完全一致** → **电话才是可靠的关联键**。
(印证 #55 : SN = 客户身份, 但一个客户可能有多台设备。)
### 实测(2026-09-11, 全量 320 条工信部工单)
| 判据 | 命中 |
|---|---|
| SN 精确命中 | 314 |
| **SN 或 电话命中** | **318**(电话兜底多抓 4 条)|
| **纯工信部** | **2** |
用户核对的 6 条候选**逐条验证通过**:
| # | SN | 结果 | 命中路径 |
|---|---|---|---|
| ① | 68032504010203 | 有对账 ✅ | 卡商**电话**×2 |
| ② | 67492609646449 | 有对账 ✅ | 卡商 SN×5 + **电话**×15 |
| ③ | 67482512338896 | **纯工信部** | — |
| ④ | 67482610416034 | **纯工信部** | — |
| ⑤ | 69212602132363 | 有对账 ✅ | 315**电话**×2 |
| ⑥ | 69212612179896 | 有对账 ✅ | 卡商**电话**×1 |
★ ①②⑥ 正是「SN 查不到、电话能查到」→ 用 SN 精确匹配会**误判为纯工信部**。
## 实现
### 后端
- `services/pure_card_complaint.py` —— 只读视图服务(**不改任何数据**)
- `_hit_sets()` 一次性载入两表 SN/电话集合(避免 `NOT IN` 的 NULL 三值陷阱)
- `_is_hit()` 返回命中路径(`卡商SN`/`315电话`…)供前端解释
- 统计: 总数 / 已对账 / **纯工信部** / `sn_exact_only`(对比电话兜底价值) / 月分布
- `GET /complaints/pure-card` —— 支持时间范围 + `include_checked` + `detail_limit`
### 前端
- 新页 `PureCardComplaint.vue`:口径说明 / 4 张 KPI / 命中路径柱图 / 月度对比 / 清单 + CSV 导出
- 路由 `/dashboards/pure-card-complaint`
- 菜单: `buildMode: 'debug_only'`(核查类工具,与「投诉核查」同档)
## 纪律
- ⚠️ **只读**:原 `service_item='工信部投诉'` 保持原样(用户: "不能乱删除 所以还是慎重")
- 与 #57/#65 卡商口径**不冲突**:卡商桶仍含全部工信部工单,
本页只是从中再筛出「两边都无对账」的那一小撮
## 测试
`tests/test_issue63_pure_card.py` 12 项:
- SN 命中 / **电话命中** / 都无命中 / 315 电话命中 / **多设备场景**
- `sn_exact_only ≤ matched`(电话兜底只增不减)
- 月分布 / 命中路径统计 / include_checked
- 非工信部工单排除 / 处理意见 remark 回退
- ★ **只读性验证**(调用后原数据不变)
全量 906 项通过; ruff + eslint + vite build 通过。
## 已知遗留(非本单引入)
卡商明细表条数(105) 与 KPI `card.total`(111) 存在差:
明细按 **SN 挂载**, KPI 按 **matched_ticket_no** —— 两个既有口径。
已记录, 未擅自改动(避免扩大范围)。
2026-09-11 17:18:07 +08:00
67a7bba039
fix(complaint-detail): source_type 筛选未约束补充表独立行 ( #65 )
...
## Bug
`/complaints/detail` 的补充表收集(`_collect`)不受 `source_type` 约束:
if source_type:
stmt = stmt.where(src_expr == source_type) # ← 只筛工单主行
...
_collect(ExternalComplaint, ...) # ← 315 表全量照收
_collect(CardComplaint, ...) # ← 卡商表全量照收
实测 `source_type=(外)卡商` 返回 **473 条**, 其中 **472 条是外部投诉**
→ 前端「卡商」筛选看到的基本全是外部投诉。
## 修复要点(★ 容易修错)
**不能**在 `_collect` 开头直接 `return` —— 补充记录若 SN 命中工单行,
它是该行的**附注(supplementary)**, 不改变主行所属桶, 且 `subject`
过滤依赖它(W2 场景: 商户号工单行 + 挂载的 315 主体)。
→ 只对**独立行**(`extra_rows`)施加桶筛选, 保留挂载逻辑。
(我第一版就是直接 return, 被 `test_detail_subject_filter_matches_attached_315`
拦住 —— 该测试有效。)
## 修复后实测(近 3 月)
| source_type | 明细 | 工单侧基准 |
|---|---|---|
| 内部投诉 | 2000(limit) | 11389 ✅ |
| 商户号投诉 | 1849 | 1849 ✅ |
| 外部投诉 | 355 | 355 ✅ |
| (外)卡商 | 23 | 23 ✅ |
| (外)卡商 + 补充表 | 105 | 工单23 + 补充独立行82 |
## 测试技巧(★ 踩过的坑)
直接调用 FastAPI 路由函数时, **未显式传入的参数是其默认值对象**
(`Query(False)`)—— 是**真值**! 必须显式传全部参数, 否则
`include_supplementary` 意外为 True。这个坑让我误判了很久
(表现为「内部投诉返回 0 条」的假象)。
## 新增测试
`tests/test_issue65_detail_filter.py` 6 项:
- 卡商筛选不得混入 315 行 / 应保留卡商行
- 外部筛选不得混入卡商行 / 应保留 315 行
- 无筛选时各桶都在
- ★ 挂载行为不被破坏(守护 test_external_brief.py 的 W2 场景)
全量 894 项通过; ruff 通过。
2026-09-11 17:11:57 +08:00
6ba0562eba
chore(reports): 重导缺口清单 v2 + PII 忽略改为通用规则 ( #65 )
...
## 1. 缺口清单 v2(#64 修复后重导)
v1(38 条)生成于 #64 之前,当时 `_clean_sn()` 未处理
前导引号 / 斜杠分隔两种脏 SN → 那类记录**永远匹配不上工单**,
会被**误判**为「文档有、工单无」。
重导结果:
| 窗口 | A. SN 无任何工单 | B. A 中电话也无 |
|---|---|---|
| 近 3 个月 | 55 | **45** |
| 2026 全年 | 183 | **168** |
**#64 有效性验证** —— 8 个已修复的脏 SN 在 v2 中**全部消失**(已匹配成功):
69142529011493 70032611013040 70032617030194 68772612319866
58812351022558 63272421032891 69212545116000 67002453179005
与 v1 差异核对: v1 的 36 个 SN **全部保留**(0 条被"修复掉"), 新增 7 条。
补充发现: 近 3 月缺口 **07 月占 62%**(28/45) → 疑似集中漏建单, 建议专项排查。
## 2. 新增可复现脚本
`backend/scripts/export_complaint_gap.py`
⚠️ 口径修正: 「工单无」= 在**全部工单**(不限 category) 里都无记录,
不是只查外诉工单 —— v1 脚本有此偏差。B 变体定义为 A 的**子集**。
## 3. ★ .gitignore 改为通用 PII 规则
**踩坑**: 原先按目录逐条加规则(3 条), 每新增一个核查目录都要**记得加一条**,
遗漏时 `git add` 会把含真实 SN/手机号的 CSV 扫进去。
改为通用 glob:
docs/reports/*核查*/*.csv
docs/reports/*核查*/*/*.csv
docs/reports/*分析*/*.csv
docs/reports/*导出*/*.csv
已验证 5 个路径(含新目录 v2)全部命中忽略。历史条目保留作变更溯源。
## 产物
- `docs/reports/20260911-外部投诉缺口核查/README-v2.md` ✅ 入库
- `.../v2/*.csv` 4 个变体清单 ❌ 含 PII, 已忽略
2026-09-11 17:00:19 +08:00
169f1496a5
feat(complaint-ui): 明细表标注软纠正工单「原登记工信部」 ( #65 )
...
## 背景
#65 让统计层按 #58 规则纠正了「误录为工信部、实为监所」的工单,
但 `service_item` 仍是「工信部投诉」→ 明细表会出现
来源: 外部投诉 | 分类: 工信部投诉 ← 自相矛盾
运营看到会以为系统分类错了。
## 改动
后端 `GET /complaints/detail` 新增 `soft_corrected` 字段:
soft_corrected = (source == '外部投诉')
∧ (service_item 含 '工信部')
⚠️ 仅**标注**, 不改 BMS 原数据(用户: "不能乱删除 所以还是慎重")。
前端在明细表的「分类」列渲染为:
[工信部投诉] [已纠正]
↑ 红色(原登记) ↑ 绿色(说明)
hover 提示完整原因 + 回滚条件(BMS 增加「监所/市监局」服务项后可取消)。
## 覆盖范围
- `OperationDashboard.vue` 运营看板 — 投诉明细表
- `ComplaintAnalysis.vue` 投诉分析 — 主体下钻抽屉明细表
> 补充说明: 另有两处表(商户号明细 / 315 补充明细)不显示工单侧数据, 无需改动。
## 验证
- 后端全量 888 项通过
- 前端 vitest 46 项通过; eslint 通过; vite build 通过
2026-09-11 16:51:26 +08:00
ab4bb6673a
fix(complaint-scope): 统计层接入 #58 软纠正 + 卡商并集去重 ( #65 )
...
## 问题: 188 条工信部工单在统计层"两边都不算"
#58 的软纠正(工信部工单若 SN 属 315 监所/市监局类 → 实为监所投诉)
**只被 external_reconcile.py 调用** → 对账正确、**统计错误**。
effective_external = 外部平台词 ∨ (工信部 ∧ SN∈监所)
effective_card = 工信部 ∧ SN∉监所
card_total = 直接查 card_complaints 表 ← 与 effective_card 无关
→ 工信部桶在统计侧既不在外部桶、又不在卡商表 → **静默丢弃**。
实测(近3月, 2026-06-11~09-12):
内部 11434 | 商户号 1876 | 外部(原) 211 | 工信部桶 188
三源合计 13521 真实投诉类工单 13709 → 缺口 188
其中 160 条是真实监所投诉(航城监所等)
## 修复 1: 软纠正下沉到分桶层
complaint_bucket_case(item_col, channel_col=None, sn_col=None)
↑ 传入即启用
`analytics._complaint_source_type_case` 默认传 `ServiceTicket.device_sn`
→ 全部统计口(总览/明细/分析/积压/主体) 一处修复, 全域对齐。
不传 sn_col 时**保持历史行为**, 兼容 metric_registry 等调用点。
数学性质(有单测守护): 软纠正是对原始分桶的**完备互斥拆分**
raw外部 ∪ raw卡商 == effective外部 ⊔ effective卡商
## 修复 2: 卡商总量并集去重
#53 撤销卡商去重的依据是「工单表 service_item/service_category 含「卡商」均为 0 行」。
**该检测方式有误** —— 卡商在工单侧不以「卡商」字面存在, 而是以
`service_item='工信部投诉'` 存在(#57 证实) → #57 后工单侧已有真卡商。
工单侧真卡商 28 条 | card_complaints 78 行 | SN 重叠 16 条(全已 matched)
改为与外部侧平行的公式:
卡商 = 工单侧真卡商 + card_complaints − 重叠
趋势同步改为 _merge_trend(card_ticket_trend, card_suppl_trend),
修掉「趋势逐日累加 ≠ 总数」的口径分裂。
⚠️ 卡商**仍不参与投诉总量**(#53 用户口径不变), 仅其自身总数改并集口径。
## 修复后实测(近3月)
外部投诉 211 → 371 (与对账接口一致 ✅ )
卡商 78 → 111
工单侧四桶合计 13616 == 投诉类工单总数 13616 ✅ 缺口归零
## 附带
- analytics 中三个已废弃关键词常量标注 DEPRECATED_*(`_EXTERNAL_PLATFORM_KEYWORDS`
仍含已移出的「工信部」→ 加了防误用警告)
- 口径文档补 §2.4 口径落地层级 / §2.5 卡商并集去重
## 测试
- 新增 `tests/test_issue65_source_scope.py` 22 项
核心不变量: `test_union_covers_raw_split`(拆分不完备/黑洞)
- 全量 888 项通过; ruff 通过
2026-09-11 16:49:37 +08:00
ada83788dd
fix(complaint-sync): SN 清洗补前导引号与斜杠分隔 + 渠道口径对账说明文档 ( #64 )
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Successful in 44m39s
backend-test / frontend-lint-test (push) Successful in 32s
## 问题(导出对账清单时肉眼发现)
`_clean_sn()` 未处理两种源文档脏 SN 形态 → 这些 SN **永不匹配工单**
→ 产生**虚假的「文档有、工单无」空悬**。
### 生产实测 7 条
| 形态 | 条数 | 例子 |
|---|---|---|
| **前导单引号**(Excel 文本格式残留) | 5 | `'70032611013040` |
| **一格两SN**(`/` 分隔) | 2 | `58812351022558/63272421032891` |
### 根因
```python
_SN_SPLIT_RE = re.compile(r"[\n,;,;、\s]+") # ← 没有 `/`
# 且未剥引号 → `'70032611013040` 整串不匹配任何工单
```
**与 #55 的关系**: 当时修过「一格多**电话**」(`split_phones`),
但**漏了同类问题的 SN**。
## 修复
```python
_SN_SPLIT_RE = re.compile(r"[\n,;,;、\s/|]+") # 补 `/` `|`
_SN_LEADING_QUOTE_RE = re.compile(r"^[\'\"\u2018\u2019\u201c\u201d]+") # 新增
```
`_clean_sn()` 切分后逐段**先剥引号再校验**。
## 测试
`tests/test_sn_backfill_dedup.py` 新增 `TestCleanSn` **7 项**(共 29 项):
前导引号(单/双/中文) / `/` 与 `|` 分隔 / **原行为不回归**(备注剥离·逗号多SN·
空白·空值) / 纯引号返回 None / 组合脏值
7 个用例全通过; 全量 pytest 全绿; ruff 无告警
## 附带:渠道口径与对账说明文档
新增 `docs/standards/投诉渠道口径与对账说明.md`(214 行,业务/上游/开发者参考)
内容:
- **一句话概括**: BMS 是「经营登记」(只读) / 文档是「接待过程」(可回填)
—— 两条腿互为补充, 对账目标是**找缺口**而非数量相等
- **渠道口径权威定义**: 内部/商户号/外部/(外)卡商 四桶 + 判据
(含「航城监所 ∈ 外部投诉」「工信部 ∈ 卡商」的明确区分)
- **渠道错位问题**: 监所投诉被录入工信部(实测 241/327=73.7%) + 根因 + 软纠正应对
- **采集特性**: SN 后补残影 + 三层处理机制 + 对上游建议
- **当前对账状况**: 实测数字(工单侧 97.8%、文档侧空悬归因)
- **给业务/上游的待办**(含已提交 BMS 的类目需求)
- **技术实现索引**
★ 文档明确记录: **BMS 侧只读不可改** → 应对是「文档回填 + 对账侧软纠正」,
而非要求改 BMS 数据(已另行提交类目需求给 BMS 开发)。
## 关联
- 前置: #55(一格多电话的同类修复) · 本单 #64
2026-09-11 16:07:00 +08:00
6a8ecbc8ee
fix(complaint-sync): 修正 #59 回归 — 残影改「标记被取代」而非回填SN ( #62 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 29m32s
backend-test / frontend-lint-test (push) Successful in 44s
## 回归(我引入的,自查发现)
#59 的 `_reconcile_sn_backfill` 想让"有SN的行"**认领**残影:
把该行的 dedup_hash 改成残影的(旧, 按无SN算) → 交给 upsert。
但 `_batch_upsert` 用该指纹做 `on_conflict_do_update` 时**同时回填了 device_sn**
⇒ 落库成「**device_sn 有值 + dedup_hash 按无SN算**」的**自相矛盾行**,
与真完整行(另一指纹)**永不归并**。
**结果与目标完全相反**: 本想合并, 却造出一条再也匹配不上的坏行。
## 生产影响(实测)
- 有 SN 但 dedup_hash 与真指纹不符: **27 条**
- 其中 13:23~14:07 被改(#59 部署后同步窗口): **26 条**
- 同弱指纹多行: 37 组, 其中**两条都有SN**(最坏形态): **16 组**
## 修复:语义修正
```python
if r.get("device_sn"):
gid = ghosts.get(w)
if gid is not None:
# ★ 把残影**标记为被取代** —— 不删、不回填
update(...).values(quality_status=QSTATUS_DUP_SN_BACKFILL, ...)
marked += 1
out.append(r) # 本行按**自己的真指纹**正常入库
else:
if w in weak_with_sn:
skipped += 1 # 无SN的重复行 → 不写入
continue
out.append(r)
```
要点:
- 完整行**保持自己的真指纹** → 不再自相矛盾
- 残影**仅打标**, 不删不回填 → 与 #60 统计排除口径天然一致
- 幂等: 已标记的残影跳过
- 返回值语义 `claimed` → `marked`(更准确)
## 测试
重写断言旧语义的用例, 新增**关键防线**:
- ★ `test_ghost_sn_not_backfilled` —— **断言残影 SN 不得被回填**(初版 bug 直接体现)
- ★ `test_marks_existing_ghost` —— 断言**指纹不得被改成残影的**
- 真实案例 2 项改为断言「标记残影 + 残影SN保持为空」
`tests/test_sn_backfill_dedup.py` **22 项**全绿; 全量 pytest 全绿; ruff 无告警
## 我的责任与教训
- 只测了「认领决策」, **没测 upsert 的最终落库效果**
- 部署后**未复查**被修改行的实际状态
- ★ 教训: **改 upsert 逻辑必须断言「落库后的最终状态」**;
认领/合并类逻辑应让**权威行保持自己的指纹**, 被取代方只打标
—— 不要试图「改写指纹去撞」
## 生产数据处置
用户已授权可全量重采(「文档采集的数据 也是 可以全量重采」)
→ 部署后**清空重采**(比逐条修复更干净), 并验证:
· 同弱指纹多行组 = 0
· 残影 device_sn 保持为空
· dedup_hash 与 device_sn 自洽
## 关联
- 回归来源: #59
- #60(统计排除) / #61(事件归并) 逻辑本身正确, 不受此 bug 影响
2026-09-11 15:06:41 +08:00
784495dc88
feat(complaints): 残影参与事件归并 — 弱指纹作归并键 ( #61 )
...
gitleaks / gitleaks (push) Successful in 4s
backend-test / backend-lint-test (push) Successful in 4m6s
backend-test / frontend-lint-test (push) Successful in 49s
## 背景
#59 标记残影 → #60 已从**统计总数**排除。但 `dedup_events`(事件归并)没接,
导致残影在**事件口径**下仍被算 2 笔。
## 缺口
`dedup_events` 归并键是 `(SN, 归一化电话)`:
```python
if not sn or not ph:
unkeyed.append(r) # ← 缺 SN 的残影落到这里, 独立成事件
```
实测: 残影 id=309648(SN空) 与 完整行 id=312247 在事件口径下各算 1 笔。
## 为什么不能直接忽略 unkeyed
`unkeyed` 里**不只有残影** —— 还有**真实缺 SN 的投诉**(如航城监所部分记录,
渠道表单无设备号列)。无差别丢弃会**漏计真实投诉**。
→ 必须只对**已标记的残影**做归并。
## 实现
- `complaint_scope.py`
· 新增 `_weak_event_key()` —— md5(日期|归一化电话|问题签名)
与 `complaint_sync._weak_identity_hash` **同口径**(#59 的标记依据)
· `dedup_events` 新增参数 `problem_key` / `quality_status_key` / `qstatus_ghost`
· **三级归并键优先级**:
① 有 SN 且有电话 → (SN, 电话) [原逻辑]
② ★ 标记为残影 → 弱指纹(日期|电话|问题签名) [新增]
· 弱指纹命中完整行所在组 → 并入(不重复计)
· 命中不了 → **保守独立成事件**(不猜)
③ 真实缺 SN/电话 → unkeyed(独立成事件) [保持现状]
· 事件产出新增 `ghost_ids` / `ghost_merged`
—— 报表可显示「该事件含 N 条残影(采集质量)」
- `complaints.py` `_suppl_event_metrics`
· 查询加 `complaint_quality_include_cond` 排除残影(#60 口径统一)
· 同时**保留**传入 `quality_status`/`problem` 让归并逻辑可兼容
「未排除但已标记」的调用场景(如下钻明细)
· 输出新增 `ghost_merged` 汇总
## 测试
`tests/test_sn_backfill_dedup.py` 新增 6 项(**共 25 项**):
· ★ 残影与完整行 → 归并为 **1 个事件**(含 ghost_ids/ghost_merged)
· ★ **真实缺SN 不被误并**(仍独立成事件, 防漏计真实投诉)
· 残影找不到完整行 → 保守独立
· 普通事件 ghost_ids 为空
· 幂等
· ★ **不修改输入行**
全量 pytest 全绿; ruff 无告警; gitleaks no leaks
## ★ 零删除
归并仅作用于**统计/事件视图**, 原始行与 `record_ids` 完整保留。
## 关联
- 前置: #59(SN后补治理) · #60(统计排除)
2026-09-11 14:17:02 +08:00
87efc139d9
feat(complaints): SN后补残影纳入统计排除 + 采集质量核查视图 ( #60 )
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
## 背景
#59 已把「SN 后补」造成的残影标记为 `quality_status='dup_sn_backfill'`
(生产 31 条: external 27 + card 4),但**尚未从统计口径排除** →
同一条投诉仍被算两次,外部投诉总数虚高。
## 沿用项目既有范式(不另造轮子)
退款侧 A-20260909-001 §2.7 已有同类治理:
_EXCL_QSTATUS_SQL = "COALESCE(quality_status,'') NOT IN ('dup_residual','ignored')"
_quality_exclude_sql(db) / _quality_include_cond() ← 统一入口
设计要点照搬: ① 统一入口防口径漂移 ② **列不存在时跳过**(不让报表报错)
## 实现
- `complaint_scope.py`
· `QSTATUS_DUP_SN_BACKFILL = "dup_sn_backfill"`
· `COMPLAINT_EXCLUDE_QSTATUS = ("dup_sn_backfill",)`
· `has_quality_col(db, table)` —— 列存在性守卫(仿 analytics 同名逻辑)
· `complaint_quality_include_cond(model, db=None)` —— **统一排除入口**
⚠️ 不传 db 时不做守卫(避免每次查询都打 information_schema)
- `external_reconcile.py`: `_reconcile_core` 的 `doc_total`/`doc_rows` 加排除
- `complaints.py`: 新增 `GET /complaints/sn-backfill`
· 返回残影清单 + **配对完整行信息**(`full_id` / `full_device_sn` /
`full_created_at`) → 可直观看到「先建行后补SN」的时序证据
· 显式返回 `excluded_from_stats: true` / `rows_preserved: true`
—— 避免被误解为数据丢失
- 前端: **投诉分析**页新增「采集质量」Tab
· KPI 残影总数 + 逐月分布
· 表格: 来源/投诉日/平台/残影ID/完整行ID/**补齐的SN**/两侧入库时间
· 顶部说明「已从统计排除 · 原始行保留可回溯」
## ★ 不删除任何行
用户明令「不能乱删除 所以还是慎重」——
排除仅作用于**统计口径**,原始行完整保留;把 `quality_status` 置空
即可**完整回滚**。
## 测试
- `tests/test_sn_backfill_dedup.py` 新增 4 项(**共 19 项**):
· 被标记的残影不出现在统计集合
· ★ **排除后原始行仍然存在**(不删数据)
· 列存在性守卫
· 未标记的普通行不受影响
- 全量后端 pytest 全绿; ruff 无告警
- 前端 vitest 46/46; eslint 无告警; vite build 通过
- gitleaks no leaks
## 关联
- 前置: #59(SN 后补治理) · 范式来源: A-20260909-001 §2.7(退款侧 dup_residual)
2026-09-11 14:12:20 +08:00
c8269d8cb0
fix(complaint-sync): SN 后补导致重复行 — 双指纹认领 + 存量残影标记不删 ( #59 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
## 用户报告(2026-09-11)
> 「67912450006278 9.2;这个有重复,没有去重成功」
> 「9.1 航城监所 …没有关键的信息显示」
> 「08-31 / 09-02 有个投诉 怎么全部都是空的」
## 根因:SN 参与指纹 + 源文档「先建行、后补 SN」
dedup_hash = md5(日期 | 电话 | SN | 问题签名)
↑ SN 参与 → 补 SN 后指纹变化 → INSERT 而非 UPDATE
时序: T1(SN空) INSERT 残影 → T2(SN补齐) 指纹变 → **又 INSERT** → 两条并列
## 三个报告问题同源
1. `67912450006278`: id=309648(16:30,SN空) + id=312247(17:00,SN有), 问题文本相同
2. 9-01「没有关键信息」= id=234603(11:30,SN空→keyword/分类/结果/跟进人全空)
旁边的 id=247519(13:00,SN补齐,字段完整) —— 用户看到的正是补全前那条
3. 8-31/9-02「全是空的」= 采集分批入库(09-02 10:00 那批连电话/问题都空)
全量(生产库): 85 条 = 37 组, 冗余 **48 条**, 形态均为「缺SN残影 + 有SN完整行」
## 为什么现有机制抓不到
- `dedup_hash`(同步侧): SN 计入指纹 → 两条指纹不同 → 判为不同记录
- `dedup_events()`(#55 ): 按 (SN,电话) 分组 → 缺SN 的落入 `unkeyed`, 不参与归并
## 实现
- 新增 `_weak_identity_hash()` —— **弱指纹不含 SN**, 仅作"SN 从无到有"的识别桥
⚠️ 主指纹**不能**直接去 SN: 同一人多设备同日同问题会**误合并**真实多条投诉
- 新增 `_reconcile_sn_backfill()`:
· 待入库行 **有SN** 且弱指纹命中库内「缺SN残影」→ 指纹改为残影的
→ 后续 upsert **UPDATE 残影**(回填 SN 及全字段), **不新增** ★
· 待入库行 **无SN** 且弱指纹命中「有SN完整行」→ **跳过**(不覆盖完整行) ★
- 接入 `_upsert_external` / `_upsert_card`; `SyncRowResult` 增
`sn_backfill_claimed` / `sn_backfill_skipped` 计数(可观测)
- 新增 `scripts/mark_sn_backfill_duplicates.py` —— 存量治理
· 判定(保守,三重条件): 同(日期,电话,问题签名) ∧ 恰好1条有SN ∧ 残影入库更早
· **只标记不删除**: `quality_status='dup_sn_backfill'` + note 记录被哪条取代
· 默认预览(dry-run), `--apply` 才改库; `--export` 导清单
## ★ 不删数据
用户明令「不能乱删除 所以还是慎重」—— 存量 48 条冗余**全部保留**,
仅打标; 原始行与 record_ids 可回溯。
## 开发库实测
预览发现 **29 组** 残影(external 25 + card 4), 形态完全吻合:
残影入库时间 **早于** 完整行(如 id=149811 08-28 07:00 ← id=152368 07:30)
## 测试
- 新增 `tests/test_sn_backfill_dedup.py` **15 项**:
弱指纹(不含SN/主指纹含SN/对电话日期敏感) /
SN后补认领(核心/无残影不改/不删原行) /
残影跳过(有完整行跳过/首见保留) /
幂等(重复认领稳定/空行/缺电话不崩/混合批次) /
**两个真实案例复现**(67912450006278、234603+247519)
- 全量 pytest 全绿; ruff 本次文件无告警; gitleaks no leaks
## 后续建议
- 上游「先建行后补SN」是采集系统固有特性 → 建议源文档一次填全
- 存量标记可跑: `python scripts/mark_sn_backfill_duplicates.py --apply`
2026-09-11 14:06:03 +08:00
ed2cf4ed0e
chore(gitignore): 排除工信部误录核查 CSV(含PII) ( #58 )
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Successful in 18m36s
backend-test / frontend-lint-test (push) Successful in 39s
2026-09-11 13:18:13 +08:00
6f8ac28c26
feat(reconcile): 工信部摘出为卡商渠道 + 监所/市监局误录软纠正 ( #57 , #58 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
## 用户口径(2026-09-11 权威)
> 「工信部投诉 应该是卡商投诉匹配,可以从外部投诉 摘出来 做单独统计对账去」
> 「全部摘出吧 然后再分开对账」
> 「航城监所 属于外部投诉;工信部 属于卡商投诉 是俩个完全不同的渠道」
> 「监督所 的哪些 都录入到了工信部 里面去了」
> 「所有其他类似的监所/监督局 应该都有可能被录入到工信部,
> 工信部的**优先算作登记错误** 与外部投诉表进行对账」
## #57 : 工信部摘出为「(外)卡商」渠道
- `_EXTERNAL_PLATFORM_KEYWORDS` **移除「工信部」** → 新增 `_CARD_TICKET_KEYWORDS`
- `complaint_bucket_case` 新增最高优先级「工信部 → BUCKET_CARD_TICKET」
- 新增 `card_ticket_cond()`、`external_ticket_cond()`
- 抽出 `_reconcile_core()` —— 两渠道**共用内核**(防判据漂移)
- 新增 `card_reconcile()` / `reconcile_all()`(分渠道对账) / `remark_matched` 双渠道
## #58 : 监所/市监局误录工信部 → 软纠正
- 新增 `_SUPERVISION_PLATFORM_KEYWORDS` = 监所/市监局/监督局/市场监管…
- `supervision_platform_cond()` / `supervision_sn_subquery()`
- `effective_external_ticket_cond()` = 原外部 **OR** (工信部 ∧ SN在315是监所类)
- `effective_card_ticket_cond()` = 工信部 **AND** SN **不在**监所SN集合
- ★ **不修改 BMS 原数据** —— 仅统计/对账口径纠正(`service_item` 保持原样)
## 数据证据(生产库只读实测)
- 工单侧**没有**监所/市监局类别(11 字段搜索全 0;仅 `remark` 2 条为客户话术)
- 321 条工信部工单: **240 条(75%) 实为监所/市监局**、76 条真卡商、
10 条两者都有(真多渠道)、10 条其他平台、5 条两边都无
- 6 月至今 165 条疑似误录: **日期差 100% ≤1 天**(航城监所 164 + 宝安市监局 1)
- 月度恶化: 06=22 / 07=32 / **08=92** / 09=19
- 工单内容**无法二次复核**: remark/submit_params/order_no 全空;
handle_opinion 92%有值但只含退款金额(渠道词命中 0)
## 实测效果(近 3 个月,开发库)
| 工单侧 | 纠正前 | 纠正后 | 差异 |
|---|---|---|---|
| 外部投诉类 | 198 | **355** | **+157** |
| (外)卡商类 | 180 | **23** | **−157** |
对账结果:
| 渠道 | 文档空悬 | 工单空悬 | 文档命中 | 工单命中 |
|---|---|---|---|---|
| 外部投诉 | 169 | **7** | **69.4%** | **98.0%** |
| (外)卡商 | 77 | 13 | 16.3% | 43.5% |
- 外部投诉: 文档命中 38.8% → **69.4%**; 工单空悬 6 → **7**
- 卡商侧: 工单空悬 166 → **13**(误录被摘走后)
## 测试
- `test_external_reconcile.py` **39 项**(+7 项 #58 : 监所判据/纠正到外部/
不再算卡商/真卡商仍卡商/**监所优先于卡商**/普通外部不受影响/不改原数据)
- `test_complaint_scope.py` 同步更新 #57 断言(工信部 → BUCKET_CARD_TICKET)
- 全量 pytest 全绿; ruff 无告警; gitleaks no leaks
## 遗留(根本治理)
- **BMS 缺「监所/市监局」服务项类别** → 客服只能误选工信部
建议业务侧在 BMS 增加该类别,否则需持续软纠正
- 165 条误录清单可导出供业务在 BMS 逐笔修正
2026-09-11 13:17:23 +08:00
1ed577d0ae
chore(gitignore): 排除外部投诉对账核查导出 CSV(含PII) ( #56 )
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Successful in 8m38s
backend-test / frontend-lint-test (push) Successful in 41s
#56 对账核查需导出含真实手机号/设备SN的明细供人工比对,
这些 CSV 仅供本地核查, 严禁入库(PII 纪律, 同 #55 处置)。
- .gitignore: 新增 docs/reports/*外部投诉对账核查/*.csv 与 /tmp/csctalon-recon/
- 已用 git check-ignore 验证两个 CSV 均被拦截
2026-09-11 12:24:15 +08:00
ef86f87e42
feat(reconcile): 外部投诉对账逻辑重构 — 候选限外部投诉类 + 双边闭环 + 同步后重匹配 ( #56 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
## 用户语义(2026-09-11 权威)
> 「内部投诉(商品投诉/服务投诉)、商户号投诉(微信投诉) 跟外部投诉 **不存在对账关系**。
> 外部投诉对账 = 服务类别为空、类型是几个平台的**外部投诉**与文档数据拉齐。
> 两边数据都有意义: BMS 是**经营登记**; 文档是**接待过程/分析/处理**。」
> 「服务类别里为空的 默认都回填为 投诉; 你现在每次都去乱查。」
> 「时间上 优先确保最新数据持续对应上; 其次近3个月; 再今年; 26年以前不能也没关系。」
## 修复的 4 个缺陷
### 1(CRITICAL)匹配门控失效 —— 64% 挂到非投诉类工单
`matched_ticket_no` 只在同步瞬间按 SN 写一次、**从不重算**。
实测 918 条已匹配中 **586 条(63.8%)** 挂到了 `未领取体验套餐/开发票/网慢网卡`
(category=套餐疑问/退款/其他) —— 那是「同 SN 的处理过程关联」, 不是投诉经营登记。
### 2 判据「乱查」item
`is_complaint_ticket_cond(cat, item_col)` 用 `item ILIKE '%投诉%'` 兜底 = 冗余扫描。
实测 category 空白 32,058 + 显式'投诉' 5 = **32,063**, 与 #53 四类完备数完全吻合
→ **空白本身就是完整判据**, item 兜底已废弃(传了会被忽略+告警)。
### 3 无双边闭环视图
只有单向 `matched_ticket_no`, 无法回答「哪些工单缺过程文档」。
### 4 工单晚到不补挂
外部投诉类工单入库滞后**平均 71.1 天**(>30天 53%), 而匹配只做一次 → 永久空悬。
## 实现
- `complaint_scope.py`
· `is_complaint_ticket_cond` 去掉 item_col(废弃+告警), 新增 `external_ticket_cond`
显式表达「是投诉 ∧ 是外部」双条件
- **新增 `services/external_reconcile.py`**(373 行)
· `_sn_candidates()`: 候选**只含外部投诉类工单**(唯一口径)
· `external_reconcile()`: 双边闭环 → `doc_orphan`(漏登记) / `ticket_orphan`(缺过程记录)
· `remark_matched()`: 重算回写, 幂等; 会**清空不合规旧匹配**; 支持 dry_run
· ⚠️ 只写 matched_ticket_no/matched_at, **不动其他字段**
- `complaint_sync.py`
· `_sn_to_ticket_map` 委托 `_sn_candidates` → 同步/对账**同一集合**(消判据漂移)
· 同步有写入后**自动重匹配**(window=90d) → 落实 P0「最新数据持续对齐」
- `complaints.py`: `GET /complaints/reconcile` + `POST /complaints/reconcile/remark`
- `analytics.py`: `_ticket_category_case` 同步收敛(移除 item 兜底)
## 实测(新版口径)
| 范围 | 文档侧 空悬 | 工单侧 空悬 | 文档命中 | 工单有文档 |
|---|---|---|---|---|
| 近3个月 | 161 | 22 | 70.8% | 94.2% |
| 2026全年 | 434 | 79 | 62.3% | 89.2% |
| 26年前 | 1,484 | 1 | 0.7% | 0.0% |
`remark_matched` DRY RUN: 扫描 2655 / 新挂 1 / **变更 597**(清空非法匹配) / 仍空悬 1927
## 测试
- 新增 `tests/test_external_reconcile.py` **24 项**: 判据收敛(含"item 含投诉但
category=退款 → 不算")、候选只含外部、就近选代表、双边空悬(两侧不互相抵消)、
重匹配(晚到补挂/清非法/幂等/dry_run/不动其他字段)
- 全量 pytest 807 passed; ruff 无告警
- ⚠️ 测试用 conftest 的 PG 测试库(非 SQLite: BigInteger 不自增) + FK 需先建设备行
## 未执行(需授权)
回写 `matched_ticket_no` 会**变更 597 条**记录 —— 属改数据, 已 dry_run 预览,
**未执行**。待确认后跑 `POST /complaints/reconcile/remark`。
2026-09-11 12:20:35 +08:00
adb27514be
feat(complaints): 事件口径前端化 + 人工审核入口 ( #55 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 3m22s
backend-test / frontend-lint-test (push) Successful in 26s
## 背景
#55 后端已实现事件归并(多渠道≠重复), 但指标只在接口里, 前端看不到;
且 `needs_review`(同设备同日多电话)无处可查。本单补齐这两处。
## 后端
- 抽出 `_suppl_event_metrics(db, model, date_col, channel_col, ts, te,
with_review_rows=False)` —— 事件归并的**唯一实现**
· 原因: `/summary` 与 `/analysis` 两处都要, 复制粘贴会**口径漂移**
· `_suppl_stats` 改为委托调用, 行为不变
· `channel_col` 必传: 315 用 `platform`; 卡商表**无 platform**, 用
`complaint_group`(群名即渠道) —— 渠道是"是否重复"的**判据**, 不能省
- `/summary` 与 `/analysis` 的 `card` 对象新增事件字段:
`events` / `multi_channel_events` / `true_dup_events` / `merged_rows` /
`needs_review`。**原 total/registered/handled 不变**(保持兼容)
- 新增 `GET /complaints/dedup-review` —— 待人工判定列表
· 返回 `device_sn` / `complaint_day` / `phones` / `record_ids` /
`record_count` / `reason` / `source_type`
· 按组内条数降序(影响面大的先看)
· ★ **只读**: 不修改、不删除任何原始行
## 前端
- `ComplaintAnalysis.vue` 卡商区块由 2 格扩为 4 格:
· 「多渠道归并(非重复)」靛蓝 · 明确标注 **不计为重复**
· 「真重复(同渠道)」玫红 · 点击跳人工审核 Tab
- 新增「人工审核」Tab:
· 顶部口径说明(多渠道 vs 真重复), 避免使用者再次混淆
· 表格: 来源/投诉日/SN/涉及电话/记录数/记录ID/触发原因
· Tab 上 `el-badge` 显示待审核数
- `api/complaint.js` 新增 `complaintDedupReview`
## 实测(容器内, 2026 全年)
```
/summary card : total 268 events 249 多渠道 6 真重复 8 归并 19
/analysis card : 同上(两处口径一致 ✅ )
supplementary.external: total 1155 events 1100 多渠道 26 真重复 23
unkeyed 135 needs_review 3
/dedup-review : groups=3 (全部 external, card=0)
```
- ⚠️ `/summary` 与 `/analysis` 的 card **数值完全一致** = 实现已收敛为单一来源
## 验证
- ruff 无告警; 后端 pytest 全绿
- 前端 eslint 无告警; vitest 46/46; vite build 通过
- `docker compose up -d --build backend` 后实测(源码不挂载, 必须重建)
- gitleaks 预检 no leaks found
## 备注
- 直接调用端点函数时 `Query(None)` 默认值是 `Query` 对象而非 None, 故
`/dedup-review` 直调须显式传 `source_type`(HTTP 走 FastAPI 注入无此问题)。
2026-09-11 11:53:04 +08:00
2f4e4ecd25
feat(complaints): 投诉事件归并 — 多渠道(非重复) vs 真重复(同渠道) + 年度分层 ( #55 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 3m11s
backend-test / frontend-lint-test (push) Successful in 25s
## 用户口径(2026-09-11 最终拍板)
> 「同一个人 设备编号 电话号码 都一致,几天内 的投诉,渠道(平台)是肯定不同,
> 内容略微不同;可以归结为一个投诉,但是不能算重复数据;
> 如果 电话 sn 渠道 都一致的话 那就基本上是同一个投诉 拷贝复制的,
> 譬如说9.7号的投诉 但是当天处理没有完结,拖到10号再处理一次。」
> 「对于2025年之前的数据 简单统计处理就OK,有一定的偏差或者多重统计可以接受」
## ★纠正概念混淆
| 形态 | 特征 | 归类 | 处理 |
|---|---|---|---|
| 同一投诉·多渠道 | 同SN+同电话+7天内+渠道不同 | multi_channel | 归并1笔,**不算重复** |
| 真·重复 | 同SN+同电话+渠道也相同 | true_dup | 计1笔,标 dup_of_ids |
此前的两个错误:① 按「同天同SN」判重复 → 会误判多渠道;
② 用「编号不同」否决重复 → 但多渠道编号本来就该不同。
## 实现
- `complaint_scope.py`
· 新增 `EVENT_WINDOW_DAYS=7` / `EVENT_STRICT_MIN_YEAR=2026` / 5 个 `EVENT_KIND_*`
· 重写 `dedup_events()`: (SN, 归一化电话) 分组 + 时间窗聚类
渠道数>1 → multi_channel(归并非重复);渠道数==1且>1条 → true_dup
· 年度分层: <2026 不归并(标 legacy_unmerged)
· `needs_review`: 同设备同日多电话 → 待人工(不自动归并)
· ★修 `normalize_phone` + 新增 `split_phones`: 处理**一格多号码**与异常长度脏值
- `complaints.py`: `_suppl_stats` 新增事件口径字段(events/merged_rows/
multi_channel_events/true_dup_events/legacy_unmerged/unkeyed/needs_review),
保留原 `total` 向后兼容
## 实测(2026 全年)
| 表 | 原始行 | 归并后事件 | 多渠道 | 真重复 | 待人工 |
|---|---|---|---|---|---|
| 315 | 1,155 | 1,100 | 26 | 23 | 3 |
| 卡商 | 268 | 249 | 6 | 8 | 0 |
## 数据安全
- ★ 零删除:执行前后 external=2,655 / card=873 不变
- 统计侧归并,原始行与渠道明细全保留;`record_ids` 可回溯
- 单测 `test_no_mutation_of_input` 断言输入不被修改
## 测试
- `tests/test_complaint_scope.py` 61 项(新增 11 项覆盖事件归并)
- 后端 pytest 全绿;前端 vitest 46/46;ruff/eslint 无告警
## ★PII 处置(gitleaks 拦截 41 处真实手机号)
- 分析 CSV **不入库**:`.gitignore` 排除 `docs/reports/*投诉去重分析/*.csv`
- 导出脚本移入 `scripts/export_dedup_analysis.py`,默认输出 `/tmp`,
含 `--out` 参数;文档/测试一律用占位号段
- 仓库仅保留**脱敏摘要** `docs/reports/20260911-投诉去重分析/README.md`
## 关联
- 前置 #52 #53 #54;本单 #55
- ⚠️ 「源文档修订」结论:库**不跟随源删除**(同步只有 upsert, 无 DELETE),
源改内容反而因 dedup_hash 变化而新增行。源对齐需架构级变更,另行评估。
## 未完成
- 前端展示「多渠道事件 / 真重复」两个指标
- 人工审核入口(needs_review Tab)
2026-09-11 11:47:44 +08:00
f0a73a8547
refactor(complaints): (外)卡商 完全独立于投诉总量 + 四类完备(移除「其他」) ( #53 )
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Successful in 3m0s
backend-test / frontend-lint-test (push) Successful in 29s
## 用户 2026-09-11 口径要求
1. (外)卡商投诉 **不纳入总量管理**, 整体独立统计分析 ——
界面/指标/接口/业务逻辑 **全部单算**
2. (延续 #52 LOCKED 冲突处理)
3. 卡商似乎没计入系统工单? 需分析; 采集数据要清洗/去重/治理
4. 空白 category 是 BMS 导出漏洞, 数据很明显; 已知类型可明确为
内部/商户号/外部投诉; 还有部分应归类为**内部投诉**
5. 工单类型要区分开; 投诉对账尽量只操作**投诉的工单**
## 实证结论(附证据)
### Q3 卡商**不在工单体系**(证实用户判断)
- 工单表 `service_item` 含「卡商」= **0 行**
- 工单表 `service_category` 含「卡商」= **0 行**
- `matched_ticket_no` 只是「该 SN 恰好也有工单」的偶然关联
→ 卡商是**纯补充表体系**; 其 registered **不扣工单重叠**
(#52 的扣减口径错误, 会把卡商算少)
### Q4 四类完备, 移除「其他」桶
`channel` 字段可救回 81 行空 item:
| item | channel | 行数 | 归类 |
|---|---|---|---|
| (空) | 微信投诉 | 78 | 商户号 |
| (空) | H5 | 3 | 内部 |
| 套餐扣费申诉 | H5 | 14 | 内部(用户要求) |
全表归类(实测 32,063 完全自洽, 无「其他」):
```
内部投诉 26,866 (17148+9696+14+3+5[category='投诉'])
商户号投诉 4,462 (4384+78)
外部投诉 735 (320+278+87+50)
─────────────────
合计 32,063
```
### Q5 对账口径
投诉对账仅在 `service_category` 空白的 **32,063** 行内进行。
## 变更内容
- `complaint_scope.py`:
· `ALL_BUCKETS` 由 5 桶收为 **3 桶**(内部/商户号/外部)
· 新增 `CARD_SCOPE`(卡商独立体系标识)
· `complaint_bucket_case(item, channel)` **新增 channel 回退**
· else 兜底由「其他」改为**内部投诉**
- `analytics.py`:
· `complaint_total` **不含卡商**
· 新增 `card_total` / `card_by_category` 独立字段
· 来源桶补齐改为三类
- `complaints.py`:
· `_sources_reg_handled` 收为**三源**(卡商移除)
· 新增 `_card_reg_handled`(独立口径, 不扣重叠)
· `/summary` 与 `/analysis` 的 `card` 对象独立; 来源分布/趋势剥离卡商
- 前端 3 页: 卡商从「投诉来源」**移出** → **独立区块**
(标注「独立统计 · 不纳入投诉总量」)
## 关键修复
- `analysis` 来源分布 `_ext_suppl_net + _card_suppl_net` → 卡商曾并回外部桶
- `analysis` 趋势 `b["外部投诉"] += net` → 卡商曾混入外部趋势
- `_card_reg_handled` 返回 tuple 被当 dict 用 → TypeError
- 卡商分支作用域错误(放在 `if 外部投诉` 内 → 永不执行)
## 测试
- `tests/test_complaint_scope.py` 适配新口径: 新增 `test_channel_fallback`
与 `test_no_other_bucket`; 51 项全绿
- 后端 `pytest tests/` 全绿; 前端 `vitest` 46/46
- `ruff` / `eslint` 无告警
## 验收基线(7~9月, 实测)
```
reg_handled 键 = [内部投诉, 商户号投诉, 外部投诉, total] ← 无卡商
三源 total 登记=11,420 处理=11,138
card 独立: total=69 registered=69 handled=48
```
## 关联
- 前置: #52(投诉统计口径重构, 2f57fa2)
- ⚠️ LOCKED `A-20260902-016` 冲突加深, 需人工审核修订
- 详见 docs/arch/_pending/A-20260910-005-投诉统计口径重构.md
## 遗留(已记录, 未在本单实施)
- 卡商**组内重复 81 组** 去重(同表内同 SN+手机)
- 315 **组内重复 143 组** 去重(影响「外部投诉」桶 → 影响总量)
- 说明: 卡商与 315 的「跨表同键」53 组**不需要**去重 ——
两者已分属互不相加的体系, 强行合并反而丢失渠道信息
2026-09-11 10:24:54 +08:00
2f57fa2cd5
feat(complaints): 投诉统计口径重构 — 判据修正/(外)卡商单列/实体去重/主体归一 ( #52 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 3m24s
backend-test / frontend-lint-test (push) Successful in 56s
## 口径修正(用户 2026-09-10 澄清)
- 投诉判据: `service_category` **空白**(非 ILIKE '%投诉%'),兼容 BMS 未来输出「投诉」
- 内部投诉(商品/服务) 与 商户号投诉(微信) **不参与**外部匹配
- **(外)卡商 单列**: 从「外部投诉」拆出, 各统计口独立展示
## 新增口径单一来源
`services/complaint_scope.py` — 判据/分桶/去重/状态/主体 全部收敛于此,
analytics.py 与 complaints.py 仅作薄封装, 避免多处关键词判断漂移。
## 修复 8 个既有缺陷(均附实测证据)
- R1 判据与口径来源不一致(实测切换差异 = 0,零回归 32,063)
- R2 卡商被硬编码并入外部(`# 卡商群投诉 = 外部投诉`)
- R3 `_INCLUDE_COMPLAINT_FILES=False` 致「来源桶卡商 0 / 处理人表卡商 69」自相矛盾
- R4 SN→工单关联取「最早登记」而非「最接近投诉日期」→ 多工单 SN 错配
(平均差 21 天 → 4 天;>30 天错配 4 条 → 0 条)
- R5 315 办结集合漏 `未达成一致方案`(53)/`暂行办理`(45)(2,460 → 2,558)
- R6 卡商 `result` 混合语义(117 种取值) → 改**前缀锚定** + 三态(603/143/127)
- R7 `_complaint_subitem_case` 优先级错误(`公共服务平台`87 行被错标「服务投诉」)
- R8 主体占位脏值(`complaint_shop` 540 行为 `/`)+ 监督渠道误入主体排行
## 新增能力
- 实体级去重: `SN + 手机`(两者都命中) + 30 天窗;同一笔多渠道算 1 笔
- 状态合并: 任一渠道完成即已处理(含依据来源 工单/315/卡商)
- 被投诉主体归一: 315 company_entity→shop 回退;卡商内部群→未标注,
监督/受理渠道独立分类;真实卡商群名不被误杀
## 前端(3 页补齐卡商单列)
Today.vue / ComplaintAnalysis.vue / OperationDashboard.vue:
KPI 卡片、来源筛选、来源饼图、双柱图、标签配色
## 测试
- 新增 `tests/test_complaint_scope.py` 51 项(纯函数,不依赖 DB)
- 后端 `pytest tests/` 全绿;前端 `vitest` 46/46;`ruff`/`eslint` 无告警
## 关联
- 设计文档(PENDING): docs/arch/_pending/A-20260910-005-投诉统计口径重构.md
- ⚠️ 需人工审核修订 LOCKED A-20260902-016(卡商合并→独立;开关 False→True)
- 回归锁: A-20260901-001(LOCKED)
## 未完成(本单不含)
- 数据治理: 空白 category 是否回填(线上同步会覆盖;建议改 PG 生成列,待拍板)
- 历史数据重算落库(关联错配 457 行、315 办结 +98 行)
2026-09-10 20:46:53 +08:00
de0706d5d9
docs(review): #51 高频采集节拍可配 审查报告(0 CRITICAL; 实测修复 migration head 冲突 + 回调方法名笔误)
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Successful in 3m34s
backend-test / frontend-lint-test (push) Successful in 1m19s
2026-09-10 19:56:26 +08:00
bd138f370c
feat(scheduler): 高频采集节拍可配 — 8:00~24:00 每 30 分钟 ( #51 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
用户诉求: "把当前生产环境的高频采集的那几个表的采集时间定义为 30 分钟一次。
8:00~23:59 中间每 30 分钟一次。**在配置界面我看没法调整**。"
根因: 节拍写死在代码常量(DAYTIME_HOURS={8..23,0} 每小时), 配置界面只能只读
展示 → "源可配、节拍不可配"。本单把节拍 DB 化并开放界面配置。
## 调度模型升级: 小时集合 → 时间槽(时段+间隔)
- 时段 [start, end) 按 interval 切分槽位, 标识 "HH:MM"
- 默认 8:00~24:00 每 30 分钟 → **32 槽/天**(原 17 次/天), 数据新鲜度从 60min → 30min
- compute_slots 纯函数: 左闭右开, **绝不含 "24:00"**(归次日), 尾部残余丢弃
## 数据库
- 新表 scheduler_settings(单行 id=1): start/end/interval + DB 层 CHECK 约束
- migration b2c3d4e5f6a7(单一 head), seed 幂等不覆盖用户改动
- ⚠️ 修正: 初版 revision id 意外撞了 8 月的 a1b2c3d4e5f6 → 已改为 b2c3d4e5f6a7
## API
- GET/PUT /fetch/schedule/config (读 ManagerUser / 写 AdminUser)
- ScheduleStatus 增 slots/slot_count/start_hour/end_hour/interval_minutes/next_slot
- **保留 schedule_hours 兼容字段**(旧前端不白屏, 前后端可独立发布)
- 校验防呆: interval ≥ **5 分钟**(每轮实测 4.2min, 低于此必然重叠) / ≤180 /
0≤起<止≤24 / 空槽组合拒绝
## 调度器改造
- 触发判定/防重登记/启动补跑: 小时集合 → 槽位集合
- 窗口 = min(30, interval) → **窗口不越到下一槽**(新硬约束)
- 重启回填: finished_at 向下取整到 interval 边界(08:29→"08:00", 不误占 "08:30")
- 配置变更回调: 清空当日槽位登记并按新配置重算, 避免"改小间隔后漏槽"
- 共享文档采集改为同一槽位判定(与 BMS 永不脱钩)
- 8:00 全量兜底改为「首槽」判定(时段改成 7:00 起始时同样生效)
## 保持不变(硬约束, 有回归测试锁定)
- 低频档 6:00 **每天 1 次**(用户明确要求不变)
- 凌晨 5:00 补跑 / 专项熔断 / 半开探测 / 缺口清点 / BMS 65s 操作间隔
## 前端
- 新增 ScheduleConfigPanel: 节拍概览 + 起止/间隔编辑 + 槽位预览 + 次/天预估
- SyncDashboard 定时计划展示改为"8:00~24:00 每 30 分钟(32 次/天)"
## 实测(本地端到端)
- 调度器启动日志确认读到新节拍: 32 槽/天, 首槽 08:00 末槽 23:30
- 页面实测: 配置面板渲染正常、32 槽清单展示、可编辑保存
- 配置改 60 → 16 槽(等价旧行为), 改回 30 → 32 槽, 调度器即时读到(缓存失效生效)
- **实测发现并修复**: on_settings_changed 调用不存在的方法名
(_reload_today_completed_slots) → 异常被吞导致重算静默失败; 已补回归测试锁定
## 验证
ruff 全过 / pytest **757 passed**(新增 31 用例 + 3 个既有用例按槽位语义更新)
eslint 0 error / vitest 46 passed / npm run build ✓
架构: A-20260910-006 LOCKED + tag; A-20260907-001 加 v1.1 修订注记
2026-09-10 19:56:00 +08:00
2a45716151
docs(arch): A-20260910-006 高频采集节拍可配 LOCKED v1.0 + A-20260907-001 v1.1 修订注记 ( #51 )
2026-09-10 19:42:39 +08:00
23ff3e0d18
docs(governance): 关闭凭证强制回填 — 消除"空悬关闭条件" ( #50 )
...
gitleaks / gitleaks (push) Successful in 5s
backend-test / backend-lint-test (push) Successful in 3m24s
backend-test / frontend-lint-test (push) Successful in 35s
触发: 用户质疑"很多关闭的工单里面有很多空悬的关闭条件, 不确定是论证了关闭
还是强行关闭的"。全量审计 46 张工单后定位为**流程缺陷**(不是没干活, 是干活
没记账), 本提交落地规范补丁并开新工单 #50 跟踪存量。
## 审计结论 (docs/reports/20260910-工单关闭凭证质量-审计报告.md)
- 39 张已关闭中: 19 张验收条件零勾选, 1 张零评论(#10 ), 2 张缺"关闭条件"节
- 存在 3 批"同一秒关闭多张"(19:51:56→[15,16,17,18] 等)均未逐张回填凭证
- **逐条追溯被质疑的 5 张(7/9/10/23/24)**: 关闭条件均真实满足, 无一例强行关闭
- #23 实际充分验证(5 项各有 commit + 生产实证), 初筛因"证据写在正文"误判
- #7/#9 commit 经 git log/show 核实存在且内容匹配
- #10 唯一真正薄弱: 代码确实实现(6f86657/d587e3f), 但关闭时零留痕
- 根因: 规范 §6.2 已要求"凭证可追溯", 但**未定义凭证形态、无关闭前校验、
未写入 AGENTS.md** → 全靠自觉
## 规范补丁
- issue-workflow.md: 新增 §四点一「关闭凭证块(强制)」
- 4.1.1 标准形态(固定标题 + 逐条对应表格 + 提交号 + 结论)
- 4.1.2 关闭前强制校验清单(6 项)
- 4.1.3 历史工单例外(不重开, 补记并显式标注"事后追溯")
- 铁律新增第 6 条「禁止无凭证关闭」
- §五 状态流转表: Closed 进入条件补"+ 已写关闭凭证评论"
- AGENTS.md: 新增「工单纪律(强制)」小节 → AI 助手侧自动执行
## 工单处置
- #43~#46: 关闭前各补「关闭凭证核验」评论(逐条对应 + 可复现核验方式), 再关闭
- #10 : 补记「关闭凭证核验(补记)」— 追溯核实后维持 Closed, 不重开
- 新开: #47(模板版本回滚) #48(多worker会话约束) #49(扫码登录部署实操)
#50(本治理项: 关闭凭证强制回填)
## 关联
- 审计报告: docs/reports/20260910-工单关闭凭证质量-审计报告.md
- 前置: #43 #44 #45 #46 (接入中心与导航重组, 本批首用新规范关闭)
2026-09-10 19:05:48 +08:00
fc6bc104d1
feat(access): 接入中心 + 页面内扫码登录 + 导航信息架构重组 ( #43 #44 #45 #46 )
...
gitleaks / gitleaks (push) Successful in 6s
backend-test / backend-lint-test (push) Successful in 6m16s
backend-test / frontend-lint-test (push) Has been cancelled
四单一次落地(架构 A-20260910-001~004 均 LOCKED), 主题: 把"对外接入"从分散
配置收敛为统一入口, 并消除导航层的历史混乱。
#43 外部 Token 生命周期与配置包
- 吊销从「终态」改为「可逆」: 新增 POST /external/tokens/{id}/enable
- 新增 POST /external/tokens/{id}/rotate: 换钥即失效(明文不可回看的合法补救)
- 新增 GET /external/tokens/{id}/setup-package: 把 token+地址+提示词+规范
拼成一段可复制 Markdown, 按 scope 裁剪
- 安全: 配置包中 token 固定为占位符, **不返回明文**(有专项断言测试)
#44 接入中心(统一入口)
- 新页 /system/access-center, 4 Tab: 登录态 / 外部Token / 推送配置包 / AI与模型
- 新表 integration_profiles(1 migration + 3 seed): 接入物料模板可维护
- AI 分层可见: 模型接入(脱敏只读)/铁律(只读)/口径卡(只读)/关注点(可编辑)
- 安全: /ai/model-config 只回"是否配置+长度", 有断言"响应不含 api_key 字段"
#45 共享文档页面内扫码登录
- 补齐最大断点: 页面原本只提示"失效需重新扫码"却无扫码入口, 只能 SSH 跑脚本
- 新增 start/status/confirm/cancel + states 健康度(5 端点)
- 复用 captcha_session 骨架; 同平台单例锁防并发覆盖; confirm 前轻量校验,
不通过**不覆盖**旧登录态; 覆盖前备份 .json.bak
- Dockerfile 补 xvfb/xauth; 无显示服务降级 501 + 一键命令(不抛 500)
#46 导航信息架构重组
- 新增 web/src/config/menu.js 作为**菜单唯一配置源**, 桌面+移动端共用 MenuTree
渲染(此前两端各自硬编码, 移动端只有 2 项, 严重不一致)
- 系统管理 13 项平铺 → 3 层分组(组织与权限/数据与接入/审计与配置)
- 一级菜单 13 → 6 项; 运营看板命名统一; 报表中心退出菜单
- 权限配对: 读接口 CurrentUser → ManagerUser, 写接口 → AdminUser, 前端按钮同步
按角色隐藏(避免"看得见点不动"/"看不见却能调")
修复(审查阶段实测发现)
- HIGH: MenuTree.nodeChildren 漏合并 node.children → 菜单整块不渲染(空白侧边栏)。
单测全绿但线上白屏 —— 根因是只测了配置数据、没测渲染。已补 MenuTree.spec.js
渲染层测试(6 用例, 首条即"至少渲染出菜单项")
- MEDIUM: 服务层 db.commit() 绕过 conftest 回滚 → 测试二次运行必失败。改用唯一
标识 + 显式自洁
- LOW: vitest 缺 @ 别名; jsdom 30 无 localStorage → 补 test-setup.js
验证: ruff 全过 / pytest 682 passed / eslint 0 error / vitest 46 passed / build ✓
浏览器实测: 菜单三层分组渲染正确, 接入中心 4 Tab 可加载, 配置包变量替换正确
审查报告: docs/reports/20260910-接入中心与导航重组-审查报告.md (0 CRITICAL)
2026-09-10 18:57:21 +08:00
0bde05743e
feat(趋势): 投诉/运营/现金流趋势图支持按日/周/月粒度切换
...
gitleaks / gitleaks (push) Has been cancelled
纯前端实现,复用后端已返回的按日数据做周/月汇聚,零后端改动:
- 投诉分析: 外部投诉趋势、三源趋势新增 按日/周/月 切换(复用 aggregateSeries/aggregateMultiSeries)
- 运营分析: 退款趋势(金额+笔数)新增粒度切换
- 现金流分析: 现金流趋势、退回钱包vs提现 两图新增粒度切换
周=ISO自然周, 月=自然月; 图例 X 轴标签过长时自动旋转
2026-08-31 19:28:02 +08:00
a5abadf644
docs(frp): 新增 OpenResty perip 限流调优 FAQ(§12.3)
...
gitleaks / gitleaks (push) Has been cancelled
- 记录 error.log "limiting connections by zone perip" 的排查与解决: 1Panel 自动生成站点配置 limit_conn perip 25, 办公 NAT 共用 IP 并发打满导致静态资源加载不全
- 给出两种解法: 调大 limit_conn perip 或对 /assets/ 静态资源豁免(独立 zone)
- 标注该站点配置会被面板重新生成覆盖的注意点, 以及海外扫描器被拦属预期
- 原 §12.3~12.6 顺延为 §12.4~12.7, 交叉引用同步修正
2026-08-31 19:04:21 +08:00
6822371573
fix(投诉): 三源趋势纳入workbuddy推送数据并按日去重
...
gitleaks / gitleaks (push) Has been cancelled
- 三源趋势(/summary、/analysis)现纳入外部投诉(315表)与商户号投诉(卡商表)推送数据,按投诉日期计入;已关联本范围内工单的记录去重,与总数口径一致
- 设备审计时序新增 设备套餐/手动退款/闪猫退款 三张表,补全SN搜索遗漏
- 修复投诉审计页设备时序读取字段错误(events→items),并改用后端type_meta渲染类型
2026-08-31 18:53:28 +08:00
211f8dde47
feat(缓存): 日期感知TTL+近N天失效+前端内存缓存优化历史窗口冷查询
...
gitleaks / gitleaks (push) Has been cancelled
生产缓存审核优化: 历史窗口(end<今天)TTL拉长到24h(cache_ttl_for), 同步/导入失效从全量前缀改为近N天(cache_invalidate_recent_days)保留历史缓存, cached_json支持callable TTL, Today.vue按viewMode|start|end内存缓存切回同窗口秒开。新增审计报告文档, 更新缓存加速规范至v1.1。
2026-08-31 13:49:17 +08:00
d04b24c73b
perf(cache): 补齐看板缓存与失效链路 (2026-08-31)
...
gitleaks / gitleaks (push) Has been cancelled
## 背景
运营看板(OperationDashboard)日/周视图与投诉决策看板(ComplaintAnalysis)存在缓存缺口:
- `/complaints/subject-analysis`(被投诉主体分析) 与 `/complaints/analysis`(投诉决策分析)
两个重查询接口**完全无缓存**,每次切换粒度/刷新都全量扫库聚合。
- 多个数据写入入口未失效看板缓存,导致同步/上传后看板读取旧聚合结果。
## 改动
### 1. 接口层补缓存 `backend/app/api/v1/complaints.py`
| 接口 | 缓存 Key | TTL |
|---|---|---|
| `subject-analysis` | `complaint:subject:{start}:{end}` | 120s |
| `analysis` | `complaint:analysis:{start}:{end}` | 120s |
### 2. 预热器补齐 `backend/app/services/cache_warmer.py`
- `_warm_complaints`(今日/本周视图预热):新增 `complaint:subject` 与 `complaint:analysis` 预热
- `_warm_reports`(月报/同步后预热):新增两接口近30天窗口预热
- key 对齐要点:投诉决策看板默认窗口为 `dayjs().subtract(30,'day')~today`,
用独立变量 `s30 = _days_ago_str(30)`,与月报 29 天窗口区分,保证 key 逐字符一致可命中
### 3. 写入入口补缓存失效(规范 §九:每个写入入口必须失效)
| 入口 | 失效 pattern |
|---|---|
| `bms_sync.py` post-sync | 原 `today:*` → 增加 `complaint:*` |
| `uploads.py` 导入后台任务 | 原**无** → `today:*` + `refund:*` + `complaint:*` |
| `manual_refund_sync.py` 手动退款推送 | 原**无** → `refund:*` + `today:*` |
> `sync_task_manager` 统一失效(`today:*`/`refund:*`/`complaint:*`)已覆盖 SCRM/投诉推送编排路径,无需重复。
## 影响
- 运营看板/投诉决策看板首次访问速度提升,切换日/周/近30天命中热缓存
- 同步/上传后看板不再读到最长 120s 的旧聚合数据
- 重启后端后新缓存条目自动按需生成;管理员也可在缓存管理页手动重建
2026-08-31 13:26:42 +08:00
1484aee054
fix(sync): 凌晨补跑由 3:00 调整到 5:00, 避开 BMS 平台凌晨维护时段
...
背景(2026-08-31 生产日志审计): 从 BMS 抓取数据时凌晨有周期性访问失败,
应为 BMS 平台凌晨维护阶段; 半夜同步数据意义不大, 且失败会触发抓取超时/熔断。
- bms_scheduler.py: CATCHUP_HOUR 3→5, 更新模块 docstring 与相关注释
(5:00 对前一天整体补跑, 语义不变; 白天 8-23/0 每小时 + 8:00 近7天全量兜底不变)
- 同步修正过时文档/注释中的旧调度描述(8/12/16/20 每天 5 次 → 当前实际调度):
fetch.py / scrm_sync_service.py / README / deploy/frp/README /
docs 数据同步与加工说明 / 数据源接入与缓存加速规范 / 规划设计
- 移除 README 中不存在的 BMS_SCHEDULE_HOURS 变量(实际为代码硬编码常量)
日志时区: main.py 已有 _beijing_localtime 转换(历史提交 3baab1c ), 无需改动;
本地 app.log 显示 UTC 为旧版本产物。
验证: py_compile 通过; get_errors 无; 全局无过时调度描述残留。
2026-08-31 12:07:14 +08:00
9d01c6c189
fix(deploy): 增加 .env 上传路径变量守卫 (2026-08-31 事故复发防护)
...
gitleaks / gitleaks (push) Has been cancelled
install-server.sh 此前只在"缺失 DB_DATA_DIR/ENV_FILE"时自动补齐 PROD_DIR_VARS,
不处理"错误值已存在"(如 UPLOAD_DIR=./uploads 相对路径指向容器内空目录);
upgrade.sh 直接 --env-file 完全不做 .env 内容校验。
补充轻量守卫(仅告警, 不自动改, 防覆盖用户配置):
- 检测已废弃复数变量 UPLOADS_DIR/BROWSER_STATES_DIR
- 检测单数变量为相对路径(./)或 /app 开头(容器内空目录)
- install-server.sh 放在"已存在 .env 沿用"之前; upgrade.sh 在切换软链前
- README §8.1 ③ 补充守卫行为说明
验证: bash -n 通过; 守卫对错误值命中、对合法绝对路径不误报。
2026-08-31 11:54:52 +08:00
11563321f6
fix(deploy): 收敛发布链路纯净性与 .env 路径变量统一 (2026-08-30 部署审计)
...
gitleaks / gitleaks (push) Has been cancelled
生产部署审计发现并修复 4 类问题:
1. 发布链路行为漂移, git 模式带入开发杂质
- upgrade.sh --git clone 后清理 .git/.gitea/.dsh/.trae/.workbuddy/docs/scripts/
backend/scripts/backend/tests/backend/uploads/VERSION.tmp 等开发/AI 工具产物
- install-server.sh --src-dir 复制分支 tar 流补充同样排除清单
- package.sh 排除清单补齐 dotfile 并去掉 docs, 与 git 模式同一口径
2. .env 路径变量单复数不一致(生产 UPLOAD_DIR=./uploads 为失效残留)
- 全链路统一为单数 UPLOAD_DIR/BROWSER_STATE_DIR(与后端 settings 同名)
- 涉及 docker-compose.yml 挂载、env.production.example、install-server PROD_DIR_VARS、
backup.sh/restore.sh 读取、根/backend .env.example
- 复数 UPLOADS_DIR/BROWSER_STATES_DIR 废弃(仅留注释说明)
3. postgres 数据目录属主不可穿透(uid 999 显示为 dnsmasq)
- compose db 服务固定 user: "999:999"
- install-server.sh 建目录后 chown -R 999:999 data/postgres
4. 运维文档: deploy/README.md 新增 §8.1 部署巡检与残留清理
(孤儿卷清理/杂质删除/变量收敛/属主修正 4 步人工收尾)
验证: bash -n 全部通过; docker compose config 生产路径映射正确; config.py 编译通过。
2026-08-31 11:38:08 +08:00
eed2eada3a
feat(web): 手机端抽屉菜单补充专项分析入口
...
gitleaks / gitleaks (push) Has been cancelled
移动端抽屉菜单此前仅开放今日看板/运营看板/数据采集, 缺少专项分析(退款/挽回/投诉), 管理层手机端无法进入这三个发布页。
新增 el-sub-menu「专项分析」(authStore.isManager 可见), 含退款分析/挽回分析/投诉分析, 与桌面端一致。
2026-08-31 09:22:29 +08:00
2bf37e87a0
feat(web): 发布页移动端适配 + 工作台/待办移入头像菜单
...
gitleaks / gitleaks (push) Has been cancelled
移动端响应式优化(<=768px):
- 挽回分析: 筛选Tab精简文案(今日/人员/设备/类型), 图表高度降为250px, KPI卡紧凑, 表格横向滚动
- 退款分析: DecisionOverview/RefundWithdrawal 加 isMobile + 移动端样式
- 投诉分析: 加 isMobile + 描述文字/控件精简, 底部Tab固定
- 人员/设备/类型趋势子组件: 加移动端表格滚动与紧凑样式
- 全部页面经375px视口验证无横向溢出, 侧边栏自动隐藏为汉堡菜单
菜单调整:
- 工作台(/dashboard)、我的待办(/agenda) 从侧边栏移入用户头像下拉菜单(带图标/分隔线), 退出登录置底
2026-08-30 22:34:22 +08:00
a00671f17d
优化挽回今日趋势
gitleaks / gitleaks (push) Has been cancelled
2026-08-30 22:10:47 +08:00
0bdf7ccb5f
修复 挽回分析 显示时间问题以及美化趋势.
gitleaks / gitleaks (push) Has been cancelled
2026-08-30 22:03:38 +08:00
efd9d1e511
feat(web): 菜单按构建模式 debug/release 收敛可见性
...
gitleaks / gitleaks (push) Has been cancelled
新增 env.js 可见性工具, 侧边栏仅核心菜单全环境显示(运营看板/工作台/我的待办/专项分析3项/系统管理), 其余菜单 debug-only; 打通 Dockerfile/compose/package.json 构建链路(WEB_BUILD_MODE 控制), 生产默认 release 收敛, 内测 debug 全量
2026-08-30 21:31:44 +08:00
df14d3ee1b
fix(users): 删除用户前清理外键关联, 修复被引用用户删除500
...
gitleaks / gitleaks (push) Has been cancelled
删除用户 cs001 等被 strategy_read_receipts/wechat_accounts 等表
(无 ON DELETE 动作)引用时, 直接 db.delete(user) 触发 ForeignKeyViolation
→ 未捕获 → 500。
- NOT NULL 关联行显式删除(已读回执/微信绑定/通知)
- 可空关联列显式置 NULL(保留业务数据, 解绑用户引用)
- 每步 SAVEPOINT 独立执行, 单步失败不影响整体
- 外层捕获 IntegrityError 兜底, 转 400 而非 500
2026-08-30 20:38:58 +08:00
112d513252
docs(deploy): 更新部署手册到v1.3并新增运维速查章节
...
gitleaks / gitleaks (push) Has been cancelled
更新版本号到v1.3,新增高频运维指令速查章节,补充常见问题排查项
2026-08-30 19:55:52 +08:00
3baab1c4c6
fix: 修复多类线上问题并优化同步逻辑
...
gitleaks / gitleaks (push) Has been cancelled
1. 修复bms_sync.py中Refund未导入导致宽表不更新的问题
2. 修复bms_fetcher.py中异步任务泄漏问题,增加finally清理download_task
3. 修复data_parser.py中辅助函数作用域错误,将CSV修复工具函数提升至模块级
4. 为定时同步增加失败专项单次重试机制,避免单日数据缺口
5. 调整日志时区为北京时间,统一日志与调度时间显示
6. 优化nginx配置:屏蔽favicon.ico 404、增大代理缓冲区、调整请求体缓冲
7. 新增2026-08-30部署审计报告文档
2026-08-30 19:26:44 +08:00
823348fa28
fix(frp): 修正 compose 镜像仓库名为 frps/frpc, 默认 0.71.0-alpine
...
gitleaks / gitleaks (push) Has been cancelled
- 修正统一 compose 镜像仓库名: snowdreamtech/frp(不存在)→frps/frpc, 默认 0.71.0-alpine
- 本地 frpc compose/cleanup 脚本镜像 tag 同步为 0.71.0-alpine
- README 简化国内拉镜像说明为推荐加速器方式
2026-08-30 15:36:32 +08:00
cc27c0f736
fix(frp): compose 镜像支持 FRP_IMAGE 环境变量, 解决国内拉 Docker Hub 超时
...
gitleaks / gitleaks (push) Has been cancelled
- 统一 compose 与本地 frpc compose 的 image 改为 ${FRP_IMAGE:-...}, 支持国内代理源镜像
- README §3.4 补充国内拉镜像超时解决方案(FRP_IMAGE 代理源/tag 匹配坑/加速器)
2026-08-30 15:19:56 +08:00
4552dae470
feat(frp): 新增 frp 一键清理脚本(cleanup-frps/cleanup-frpc)与卸载文档
...
gitleaks / gitleaks (push) Has been cancelled
- 新增 cloud/cleanup-frps.sh: 云端 frps 一键清理(旧 docker run 与统一 compose 双方式, 默认只停服务, --purge 连配置/日志/镜像删除)
- 新增 local/cleanup-frpc.sh: 本地 frpc 一键清理(binary systemd + docker 双方式, --purge 连安装目录/镜像删除)
- README 更新: 新增 §9 卸载/清理章节与 §4.3 脚本参数参考, 原 §9/10/11 顺延为 §10/11/12, 文件清单补充, 交叉引用同步
2026-08-30 14:32:52 +08:00
e6367e2743
feat(frp): 新增统一 docker-compose.yml 双端共用部署(profile 区分 frps/frpc)
...
gitleaks / gitleaks (push) Has been cancelled
- 新增 deploy/frp/docker-compose.yml: 一份文件, 服务端 --profile frps 起 frps, 客户端 --profile frpc 起 frpc
- 环境变量 FRPS_CONFIG/FRPC_CONFIG 可覆盖配置路径, 默认读 compose 同目录模板
- 与脚本方式(deploy-frps.sh/install-frpc.sh)平行, 版本/端口安全约定保持一致
- README 更新: 新增 §3.4 compose 方式, 顺延 3.5~3.7 小节编号, 更新文件清单与版本号 v2.1
2026-08-30 14:27:59 +08:00
1527777048
docs(frp): 新增实战部署实录与排障 FAQ + 防 frp 暴露兜底配置
...
gitleaks / gitleaks (push) Has been cancelled
- README §11: 全流程部署顺序 / 排障 FAQ(7000 超时、proxy already exists、
IP 访问提示页、证书申请域名解析 bug、DomainRecordDuplicate、Safari 打不开、
备案拦截、Host 分流) / frp 架构下证书申请推荐(ZeroSSL+DNS) /
不暴露 frp 兜底 / 域名证书核对清单 / 上线收尾
- 新增 openresty-default-server.conf: default_server 兜底(return 444/自定义 403),
拦未匹配域名/IP 访问, 不暴露 frp; 配套 ufw deny 8080
2026-08-29 01:37:06 +08:00
f596adb8e3
feat(frp): 云端 TLS 入口默认改为 1Panel OpenResty
...
gitleaks / gitleaks (push) Has been cancelled
- deploy-frps.sh 新增 --mode openresty(默认, 1Panel OpenResty 反代 frps) 与
--mode nginx(宿主 apt nginx, 原方案) 双模式
- 新增 --skip-frps / --no-install-docker 参数; nginx 模式才安装宿主 nginx
- 新增 deploy/frp/cloud/openresty.conf: 1Panel OpenResty 反代模板(不含
listen 443/证书, 由 1Panel 自动签发 Let's Encrypt; 反代 127.0.0.1:8080)
- README 同步: 架构/流量路径/前置/端口表/§3.2 三种部署方式/参数表/证书/FAQ/文件清单
2026-08-28 23:14:48 +08:00
9dfd7cbee1
fix(deploy): docker-compose 固定 project name=csctalon 防止目录名漂移导致容器冲突
...
gitleaks / gitleaks (push) Has been cancelled
部署脚本在 /opt/csctalon/current(软链指向 releases/<版本>)下运行 compose, 不加 name 时 project name 取目录名, 每次切版本目录名变 → 容器/卷归属不同 project, 重装/升级报 container name already in use 冲突、产生 920f48e_* 残留卷。顶层 name: csctalon 固定 project, 任何目录跑 compose 都用同一 project。
2026-08-28 17:45:38 +08:00
b9f77dfde1
fix(deploy): 移除误提交的 docker-compose.override.yml 并加入 .gitignore
...
gitleaks / gitleaks (push) Has been cancelled
docker-compose.override.yml 仅用于本地 macOS colima(改用 named volume 解决 bind mount 权限), 注释明确"不影响服务器部署、不提交", 但实际被提交(commit 5685b2d )。导致服务器 clone 时拿到该文件, Docker Compose 自动合并 → 生产环境 db/redis 改用 named volume 而非 /opt/csctalon/data bind mount, 与已有容器配置不一致 → compose up 报 "container name /csctalon-db already in use" 冲突。现从 git 移除跟踪(保留本地文件)并加入 .gitignore。
2026-08-28 17:42:55 +08:00
920f48ed9c
fix(deploy): restore.sh 容忍 pg_restore DROP 阶段已知无害错误并做数据完整性校验
...
gitleaks / gitleaks (push) Has been cancelled
groups/specialties 表的 ix_groups_name/ix_specialties_code/ix_specialties_name 在 create_all 环境(唯一索引)与 alembic 迁移环境(唯一约束)间命名相同但对象类型不同, pg_restore --clean 的 DROP INDEX 会报"cannot drop index ... because constraint requires it"且退出码非0, 导致 set -e 下 restore.sh 在 pg_restore 后中断、后续启动服务不执行。现改为: 不因 pg_restore 退出码中断, 恢复后校验 users/refunds 表行数确认数据真实导入, 校验失败才中止(避免假成功)。
2026-08-28 17:39:36 +08:00
7e195015cb
fix(deploy): install-server 已存在旧 .env 缺生产目录变量时自动补齐而非报错退出
...
gitleaks / gitleaks (push) Has been cancelled
旧模板生成的 .env(根目录 .env.example 无 DB_DATA_DIR/ENV_FILE)重跑安装会卡在"缺少生产关键变量"报错。现改为自动补齐 7 个生产目录变量(保留用户已有密钥配置),并对占位符密钥给出明确警告。README 常见误用表补充该场景。
2026-08-28 17:02:49 +08:00
1c408b9e79
钱包提现的统计口径发生变化,数据源变动了标准 修订统计方式.
gitleaks / gitleaks (push) Has been cancelled
2026-08-28 16:20:39 +08:00
f2d7fc36b2
补充部署文档,支持frp 反向代理部署.
gitleaks / gitleaks (push) Has been cancelled
2026-08-28 10:39:45 +08:00
0d438f26d1
feat(deploy): 新增frp内网穿透部署方案,数据留内网+云端转发
...
gitleaks / gitleaks (push) Has been cancelled
- 新增 deploy/frp/ 完整方案: 云端 frps(nginx TLS 终止+端口转发) + 本地 frpc 出站隧道
- 本地仅跑业务容器, 业务数据零落地云端; 内网直连不受影响
- 新增 sync-backup-to-cloud.sh 每日异地备份, 云端可作灾备恢复
- README 增加两种部署形态对比表(deploy/frp 为主 + deploy 整体上云为灾备)
- deploy/env.production.example 更新 CORS 说明支持 frp 外网域名
- .gitleaks.toml: hardcoded-strong-password 规则 allowlist 改 regexTarget=line,
放行 REPLACE_ 模板占位与 shell 参数展开, 修复 frp 模板误报
2026-08-27 16:49:53 +08:00
1602ac62c0
feat: 添加网站备案信息支持,更新应用名称与描述
gitleaks / gitleaks (push) Has been cancelled
2026-08-27 13:12:55 +08:00
60a5de679a
feat(complaint-analysis): 支持按日/周/月汇聚的投诉主体趋势图表更新
gitleaks / gitleaks (push) Has been cancelled
2026-08-26 16:32:15 +08:00
9b2d1eb96d
Update documentation and code for database migration and role management
...
gitleaks / gitleaks (push) Has been cancelled
- Updated Alembic migration documentation to reflect new version (v1.1) and current head (73a7922ec598), including migration history and key milestones.
- Revised action item tracking document with recent updates and completed items, aligning with the latest code changes.
- Enhanced database structure documentation to include new tables and updated field descriptions, increasing total tables from 56 to 86.
- Modified Persons.vue to include new roles: '专员' (specialist) and '专项组长' (specialty lead) in both table display and form selections, ensuring consistency across the application.
2026-08-26 14:47:08 +08:00
e0757efc8b
feat(analytics): 新增退款明细筛选、风险概览与污染数据清理能力
...
gitleaks / gitleaks (push) Has been cancelled
1. 后端新增refund-withdrawal/facets接口获取筛选候选项
2. 优化退款人展示逻辑,优先使用审核人
3. 新增姓名字段污染校验,拦截时间戳/非法字符/超长值
4. 前端新增涉诉/操作人/退款原因筛选与风险统计展示
5. 新增批量清理污染操作员字段的脚本
2026-08-26 12:44:30 +08:00
98a653f5e1
refactor(bms_fetcher): 替换旧CSV修复逻辑为通用data_parser方案
...
gitleaks / gitleaks (push) Has been cancelled
将原手动逐行解析的CSV修复逻辑替换为新增的repair_csv_blocks工具函数,实现贪婪拼接截断行、合并多余字段和统一校验流程,同时新增评估脚本用于对比修复效果
2026-08-26 09:41:05 +08:00
89db5737b1
feat: 新增投诉积压导出、主体趋势图表与缓存预热优化
...
gitleaks / gitleaks (push) Has been cancelled
1. 新增投诉积压明细导出接口,支持同分页口径全量导出Excel
2. 新增被投诉主体按日堆叠趋势图表,商户号内细分不合并主体
3. 重构缓存预热逻辑,独立于调度器启动避免冷加载超时
4. 修复alembic日志覆盖导致应用日志丢失的问题
5. 归一化被投诉主体名称,合并云客猫相关商户投诉
2026-08-26 07:22:11 +08:00
5debb8b084
fix(redis cache): 修复pydantic对象缓存序列化导致的500问题
...
gitleaks / gitleaks (push) Has been cancelled
重构缓存JSON序列化逻辑,新增_json_default处理函数,将pydantic模型、日期、Decimal等类型正确序列化为标准JSON格式,同时跳过历史投毒的非dict缓存条目避免校验失败
2026-08-25 17:11:28 +08:00
d4487e7bee
feat(cache-admin): add cache stats dashboard and rw prefix support
...
gitleaks / gitleaks (push) Has been cancelled
1. 新增缓存统计接口和工具函数,支持按前缀统计缓存键数量、TTL分布和即将过期键数
2. 新增提现全链路(rw:)缓存前缀分组,完善文档规范
3. 重构缓存管理页面,新增缓存清单表格和大盘统计指标
2026-08-25 16:19:26 +08:00
9c8e31347c
feat: 新增缓存管理体系,实现看板缓存自动预热与管理员强制重建
...
gitleaks / gitleaks (push) Has been cancelled
### 变更详情
1. **后端核心实现**
- 新增缓存预热模块`cache_warmer.py`,支持同步后/启动后/空闲期三时机自动缓存填充
- 新增管理员缓存管理API,支持按范围失效/重建缓存并记录操作日志
- 为现有看板接口统一添加路由层缓存,优化访问性能
- 修复投诉同步后缓存未失效的问题,新增缓存失效兜底机制
- 新增Redis连接失败告警机制,提升运维可观测性
- 优化退款工单批量查重逻辑,减少数据库查询次数
2. **前端与路由**
- 新增系统菜单缓存管理页,支持可视化缓存操作
- 新增缓存管理路由与API接口注册
- 为投诉相关接口添加缓存装饰器
3. **文档与配套**
- 新增《数据源接入与缓存加速规范》文档,标准化缓存接入流程
- 更新项目变更日志,记录缓存体系迭代内容
2026-08-25 15:49:45 +08:00
fed384cb4e
feat(complaint-analysis): 优化投诉分析页面展示与功能
...
gitleaks / gitleaks (push) Has been cancelled
1. 新增设备SN点击跳转审计页面的功能
2. 更新投诉水位的文案与图例说明
3. 重构被投诉主体分布图表为堆叠柱状图并添加悬浮提示
4. 将主体趋势图改为带面积填充的折线图
5. 完善来源饼图的兜底数据逻辑
6. 调整部分图表样式与布局
2026-08-25 13:41:53 +08:00
cb8c72f7a0
feat(scheduler): 早8点近7天全量兜底同步, 回填BMS分批滞后数据
...
gitleaks / gitleaks (push) Has been cancelled
BMS 数据按登记时间分批滞后 1~2 天(当天只出 20%~60%, 后续补全)。
调度调整: 8:00 改为近 7 天窗口全量兜底(忽略熔断, 失败不写熔断,
与 3 点补跑同性质); 9~23/0 点保持当天窗口增量(带专项熔断过滤);
3:00 前一天补跑不变。窗口天数 FULL_WINDOW_DAYS=7 可调。
2026-08-25 13:28:56 +08:00
2d6da2d4a3
fix(deploy): 生产回退纯 HTTP, 移除 HTTPS 强制证书依赖
...
gitleaks / gitleaks (push) Has been cancelled
新 nginx.conf 强制 443 + kf.example.com 示例证书, 生产无证书导致 web
容器 emerg 启动失败。当前内网/纯 IP 场景回退纯 HTTP(80 直服),
如需 HTTPS 按 deploy/README.md §10 落地。
2026-08-25 12:58:59 +08:00
53bf55e89d
feat: add system event notification, source group and complaint analysis features
...
gitleaks / gitleaks (push) Has been cancelled
1. 新增系统事务通知模块: 记录同步/推送等系统事件, 提供未读角标和事件列表页
2. 新增数据源分组功能: 同域数据源共享登录凭证, 支持组级配置与单源覆盖
3. 新增被投诉主体维度分析接口与看板展示
4. 优化退款看板图表为并列柱形对比展示
5. 固化投诉分类口径与涉诉判定逻辑
6. 新增complaintBacklog接口并更新今日看板的积压统计逻辑
7. 完善sync_source_config的凭证继承逻辑与自动归组功能
2026-08-25 12:49:05 +08:00
a30212feb4
docs(deploy): update deployment docs to v1.2 and add HTTPS cert section
...
gitleaks / gitleaks (push) Has been cancelled
update the version tag and add digital certificate deployment content to the manual
2026-08-24 18:32:12 +08:00
826624c842
feat: 新增HTTPS支持与部署文档更新
...
gitleaks / gitleaks (push) Has been cancelled
1. 为nginx配置443端口监听与HTTP强制跳转HTTPS
2. 挂载SSL证书目录与开放443端口
3. 补充完整的HTTPS部署运维文档
4. 更新部署流程文档,新增git直连部署方式
2026-08-24 17:24:15 +08:00
b572fcce55
build(backend): fix chromium install failure in mainland China
...
gitleaks / gitleaks (push) Has been cancelled
1. add playwright download mirror env to speed up chromium install
2. fix doc link typo in deploy guide
2026-08-24 16:34:16 +08:00
eda2d499d4
fix(deploy): 修复 tar exclude 误删 web/src/views/data 源码目录
...
gitleaks / gitleaks (push) Has been cancelled
--exclude='data' 会匹配任意层级的 data 目录, 把 web/src/views/data/
(Ingest.vue 等 14 个前端源码)排除出发布包, 导致服务器前端构建
报 Ingest.vue ENOENT。根 data/logs 不在打包清单中, 无需 exclude,
直接移除该规则。
2026-08-24 15:01:10 +08:00
c2a880799a
fix(deploy): 支持 bash -s 参数透传并修复 compose 找不到 .env
...
gitleaks / gitleaks (push) Has been cancelled
- bootstrap.sh: 支持 --env-file/--restore 位置参数(经 sudo bash -s 透传,
避免 sudo 清空环境变量导致参数丢失)
- install/upgrade/rollback: 切换版本后软链生产 .env 到 current/.env,
兜底 compose env_file 相对目录解析; 沿用 .env 时校验 DB_DATA_DIR/ENV_FILE
2026-08-24 14:54:54 +08:00
c96def6611
chore: 优化docker-compose和生产env示例的配置
...
gitleaks / gitleaks (push) Has been cancelled
1. 给docker-compose添加了ENV_FILE变量支持,支持自定义.env文件路径
2. 更新了生产环境示例配置,新增ENV_FILE说明和默认配置
3. 调整了示例配置的注释排版
2026-08-24 14:38:36 +08:00
79157d161a
feat(deploy): add bootstrap script and support git-based deploy/upgrade
...
gitleaks / gitleaks (push) Has been cancelled
- 新增 bootstrap.sh 实现一键从gitea拉取安装部署
- 为upgrade.sh新增--git模式支持直接拉取代码升级
- 为install-server.sh新增--src-dir参数支持使用本地代码目录
- 优化升级和安装流程的参数处理与提示信息
2026-08-24 13:53:34 +08:00
a986c7f987
feat(deploy): add full deployment & operation script suite
...
gitleaks / gitleaks (push) Has been cancelled
Add complete set of deployment, upgrade, rollback, backup and restore scripts, plus Chinese deployment documentation:
1. Add package.sh/upload.sh for local packaging and uploading
2. Add install-server.sh/upgrade.sh/rollback.sh for server-side operations
3. Add backup.sh/restore.sh for data backup and recovery
4. Add detailed Chinese deployment documentation
5. Add env.production.example production environment template
6. Add backups/ to gitignore
7. Make docker-compose.yml path configurable via .env
2026-08-24 13:48:13 +08:00
7a66b79c00
refactor(export): 统一处理blob响应并升级退款导出功能
...
gitleaks / gitleaks (push) Has been cancelled
1. 修复前端导出接口兼容不同响应格式的blob数据
2. 后端退款导出升级为多sheet模式,覆盖四路经营退款类型并添加按日汇总
2026-08-24 12:36:52 +08:00
7032b2a013
feat(complaint): add backlog unprocessed complaint audit analysis feature
...
gitleaks / gitleaks (push) Has been cancelled
新增积压未处理投诉审核分析功能,统一使用归一化状态非已完成作为积压口径,包含统计看板、多维度筛选、分页明细以及核查留痕能力,对齐前后端数据口径避免统计偏差。
2026-08-24 12:06:15 +08:00
d638c1f39b
docs,refactor,feat: 更新文档、前端提示和后端退款分析逻辑
...
gitleaks / gitleaks (push) Has been cancelled
1. 补充upsert策略支持的manual_refunds复合键表
2. 更新前端退款监控面板的业务提示文案,优化分析逻辑描述
3. 重构后端退款分析报告,新增未关联工单占比警示、涉诉占比分级解读和钱包水位多场景分析
4. 优化手动退款蓄水的提示文案
2026-08-24 10:28:37 +08:00
54312cfdc1
docs(manual_refund): 升级涉诉口径并完善手动退款文档
...
gitleaks / gitleaks (push) Has been cancelled
1. 新增manual_refunds表的复合去重键说明,更新同步规则与全量对账支持
2. 升级退款决策概览涉诉口径,增加备注涉诉分类
3. 完善SCRM时区说明与手动退款时间字段定义
2026-08-24 10:16:05 +08:00
c5e76c0c2b
refactor(manual-refund): 修改手动退款去重逻辑与全量同步功能
...
gitleaks / gitleaks (push) Has been cancelled
1. 更新手动退款去重键为(登记日期,订单号,设备号,登记人),不再以退款金额作为去重依据
2. 新增全量同步对账能力,支持删除推送外的残留退款记录
3. 补充同步日志表的删除行数统计字段
4. 修复时间查询时区问题,统一使用北京时间统计数据
5. 优化前端退款看板的单日数据查看体验
6. 完善SCRM时间解析的时区备注说明
2026-08-24 09:49:48 +08:00
3d987f60a6
docs(manual-refund): add note about default time for date-only records
...
gitleaks / gitleaks (push) Has been cancelled
refactor: add _ensure_afternoon_time to set default 15:00 for date-only entries
首先补充了文档中关于仅日期无时间数据的默认时间处理说明,然后新增了`_ensure_afternoon_time`工具函数,将原始数据中没有时间部分的北京时间0点数据统一设置为下午3点作为默认值,同时在手动退款同步的两处时间清洗逻辑中接入该函数。
2026-08-24 08:34:22 +08:00
4a8d729787
feat: 新增workbuddy手动退款数据同步功能,修复前端chunk加载404问题
...
gitleaks / gitleaks (push) Has been cancelled
本次提交包含以下核心变更:
1. 新增手动退款同步日志模型与数据库迁移脚本
2. 实现workbuddy手动退款数据推送API与同步服务
3. 扩展采集日志页面支持手动退款同步日志展示
4. 为Nginx添加index.html不缓存规则,避免旧chunk引用404
5. 前端添加chunk加载失败自动刷新自愈逻辑
6. 修复SQLAlchemy upsert表达式索引渲染问题
2026-08-23 21:39:57 +08:00
77f1756702
feat(complaint): 新增投诉决策分析接口并迁移投诉总览到分析页
...
gitleaks / gitleaks (push) Has been cancelled
1. 后端新增投诉决策分析API,支持管理摘要、平台分布、时效、处理人效率等多维度统计
2. 前端将原投诉总览路由重定向到投诉分析页,移除冗余的投诉总览菜单和快捷入口
3. 优化投诉同步逻辑,新增空行过滤功能并统计过滤数量
4. 修复商户号投诉处理人脏数据问题,过滤被投诉主体名作为处理人的情况
2026-08-23 19:56:04 +08:00
3b1d752794
fix: 修复采集日志接口500错误和前端watch未定义
...
gitleaks / gitleaks (push) Has been cancelled
- collection_logs.py: 新增 _extract_error_detail 兼容 error_detail 中 dict 类型错误
- ComplaintIngest.vue: 补充 watch 导入缺失
2026-08-23 17:47:49 +08:00
da356baa3d
feat: 新增通用数据质量审核系统,优化退款提现看板
...
gitleaks / gitleaks (push) Has been cancelled
1. 新增QualityMixin数据质量混入类,为核心业务表添加质量审核字段
2. 为退费单、服务工单、钱包提现、闪猫退款表添加质量审核支持
3. 新增统一采集日志管理功能,支持多来源日志查询
4. 重构退款提现看板,新增手动退款统计并拆分三路出款数据
5. 重构数据质量页面,改为通用审核面板并整合原有手动退款审核
6. 调整前端路由,将数据质量页面迁移至数据采集菜单下
7. 新增数据质量审核后端API,支持全表校验、状态查询与人工修正
2026-08-23 17:09:40 +08:00
3fb36ee167
feat: 全域统一核算v4落地,新增手动退款管理全流程
...
gitleaks / gitleaks (push) Has been cancelled
- 新增manual_refunds模型与数据库表,支持商户号手动退款登记导入
- 实现手动退款数据质量校验与自动纠错/待确认标记逻辑
- 新增数据质量审核页面,支持人工确认与修正异常数据
- 重构四路经营退款统计,将D路替换为手动退款,更新全链路对账口径
- 更新文档与预设视图,统一全域核算v4标准
2026-08-23 14:11:13 +08:00
1fb2ab85ce
feat: 支持共享文档(腾讯文档)投诉数据接入与采集方式开关
...
gitleaks / gitleaks (push) Has been cancelled
- 主动监测: 腾讯文档 OAuth 授权 + 定时轮询读取共享表格(315/卡商), token 加密存储与自动刷新
- 被动推送: 保留 WorkBuddy push 通道, 新增推送配置状态接口与前端日志展示
- 文件导入: 复用现有上传导入, 三种模式统一到「投诉接入」入口(tab 切换 + 深链)
- 采集方式开关: ingest_mode_configs 控制 monitor/push 生效(关闭即停轮询/拒接收), 双开提示
- 新增 doc_sync_configs / ingest_mode_configs 表与两条迁移; 补充设计文档与使用说明
2026-08-22 12:53:16 +08:00
e14c879e0d
feat: 新增预警中心与指标阈值化监测功能
...
gitleaks / gitleaks (push) Has been cancelled
- 新增指标注册表、阈值配置、每日快照模型
- 实现多类型运营指标自动求值与四级水位判定
- 搭建预警引擎支持幂等告警、升级与自愈恢复
- 开发通知通道抽象层与站内信推送能力
- 扩展告警规则与记录模型,兼容原有预警逻辑
- 补充前端API与看板展示优化
2026-08-21 19:03:01 +08:00
f70ac5006f
refactor: 重构看板系统,统一命名并新增分析功能
...
gitleaks / gitleaks (push) Has been cancelled
1. 将所有看板名称从「XX看板」改为「XX分析」,优化导航菜单与快捷入口
2. 新增退款决策概览页面,整合资金流出、钱包水位、涉诉分析等管理视图
3. 新增提现沉淀清单接口与页面,支持转入未提现订单查询
4. 新增退款明细Excel导出功能
5. 调整菜单结构,新增挽回分析页面并优化路由配置
6. 统一操作人姓名归一化展示逻辑
2026-08-21 11:27:24 +08:00
54644fb875
refactor(complaint): 重构去重指纹逻辑并优化明细展示
...
gitleaks / gitleaks (push) Has been cancelled
1. 重构投诉去重指纹方案:移除易变的编号字段,加入归一化投诉问题提升区分度
2. 新增投诉内容归一化工具函数,解决同设备同日期不同投诉的误合并问题
3. 优化API明细接口:补充关联工单的完成时间、投诉内容和计数字段
4. 更新前端页面:展示补充工单信息、补全缺失的投诉内容和关联统计
5. 替换旧的存量回填脚本,适配新指纹规则处理重复数据
6. 更新文档说明指纹设计思路和优化效果
2026-08-20 18:04:07 +08:00
cfc22765eb
feat: 实现workbuddy投诉数据同步功能
...
gitleaks / gitleaks (push) Has been cancelled
本次提交完成了315投诉/卡商群投诉的数据同步全链路改造:
1. 新增WORKBUDDY_SYNC_TOKEN鉴权配置与同步路由
2. 重构投诉表去重逻辑,改用dedup_hash指纹替代原complaint_no唯一约束
3. 精简外部投诉表字段,移除低价值冗余列
4. 新增投诉补充明细API与前端看板展示
5. 添加同步日志与数据回填脚本
6. 完成数据清洗、SN关联工单、幂等去重等核心逻辑
2026-08-20 12:55:00 +08:00
8b61f1e79d
fix: 全栈审计v0.7 P0 安全修复(凭证泄露/报销闭环/微信认证/密钥校验/贴图404)
...
gitleaks / gitleaks (push) Has been cancelled
- C-1: 删除杉猫平台硬编码凭证并 fail-fast,.dockerignore 排除 scripts/,gitleaks 新增 shanmao 规则
- H-2: 报销闭环:create 强制待审核+拦截占用发票;approve 状态机校验+通过置已报销/驳回释放;invoice_id 唯一约束迁移
- H-1/H-3: 微信 mock 登录生产禁用(503)、申请角色枚举限制、审批角色校验、已审批不可重置
- H-4/OPS-04: JWT/口令 fail-fast 升级占位符+长度校验(四项全覆盖)
- H-6: nginx ^~ /uploads/ + vite /uploads 代理,修复审计贴图 404
- H-7/SEC-09: 初始密码日志脱敏、Redis URL 脱敏
- FEVM-01: 发票编辑保存按 _id 走 update(防重复发票)
- 附加: sub 畸形→401、XFF 取末段、bcrypt 超长拒绝、凭证字面值残留清理
2026-08-20 09:51:36 +08:00
2870be243c
refactor: 优化审计相关UI与同步功能,修复金额计算问题
...
gitleaks / gitleaks (push) Has been cancelled
1. backend: 修复充值记录金额求和逻辑,避免空值异常
2. web: 重构审计弹窗与页面,支持紧凑模式适配弹窗场景
3. web: 新增手动数据同步按钮,支持增量同步并展示进度
4. web: 优化同步提示条样式与文案,调整弹窗尺寸与间距
2026-08-20 00:15:56 +08:00
4a292d30ee
refactor: 统一术语与功能迭代
...
gitleaks / gitleaks (push) Has been cancelled
- 将全量"异常事件"术语替换为"影响事件"
- 新增影响事件周期性支持: 支持每日/工作日/每周/每月周期配置
- 升级审计/分析模块: 重构审计时序服务,移除废弃数据源,新增快捷分析入口
- 优化看板与队列展示: 支持周期性影响事件每日高亮显示
- 完善绩效计算逻辑: 新增专员岗跨类豁免判定,优化数据兜底策略
2026-08-19 15:51:15 +08:00
90cac32d2e
Merge remote-tracking branch 'origin/main'
gitleaks / gitleaks (push) Has been cancelled
2026-08-19 07:47:53 +08:00
4c464f1996
feat: 添加全局审计弹窗与投诉明细功能
...
1. 新增全局审计弹窗组件与状态管理hook,支持设备/人员审计一键唤起
2. 新增三源合并的投诉明细API与前端展示页面
3. 优化运营看板与今日看板,支持点击人员/SN打开审计弹窗
4. 调整时间筛选选项与部分页面UI样式
2026-08-19 07:46:29 +08:00
6321e8ad2a
docs: add series of design specs and review reports
...
gitleaks / gitleaks (push) Has been cancelled
1. add 3 CSCTalon design specification docs: UI organization, workflow design, management effectiveness
2. add full-stack review, planning design review, action item registry and project audit report
3. fix prod ENV injection in docker-compose.yml for fail-fast校验
2026-08-19 06:21:32 +08:00
dde6d9d3b1
feat: 新增微信扫码注册审批、移动端H5适配与数据采集并发优化
...
gitleaks / gitleaks (push) Has been cancelled
此版本为v0.6增量迭代,包含以下核心改动:
1. 新增微信扫码注册审批流程:新增wechat_registrations/wechat_accounts表,用户表新增微信openid/unionid字段,实现微信code登录、注册申请与管理员审批功能
2. 移动端H5适配:新增响应式布局,隐藏非核心菜单,仅开放今日看板、运营看板与数据采集功能
3. 数据采集并发优化:新增submit_multi接口,通过asyncio.gather实现BMS与SCRM并发执行,缩短同步耗时
4. 菜单结构收敛:将数据中心功能拆入系统管理菜单,收紧路由权限
5. 同步完成后统一失效today:*与analytics:*缓存,保证数据一致性
2026-08-18 19:24:14 +08:00
197fde4fe2
feat: 全量迭代功能模块,覆盖发票识别、数据质量、政策管理等
...
gitleaks / gitleaks (push) Has been cancelled
新增:
1. 发票本地二维码解码功能,支持图片上传解析并回填表单
2. 数据质量规则管理页面与API接口
3. 政策替代链管理与主题标签筛选
4. 考勤打卡管理API与前端对接
5. 调度器定时预警扫描功能
优化:
1. 菜单权限控制细化,调整管理员/主管权限范围
2. 重构预警扫描逻辑,支持多规则类型与幂等校验
3. 统一状态码与颜色映射工具类
4. 完善排班对照接口,对接考勤数据
依赖更新:
1. 添加pyzbar与pillow用于发票二维码解码
2. 更新系统依赖libzbar0用于解码支持
2026-08-18 16:46:35 +08:00
02a88a8618
feat: 完成v0.5版本核心业务模块开发与权限升级
...
gitleaks / gitleaks (push) Has been cancelled
本次提交实现了v0.5版本的多项核心功能升级:
1. 新增多套业务API模块:
- 异常事故跟进时间线、投诉审计、知识库管理
- 跨部门协作工单、预警中心、通知中心
- 排班管理、发票报销、设备专项标签
- 数据质量规则模块
2. 重构用户权限体系:新增五级角色与管理层权限组
3. 完善政策模型扩展字段与CRUD接口
4. 新增站内信通知服务与统一待办中心
5. 补充全量前端路由与侧边栏菜单配置
6. 优化用户管理页面的角色展示与权限控制
2026-08-18 10:36:51 +08:00
5685b2dcd0
chore: add docker compose override for colima and add v0.5 design doc
...
gitleaks / gitleaks (push) Has been cancelled
1. add docker-compose.override.yml to fix postgres permission issue on colima
2. add 客服中心管理系统 规划设计 v0.5.md full project design document
2026-08-18 00:31:46 +08:00
b50594a497
fix(gitleaks): 修复 hardcoded-strong-password 规则误报占位符
...
gitleaks / gitleaks (push) Has been cancelled
- regex 值首字符排除 < : 避免匹配 <密码>/<ClientSecret> 等占位符
- allowlist 补充 REDACTED: 规则内 allowlist 覆盖全局, 须重复声明
- 配合 filter-repo 历史清理后的占位符形式, gitleaks 全历史扫描 0 leaks
2026-08-17 15:40:58 +08:00
53737fd406
feat(security): 一次性完成安全加固剩余项
...
JWT 吊销机制(M7):
- User 模型新增 token_version 字段 + alembic 迁移 p1b2c3d4e5f6
- security.py 签发 token 时写入 tkv=token_version
- deps.py 校验 token.tkv == user.token_version, 不一致即 401
- auth.py 两处 login 传 token_version; 新增 POST /auth/logout 自增版本号
- users.py 改密验旧密码(防会话劫持改密锁户) + 自增 token_version;
管理员重置他人密码不强制验旧
- schema UserUpdate 新增 old_password 字段
- 前端 auth.js 拆 logout/localLogout, 主动登出调后端吊销;
401 拦截器用 localLogout 防递归; MainLayout 登出改 async
前端依赖升级:
- echarts ^5.5.0 → ^6.1.0, vite ^5.2.0 → ^6.4.3
- 移除未使用的 vue-echarts(项目只用 echarts core API, 避免 peer 冲突)
- npm run build 验证通过(7.88s)
Gitea Actions CI:
- 新增 .gitea/workflows/gitleaks.yml, push/PR 扫描全仓库(含历史)
- 与本地 pre-commit 钩子互补, 防绕过 --no-verify 的提交
修复 gitleaks pre-commit 钩子 bug:
- 钩子脚本误用 --source 标志(gitleaks 8.x git 子命令不接受),
改为位置参数传递仓库路径; 否则 gitleaks 报 unknown flag 退出非 0,
钩子误判为检测到凭证阻止所有提交
此前已做(本次一并提交):
- nginx 安全响应头(CSP/X-Frame-Options/nosniff/Referrer-Policy)
- Dockerfile npm ci + nginx 锁版本 + -w 1 一致化
- config.py 生产 fail-fast(拒默认 POSTGRES_PASSWORD/JWT_SECRET_KEY/INIT_ADMIN_PASSWORD/REDIS_PASSWORD)
- docker-compose Redis requirepass + maxmemory
- match.py/wide_tables.py 重活接口收紧为 AdminUser
- analysis_views.py 视图/规则属主保护(预设仅管理员, 自定义仅属主)
- 登录限流(rate_limit.py, 5分钟10次)
- .gitleaks.toml 移除 docs 白名单, 处置手册 redact 真实凭证
2026-08-17 15:26:27 +08:00
e8f77ba30d
feat: 完成多维度安全加固与功能优化
...
- 新增Playwright登录态独立存储目录与安全配置
- 加固静态文件服务,防止路径穿越与XSS
- 新增图片与上传文件magic bytes校验
- 优化Markdown渲染与XSS防护
- 新增gitleaks扫描与凭证泄露处置文档
- 新增接待分类设备量统计与视角切换
- 重构上传路径解析逻辑,统一安全校验
- 更新依赖包与配置文件
2026-08-17 14:26:28 +08:00
dc68611cac
fix(migration): 补齐 persons 表 current_group_id 和 primary_specialty 两列
...
根因: n1b2c3d4e5f6 只创建了 6 张新表,遗漏了对已有 persons 表的 ALTER TABLE ADD COLUMN。
本地环境通过 Base.metadata.create_all() 自动补了列,但新环境只跑 alembic upgrade head 会缺列,
导致 /api/v1/groups 和 /api/v1/specialties 返回 500 (UndefinedColumn 错误)。
修复: 新增 o1b2c3d4e5f6 migration
- 添加 current_group_id (BigInteger, FK→groups.id, indexed)
- 添加 primary_specialty (String(64))
- 兜底添加 specialty 兼容字段
- 幂等设计: 列/索引/约束均检查存在性后再创建
- 修复 Base.metadata.create_all() 静默失败导致的 FK 约束和索引缺失
验证: alembic upgrade head 成功,FK 约束+索引已补齐,3 个 API 全部返回 200
2026-08-17 12:54:53 +08:00
468de3321a
docs: 更新v0.4版本开发文档,新增多项功能说明
...
本次更新补充了v0.4版本的完整开发文档内容,包括:
1. SCRM同步体系:支持三表同步、增量拉取、Token自动刷新等完整流程
2. OCR自动登录:带置信度预检的验证码识别登录方案
3. SyncDashboard统一数据采集入口:合并BMS/SCRM同步等功能的前端面板
4. BMS查询按钮兜底策略:5层DOM匹配+SPA渲染守卫
5. 运营看板多粒度切换:支持日/周/月/季/年时间维度
6. Group/Specialty双管理体系:行政分组与业务专项的完整数据模型
7. 决策看板六大子菜单:面向管理层的专项分析视图
8. 人均效率算法:基于专项难度系数的工作量/效率计算逻辑
9. alembic迁移管理:合并版本链与幂等升级规范
2026-08-17 12:44:59 +08:00
823e3e865a
docs: 全面对齐文档与代码 (v0.4)
...
## 变更清单
### 仓库清理
- .gitignore: 新增 .venv_test/ 忽略规则
- 移动 debug-*.md 调试文档到 docs/debug/ 归档
### 文档更新 (P0)
- README.md: 重写为完整项目说明(架构/技术栈/快速启动/目录/文档索引)
- docs/规划设计.md: v0.3.2 → v0.4, 新增8章(运营看板多粒度/Group-Specialty双体系/
SCRM同步/决策看板6大子菜单/OCR登录/人均效率算法/SyncDashboard/alembic管理)
- docs/数据同步与加工说明.md: v0.3.2 → v0.4, 新增3章(SCRM同步/OCR登录/SyncDashboard)
### 文档新建 (P1)
- docs/人均效率算法说明.md: 工作量分/效率分计算/6专项系数/SN去重/投诉分类/退款挽回
- docs/Group-Specialty双体系设计.md: 行政分组+业务专项双维度/SCD Type 2/6张表设计
### 文档新建 (P2-P3)
- docs/API接口文档.md: 30+路由端点/参数/响应格式
- docs/数据库表结构说明.md: 56张表分类/关系图/索引策略
- docs/alembic-migration管理规范.md: 版本链/常用命令/幂等升级/stamp用法
2026-08-17 12:34:38 +08:00
559d3eb709
feat(web+backend): 新增专项筛选开关与分组专项表体系
...
1. 新增数据库迁移合并两个分支版本,统一alembic版本链
2. 补全分组与业务专项表的迁移文件,新增6张关联表与标准专项种子数据
3. 今日看板新增跨专项筛选开关,优化专项匹配逻辑与人员主专项推断规则,修复跨专项人员误显示问题
2026-08-17 12:18:45 +08:00
0e70ac802a
feat(data): add SCRM sync page and recharge traffic query page, fix .gitignore path issue
...
1. add RechargeTraffic.vue for local/upstream recharge and traffic query
2. add FetchScrm.vue for SCRM data sync management
3. fix .gitignore to use absolute /data/ path to avoid ignoring web/src/views/data/
2026-08-17 09:41:56 +08:00
37fc51388e
refactor(analytics,web): 完成投诉与专项优化全量改造
...
1. 修复人员任职记录同步逻辑,改用effective_from判断最近任职
2. 将人员专项选择改为动态字典,替换硬编码选项
3. 拆分运营看板投诉子类分布为三类独立饼图
4. 将投诉处理人排行拆分为内部/外部两个独立表格
5. 新增未闭环投诉数据不一致校验与提示
6. 实现按天匹配专项的工作量分计算改造
2026-08-14 18:01:10 +08:00
6d212f2a3b
fix(bms_fetcher): 修复BMS页面查询按钮点击不稳定问题
...
1. 替换固定sleep3s为渲染就绪守卫,等待页面可点击元素出现
2. 重构查询按钮点击逻辑,新增快速直击策略并调整遮挡层清理时机
3. 统一更新所有BMS数据源的base_url为wifi域名
4. 优化弹层清理逻辑,避免误隐藏页面功能元素
2026-08-14 11:04:11 +08:00
442f83186f
fix: 修复投诉待处理列表漏单&同步任务OOM&BMS selector失效问题
...
1. 修复complaint_pending列表ASC LIMIT 201导致的微信商户号投诉被截断问题,改为Python层统一过滤后截断
2. 优化upsert_cs_records批量插入逻辑,避免巨型OR查询导致PG OOM
3. 修复BMS退款同步页面DOM selector变更导致的抓取失败问题,扩大可点击元素范围并新增兜底策略
4. 新增数据库瞬态故障自动重试机制,提升同步任务稳定性
5. 新增近7天未闭环投诉看板模块与验证脚本
2026-08-14 09:57:48 +08:00
a99cf45050
feat(bms-sync): 实现专项级熔断与新调度策略
...
重构BMS定时同步调度器,更新为新的分时段调度策略:白天每小时同步、凌晨补跑;新增专项级熔断机制,单个数据源同步失败仅暂停该类型后续自动同步,手动同步可清除熔断标记。
2026-08-13 16:10:00 +08:00
b741ae7d17
fix(backend/web): 修复页面内存泄漏并新增投诉统计看板
...
1. 后端BmsFetcher:修复同组任务共用Page导致的内存溢出问题,新增Page异常自动恢复逻辑
2. 后端analytics:新增投诉大类自动归类与处理人/状态统计接口
3. 前端Today页面:新增投诉处理状态饼图与处理人排行表格
2026-08-13 12:32:43 +08:00
b7ffa0d704
feat: 全面优化同步流程与UI体验,提升并发友好性与数据展示
...
- 数据库连接优化:添加TCP keepalive与连接池回收配置,避免空闲连接被断开
- 宽表构建优化:分批处理后添加200ms休眠,避免打满数据库连接影响前端查询
- 同步状态扩展:API新增同步进度、写入高峰等状态字段,前端增加全局同步提示条
- 缓存策略优化:写入高峰期自动延长缓存TTL,防止缓存雪崩击穿数据库
- 匹配逻辑重构:返回受影响退费单号集合,支持宽表增量构建
- 投诉统计优化:拆分投诉来源分组,优化根因统计与可视化展示
- 下载重试修复:修复协程复用导致的异常日志与任务泄漏问题
- 同步标记优化:添加Redis全局写入高峰标记与兜底过期机制
2026-08-13 10:33:15 +08:00
37e6b5d98a
feat(refund-dashboard): 新增退款看板深度分析模块,重构现有退款看板
...
1. 新增9个后端退款分析接口,支持人员/设备/类型维度的多维度分析
2. 新增前端人员分析、设备分析、类型趋势三个子页面
3. 重构退款看板:移除无意义的催单风险模块,新增原因三分布视图
4. 优化图表组件,支持双Y轴、横向柱状图、自定义列配置
5. 调整预设视图配置,更新退费原因TOP10和审核人视图为双数值轴展示
2026-08-12 16:44:11 +08:00
e5c11c693b
perf(matcher): 优化关联匹配性能,添加进度上报
...
修复了原N+1次SQL查询导致的匹配耗时过长问题,将单次批量查询替代多次单条查询,并新增匹配进度回调以实时上报进度
2026-08-12 11:50:18 +08:00
36bc9150b1
refactor: 完成退款统计链路清理与性能优化
...
本次提交包含多部分改进:
1. 前端更新:移除其他退款占位说明,更新经营估算注释
2. 数据源清理:从种子配置移除other_refund并自动清理废弃数据源
3. 宽表构建优化:改为分批处理避免OOM,添加进度回调
4. 同步任务修复:将post-sync钩子移至线程池避免阻塞事件循环
5. 登录逻辑修复:排除只读输入框避免匹配到下拉框,修复重复登录问题
6. 缓存优化:添加表存在性缓存避免日志刷屏
7. 异常处理完善:修复下载任务泄漏与登录态传递问题
2026-08-12 11:37:09 +08:00
e74d8ac63b
fix: 多场景修复与优化
...
1. 新增nginx拦截废弃/api/v1/history接口
2. 后端日志持久化与健康检查配置
3. 修复钱包导入op_time空值校验问题
4. 优化BMS导出限流处理与日志诊断
2026-08-12 10:01:06 +08:00
16c716a7e2
refactor(bms_fetcher): 重构BMS抓取服务,统一域名并拆分裁剪逻辑
...
1. 将退费单等数据源的base_url从wifi.cloud-cat.cn统一到support.cloud-cat.cn,实现多数据源共享登录态
2. 拆分闪猫退款单和BMS退费单的日期裁剪逻辑为独立方法,实现代码复用
3. 修复CSV解析问题,处理BMS导出CSV末尾多制表符、字段错位等异常
4. 完善验证码识别逻辑,更新闪猫验证码类型说明
5. 增加时间范围填入二次校验和日期弹窗自动关闭逻辑
6. 优化查询按钮点击兜底策略,增加JS全局点击和输入框聚焦Enter触发
2026-08-11 17:54:58 +08:00
9bc2c03b2c
feat(person_extractor): 新增生产/财务/测试等独立部门识别与部门推断
...
1. 扩展人员名称前缀列表,新增生产/财务/测试等独立部门前缀
2. 新增部门前缀推断工具函数,支持从别名中识别部门归属
3. 重构前缀剥离逻辑,复用统一前缀规则覆盖全量部门前缀
4. 优化员工效率统计逻辑,新增带前缀别名的兜底匹配与部门补全
5. 修正虚拟账户判定规则,修复误判员工工号/姓名的问题
2026-08-11 15:50:19 +08:00
3de83afc29
feat: 全栈优化看板体验与后端性能
...
1. 前端请求层: 支持AbortController取消请求,添加防抖合并重复请求
2. 后端: 优化today接口缓存与数据库查询性能,修复统计信息滞后问题
3. 模型层: 为关键字段添加索引提升查询速度
4. 错误处理: 区分网络异常/超时/取消请求的提示逻辑
2026-08-11 14:53:09 +08:00
e591af5fff
refactor(analytics+sync+dashboard): 2026-08-11 退款挽回口径修正与专项优化
...
1. 更新退款挽回分析接口文档与核心口径,修正人员判定逻辑为按specialty而非组名
2. 新增工单按完成时间的双维度同步逻辑,解决老工单handler回填遗漏问题
3. 重构前端退款看板与今日视图:
- 更新新口径的tooltip说明
- 拆分退款/投诉专项为退款挽回/退款处理、内部/外部投诉
- 优化专项筛选逻辑,支持跨专项工作量展示
- 补充专项样式与权重配置
4. 新增服务启动僵尸任务清理逻辑
5. 临时禁用自动迁移以规避多head分支问题
2026-08-11 13:52:28 +08:00
2758866bda
feat(analytics): 新增时间粒度自定义选择,支持周/月/季/年维度统计
...
1. 后端接口新增trend_grain参数,支持hour/day/week/month手动指定聚合粒度
2. 重构今日看板前端页面,新增年视图、自定义日期范围选择
3. 支持按周/月/季/年维度切换,并可选择对应聚合粒度
4. 更新接口文档和注释,完善时间粒度计算逻辑
2026-08-10 19:31:39 +08:00
92f86cf392
refactor(analytics): 修正退款挽回分析的口径与逻辑
...
1. 移除problem_type过度过滤,纳入所有退款理由的SN
2. 调整时间窗口为SN最早接待时间+7天,避免误判
3. 优化退款工单/退款单的统计规则,移除不必要的操作人限制
4. 更新注释与计算说明文档
2026-08-10 18:16:20 +08:00
3939e57832
refactor: 2026-08-10 专项细分调整与效率统计优化
...
1. 重构专项配置:拆分投诉/退款专项,新增内部/外部投诉、退款处理专项
2. 优化人员效率计算逻辑:调整难度系数权重,新增大类默认系数与管理岗豁免规则
3. 前端页面适配:重构筛选栏位置与逻辑,新增前端本地过滤能力,更新专项配色与展示文本
4. 同步服务优化:新增同步源配置读取,补充同步状态展示字段
5. 修复分组关联逻辑:通过PersonGroupMembership实时获取分组名称,移除冗余镜像字段
2026-08-10 15:29:54 +08:00
7ae1159dd2
refactor(sync&db): 完善同步任务稳定性与数据去重机制
...
1. 调整同步任务管理器注释,明确单worker部署要求
2. 修改gunicorn启动参数为单worker以保证内存锁生效
3. 为钱包提现和客服记录表添加业务唯一约束
4. 实现同批次数据去重+数据库层兜底去重,防止重复同步数据膨胀
5. 优化导入统计逻辑,精确计算实际新增数据量
2026-08-10 13:01:18 +08:00
5236bca17e
feat: 升级退款统计到v4版本,新增C/D路线支持与紧凑模式
...
1. 后端新增C闪猫/D其他退款的时间趋势聚合函数,重构KPI和小时节奏统计逻辑,将退款口径升级为四路合计
2. 前端KpiCard新增紧凑模式适配核心指标展示,重构小时节奏图表支持四路退款拆分与分组展示
3. 调整页面布局与样式,优化图表配色与tooltip展示,完善向后兼容逻辑
2026-08-10 12:22:17 +08:00
6d0c3e85b8
feat: 新增行政分组与业务专项管理功能,添加多维度人员筛选
...
- 新增分组API:分组列表/详情、负责人轮岗、成员管理
- 新增专项API:专项列表/详情、负责人变更、人员分配、归档
- 今日看板新增部门/分组/专项筛选器
- 人员列表页新增分组和专项筛选,优化人员详情展示
- 为看板统计接口添加部门、分组、专项筛选参数
2026-08-10 09:52:56 +08:00
c0ef529573
feat: 新增行政分组与业务专项双体系,完善人员管理功能
...
1. 新增Group/Specialty模型与初始化迁移脚本
2. 优化人员在职状态枚举与前端展示
3. 重构今日数据看板聚合逻辑
4. 修复事件接口路径与人员查询过滤条件
2026-08-10 09:52:56 +08:00
c27e2da5a0
feat: 新增流量充值查询、管理制度、部门同步功能模块
...
1. 新增web端路由与页面: 流量查询页面、登记中心页面与通用表格组件
2. 新增后端接口与模型: 流量充值API、管理制度CRUD、部门同步登记功能
3. 优化审计时序接口: 新增设备SN、操作人姓名返回开关
4. 增强今日看板: 支持企微人员筛选与多维度聚合统计
5. 补充操作日志翻译映射与模型导入
2026-08-10 09:52:56 +08:00
46fae69ad6
fix: 修复工单导入导出的两个关键bug并优化数据库字段
...
1. 修复工单导入时rows字典键不一致导致的SQL绑定参数错误,强制为所有行添加updated_by_raw和synced_at键
2. 扩展service_tickets表的字符串字段长度,将is_appeal、channel、status等字段从16/32扩容到64,避免状态名称过长触发截断错误
3. 为所有导入的字符串字段添加截断保护,防止超出数据库列长度限制
4. 添加多个调试脚本用于排查工单导入导出问题
5. 优化ECharts图表的颜色配置和数据格式
2026-08-09 12:10:00 +08:00
dbc6ebcf2b
refactor(scrm): 重构异步抓取逻辑,支持playwright模式无需提前校验凭证
...
1. 调整ScrmClient:将client_id/client_secret校验移至_ensure_openapi_ready,仅调用OpenAPI时校验
2. 新增全局500异常处理器,记录完整请求日志与堆栈
3. 优化同步设置动态占位符逻辑,移除旧版动态placeholder
4. 拆分BMS定时同步日志,区分BMS和SCRM子任务日志
5. 重构数值列归一化工具,支持带单位/货币/百分比的字符串转换
6. 升级BMS导出下载超时重试机制,按类型配置超时与重试次数
7. 重构SCRM同步服务,新增单源超时熔断、进程级登录缓存
8. 迁移Playwright同步到async_api,修复asyncio运行环境兼容
2026-08-07 17:05:04 +08:00
c6dd5ef3b8
feat: add SCRM staff profile feature and related toolings
...
- Add ScrmStaffProfile model and import it
- Fix PG dict type insert by json.dumps detail field
- Add SCRM chat API router and schemas
- Add multiple SCRM debug/fix/verify scripts
- Add scrm staff API endpoints and schemas
- Fix alembic migration script noise
2026-08-07 13:40:22 +08:00
4a862a9340
refactor: 清理本地调试脚本,修复scrm抓取配置,新增工单同步字段与日志表
...
1. 清理全部15个本地调试/验证脚本,移除开发临时文件
2. 修复SCRM抓取配置:从openapi切换为playwright模式适配风控
3. 为service_tickets和refund新增synced_at/updated_by_raw字段及对应迁移
4. 新增工单操作流水表ticket_action_logs用于业务动作追踪
5. 为bms_sync日志更新添加事务回滚兜底逻辑
6. 注册TicketActionLog模型到全局导入列表
7. 新增6个服务请求管理专项预设视图
2026-08-06 15:29:01 +08:00
89d4946720
refactor: 重构同步系统,合并BMS/SCRM同步并修复工单同步问题
...
1. 更新SCRM默认接口地址为新网关
2. 修复BMS工单同步漏单问题:改用登记时间筛选替代完成时间
3. 合并前端BMS/SCRM同步页为统一数据同步入口
4. 重构同步源配置模型,新增扩展配置字段支持多抓取模式
5. 新增数学验证码识别支持SCRM网页登录
6. 重构SCRM同步服务,支持OpenAPI/Playwright双模式抓取
7. 优化投诉统计口径,兼容外部投诉表数据
8. 调整后端数据库迁移适配新配置字段
2026-08-06 14:34:12 +08:00
8ce946b15d
feat: 新增设备标签管理与验证码自动识别功能
...
1. 新增ddddocr+onnxruntime依赖实现验证码自动识别
2. 新增设备标签定义与打标明细的数据模型、导入导出支持
3. 新增闪猫退款调试脚本与请求嗅探工具
4. 重构验证码登录流程,支持自动OCR登录兜底
5. 扩展同步数据源类型与字段映射配置
2026-08-05 16:23:14 +08:00