Files
csctalon/docs/standards/数据时间字段使用规范.md
tanzk 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

12 KiB
Raw Permalink Blame History

数据时间字段使用规范

性质:元需求(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.0 快照源,已停用) 微信侧自动时间;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)。


七、代码落地位置

修改上述任一文件后,需重启后端或手动调用 ensure_data_sources_registered / ensure_preset_views_registered 让 seed 重新生效。