用户: 「有的是需求,有的是需求的需求,都需要打上不同的标记」 ## 核心洞察 三类文档(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
12 KiB
数据时间字段使用规范
性质:元需求(META)—— 约束全系统的时间统计口径(时区换算 + 系统操作人排除) 生效日期: 2026-08-01 适用范围: 全系统统计分析(预设视图、自助分析、自定义 SQL、周报/审计输出) 关联文档: 数据时间审核报告.md(一次性审计快照)、规划设计.md、数据同步与加工说明.md
本规范基于 2026-08-01 的全系统时间审核结论制定,目的是确保所有统计分析的时间标签合理、可比、可信。
一、客服上班时段与统计合规原则
- 客服上班时间:全年无休
08:00 ~ 23:59(北京时间) - 客户自操作:提现等可发生在任意时段(含 0~7 点)
- 合规原则:
- 凡客服操作记录(
cs_records.operation_time、refunds.created_at_raw真实客服、service_tickets.register_time、external_complaints.handle_time等)按理应落在 08:00~23:59 - 凡系统自动操作(SYS/system/Wallet/WeChat 自动退费转入、套餐激活、自动赠送工单)可落在凌晨,合规
- 数据库时区 =
Etc/UTC,所有业务时间已存为timestamptz(aware)。统计/显示时统一AT TIME ZONE 'Asia/Shanghai'转北京时间
- 凡客服操作记录(
二、SYS/system 系统操作人排除标准(强制)
2.1 标准排除清单
["sys", "system", "SYS", "System"] # 必须 4 种写法全列
2.2 为什么必须 4 种全写
- PostgreSQL
NOT IN大小写敏感(varchar默认按字节比较) refunds.operator实际并存system(8132 条)和SYS(5577 条)两种写法- 只写
["sys", "system"]会漏排 5577 条 SYS 系统退款(退到会员钱包,非真实现金流出),导致金额虚高、人员工作量虚增 - 唯一例外:
wallet_withdrawals用op_type区分(用户提现vs订单退费转入),不依赖 operator
2.3 引擎层过滤写法
# preset_views_seed.py 中的标准写法
{"field": "operator", "op": "notin", "value": ["sys", "system", "SYS", "System"]}
或 SQL 层:
WHERE operator NOT IN ('sys', 'system', 'SYS', 'System')
2.4 真实现金流出定义(全域统一核算规范 v4 · 双视角)
2026-08-02 v3 升级:C(闪猫退款)/D(手动退款) 是独立业务表,不是 merchant_refunds 的 refund_type 分桶;现金流对账基准 E 独立(当时=merchant_refunds 快照)。
2026-08-23 v4 升级:C(shanmao_refunds)/D(manual_refunds) 均已建表并导入数据;D = 手动退款登记(商户号手机退款处理登记.xlsx,is_refunded='是' 计入经营出款)。E 商户号现金流为同步快照(当时截止 7/20),只作对账基准,不与经营四路累加。
2026-09-05 v1.1 升级(#20):E 路数据源由 merchant_refunds(一次性 Excel 快照)切换为
refund_record_reports(BMS「退款记录报表」,线上真实退款水单,每天 T+1 采集 + 文件导入补历史,见 A-20260902-011 v1.1 / A-20260905-002 v1.1)。E 时间列 =refund_time;E 状态 =IN ('退款成功','成功','SUCCESS');E 截止标注 = 最新采集/导入退款日。旧表 merchant_refunds 保留供 matcher/wide 关联,不再作 E 统计取数。
┌── 经营视角(四路 A/B/C/D 登记出款)────────────────────────────────┐
│ 经营估算 = A.退费单线上直退 (refunds refund_method='线上') │
│ + B.钱包用户取现 (wallet_withdrawals op_type='用户提现')│
│ + C.闪猫退款 (shanmao_refunds 独立表, status=已退款)│
│ + D.手动退款 (manual_refunds 独立表, is_refunded=是)│
├── 财务视角(商户号真扣款 · 对账基准 E)──────────────────────────────┤
│ 实际现金流 = E.refund_record_reports WHERE refund_status │
│ IN ('退款成功','成功','SUCCESS') │
│ (不分 refund_type, ORIGINAL+BALANCE 全部合计) │
├── 对账偏差 ────────────────────────────────────────────────────────┤
│ 对账偏差 = 经营估算 − 实际现金流 = (A+B+C+D) − E │
│ (正值 = 商户侧已扣款但尚未登记到四路的退款) │
└────────────────────────────────────────────────────────────────────┘
术语对齐(全局看板/接口统一):
- "今日退款笔数/金额" = 路线 A(退费单线上直退,业务退款口径)
- "真实现金流出 / 现金流出" = 经营估算 A+B+C+D(经营侧四路登记出款)
- "商户号实际现金流" = E(refund_record_reports 退款成功,对账基准,数据截止=最新采集/导入退款日,见各看板提示)
- "对账偏差" = 经营估算 − 实际现金流
- "手动退款登记蓄水" = 已登记未退款(is_refunded='否' 的退款金额),类比钱包净水位
operator NOT IN 说明(仅对路线A有效):
路线 A 是 refunds 表 refund_method='线上',其中 operator 大小写 4 种('sys','system','SYS','System')表示退到会员钱包(非真实现金流出),实际统一核算聚合器中没有额外人工剔除(靠 refund_method='线上' 业务字段本身筛选即可;若后续发现 SYS 线上退款确实存在则补加)。
三、时间字段优先级表
选字段时优先用「推荐」列,「不推荐」列因不可靠或语义不符,仅在明确知晓风险时使用。
| 分析场景 | 推荐字段 | 不推荐 | 原因 |
|---|---|---|---|
| 客服接待时段/工作量 | cs_records.operation_time |
— | 已验证干净(0.004% 异常) |
| 退费单产生时间 | refunds.created_at_raw |
updated_at_raw |
updated_at_raw 被 SYS 03:15 批量覆盖 |
| 工单产生时段 | service_tickets.register_time |
finish_time |
finish_time 89% 不可靠(见第四节) |
| 工单完成时间 | handle_duration + register_time 推算 |
finish_time |
同上 |
| 用户提现时间 | wallet.op_time(op_type='用户提现') |
— | 注意排除 订单退费转入(SYS 自动);2026-08-28 起还需排除备注含失败/冻结关键词(提现申请-冻结/退款失败/超期订单不允许退款等)的记录——这些是提现申请被冻结/拒绝,非成功退款,不计入"成功提现/成功退款"统计口径 |
| 设备套餐生效 | device_combos.start_date / end_date |
created_at_raw |
created_at_raw 含 02:05 Wallet 批量激活时间 |
| 外部/卡商投诉日期 | complaint_date |
— | 日期型(00:00:00),无小时维度 |
| 商户号退款时间 | refund_record_reports.refund_time(2026-09-05 v1.1 起) |
merchant_refunds.trade_time |
微信侧自动时间;v1.1 起 E 读退款记录报表 |
四、不可靠时间字段清单(已在 UI 隐藏)
以下字段因数据质量问题,在数据源注册里已设 is_filterable=False,不再出现在自助分析的筛选器/维度选择 UI。字段数据保留,直接 SQL 仍可查(但需知晓风险)。
| 字段 | 不可靠原因 | 替代方案 |
|---|---|---|
ticket.finish_time |
89% 非真实完成时间:50% 被 BMS 批量覆盖(finish>register+12h)、23% finish==register(同步填充)、17% 同瞬填充;仅 10.7% 是真实完成 | register_time(产生时段)+ handle_duration(处理时长) |
refund_wide.ticket_finish_time |
同上(来自 ticket 表关联) | 同上 |
refund.updated_at_raw |
被 SYS 03:15 批量任务覆盖(841 条/27 天),反映"最后触碰时间"非真实业务变更时间 | created_at_raw(创建时间,稳定) |
4.1 finish_time 不可靠的证据(2026-08-01 审计)
对「已完成且 handler 非空」工单核对 register_time vs finish_time:
| 时序类型 | 数量 | 占比 |
|---|---|---|
| finish 晚于 register > 12h(被批量覆盖) | 5967 | 49.6% |
| finish == register(同步填充) | 2773 | 23.0% |
| finish == register 同瞬(自动填充) | 2002 | 16.7% |
| 真实完成(稍晚于 register) | 1282 | 10.7% |
典型证据:企微-叶珍凤 工单 register=2026-07-28 14:52,finish=2026-07-30 07:09(批量覆盖);企微-吴晓龙 工单 register=2026-07-26 07:55,finish=2026-08-01 06:11(6 天后批量覆盖)。
4.2 若必须统计工单完成时间
-- 仅保留真实完成记录(10.7% 样本,慎用,样本量小)
WHERE status = '已完成'
AND handler IS NOT NULL AND handler <> ''
AND handler NOT IN ('SYS', '客户')
AND finish_time BETWEEN register_time AND register_time + INTERVAL '12 hours';
五、上游 BMS 批量时间戳说明
下列时间戳来自上游 BMS/ai-acc 的批量任务(非我方同步,我方同步在 00:00/08:00 执行),我方如实导入。这些是系统自动操作,凌晨运行合理,时间本身不需要修复,但统计客服工作负荷时必须按操作人排除。
| 表 | 字段 | 批次时间 | 记录数 | 操作类型 | 性质 |
|---|---|---|---|---|---|
| wallet_withdrawals | op_time | 03:15 | 1631 | op_type='订单退费转入', operator='SYS' |
系统自动退费转入钱包 |
| device_combos | created_at_raw | 02:05 | 12260 | creator='Wallet' |
钱包购买套餐自动激活 |
| refunds | updated_at_raw | 03:15 | 841(其中 SYS 753) | SYS 批量状态更新 | 系统自动 |
| service_tickets | finish_time | 03:15 / :09 | 331+ | SYS 自动赠送工单 + 批量覆盖 | 系统自动 |
六、各业务表时间字段速查
| 表 | 主时间字段(time_field) | 用途 | 备注 |
|---|---|---|---|
| cs_records | operation_time |
客服接待操作时间 | 干净,直接可用 |
| refunds | created_at_raw |
退费单创建时间 | 稳定;updated_at_raw 已隐藏 |
| refund_wide | created_at_raw |
退费宽表创建时间 | 物化表,跨表关联 |
| ticket | register_time |
工单登记时间 | finish_time 已隐藏 |
| wallet | op_time |
钱包操作时间 | 按 op_type 区分用户提现/系统转入 |
| merchant_refund | trade_time |
微信交易时间 | 微信侧自动 |
| device_combo | created_at_raw |
套餐创建时间 | 含 02:05 Wallet 批量,业务时段用 start_date |
| external_complaint | complaint_date |
投诉日期 | 日期型 |
| card_complaint | complaint_date |
投诉日期 | 日期型 |
| manual_refund | COALESCE(op_time, register_time) |
手动退款 D 路出款 / 登记水位 | 操作时间优先(实际出款时间), 缺失回退登记日期; 登记日期为北京本地日期, 统一 naive=北京时间解析 |
| scrm_operators / scrm_sessions / scrm_session_transfers | last_active_at / start_time / transfer_time |
SCRM 会话/转接/坐席 | ⚠️ 时区待核实(见下) |
⚠️ SCRM 时区假设待核实(2026-08-24 备注, SCRM 数据暂未同步) SCRM 同步把返回的 naive 时间按 UTC 解析存储(
scrm_sync_service._to_utc_aware), 但查询参数按东八区发送(_fmt_scrm_ts)。若上游返回的其实是北京时间, 会偏 8 小时。 待 SCRM 首次同步有数据后, 用实际数据验证(startTime 换算北京是否落在客服上班时段 08:00~23:59), 确认后再决定是否把 naive 分支改为按北京时间解释。手动登记表(manual_refunds 等)无此问题, 统一按"naive = 北京时间"处理(upsert._to_datetime)。
七、代码落地位置
- 预设视图过滤规则:backend/app/services/preset_views_seed.py
- 数据源字段注册(is_filterable/name_cn):backend/app/services/data_source_registry.py 的
FIELD_OVERRIDES - 引擎过滤实现:backend/app/services/analysis_engine.py 的
_build_op_condition(notin大小写敏感,L487) - 数据导入时区转换:backend/app/services/upsert.py 的
_to_datetime(naive 北京时间 → UTC aware)
修改上述任一文件后,需重启后端或手动调用 ensure_data_sources_registered / ensure_preset_views_registered 让 seed 重新生效。