开发机迁移

This commit is contained in:
Neo
2026-04-10 06:24:13 +08:00
parent f65c1d038b
commit 79d3c2e97e
50 changed files with 1565 additions and 318 deletions

View File

@@ -1,11 +1,12 @@
# 审计一览表
> 自动生成于 2026-04-06 01:04:43,请勿手动编辑。
> 自动生成于 2026-04-08 15:09:30,请勿手动编辑。
## 时间线视图
| 日期 | 项目 | 需求摘要 | 变更类型 | 影响模块 | 风险 | 详情 |
|------|------|----------|----------|----------|------|------|
| 2026-04-08 | 项目级 | 变更审计记录Fix-13 回滚手动完成 + 广义召回完成机制 | bugfix | 其他 | 低 | [链接](changes/2026-04-08__fix13-recall-events-refactor.md) |
| 2026-04-06 | 项目级 | 变更审计记录v1 历史清理与 DDL 合并归档 | 清理 | 其他 | 极低 | [链接](changes/2026-04-06__v1-cleanup-ddl-consolidation.md) |
| 2026-04-05 | 项目级 | 变更审计记录Kiro → Claude Code 全量迁移 | 文档 | 其他 | 低 | [链接](changes/2026-04-05__kiro-to-claude-code-migration.md) |
| 2026-03-31 | 项目级 | 变更审计记录:任务引擎改造 — 参数调优 + 客户级升级/转移 + 任务统计写入 | 功能 | 其他 | 未知 | [链接](changes/2026-03-31__task-engine-overhaul.md) |
@@ -240,6 +241,7 @@
| 日期 | 需求摘要 | 变更类型 | 影响模块 | 风险 | 详情 |
|------|----------|----------|----------|------|------|
| 2026-04-08 | 变更审计记录Fix-13 回滚手动完成 + 广义召回完成机制 | bugfix | 其他 | 低 | [链接](changes/2026-04-08__fix13-recall-events-refactor.md) |
| 2026-04-06 | 变更审计记录v1 历史清理与 DDL 合并归档 | 清理 | 其他 | 极低 | [链接](changes/2026-04-06__v1-cleanup-ddl-consolidation.md) |
| 2026-04-05 | 变更审计记录Kiro → Claude Code 全量迁移 | 文档 | 其他 | 低 | [链接](changes/2026-04-05__kiro-to-claude-code-migration.md) |
| 2026-03-31 | 变更审计记录:任务引擎改造 — 参数调优 + 客户级升级/转移 + 任务统计写入 | 功能 | 其他 | 未知 | [链接](changes/2026-03-31__task-engine-overhaul.md) |
@@ -370,6 +372,7 @@
| 日期 | 需求摘要 | 变更类型 | 风险 | 详情 |
|------|----------|----------|------|------|
| 2026-04-08 | 变更审计记录Fix-13 回滚手动完成 + 广义召回完成机制 | bugfix | 低 | [链接](changes/2026-04-08__fix13-recall-events-refactor.md) |
| 2026-04-06 | 变更审计记录v1 历史清理与 DDL 合并归档 | 清理 | 极低 | [链接](changes/2026-04-06__v1-cleanup-ddl-consolidation.md) |
| 2026-04-05 | 变更审计记录Kiro → Claude Code 全量迁移 | 文档 | 低 | [链接](changes/2026-04-05__kiro-to-claude-code-migration.md) |
| 2026-03-31 | 变更审计记录:任务引擎改造 — 参数调优 + 客户级升级/转移 + 任务统计写入 | 功能 | 未知 | [链接](changes/2026-03-31__task-engine-overhaul.md) |

View File

@@ -439,8 +439,10 @@ SELECT table_fee_log_id,
WHERE (site_id = (current_setting('app.current_site_id'::text))::bigint);
;
-- CHANGE 2026-04-08 | DISTINCT ON 只取每对 (assistant, member) 最新快照
CREATE OR REPLACE VIEW app.v_dws_assistant_customer_stats AS
SELECT id,
SELECT DISTINCT ON (assistant_id, member_id)
id,
site_id,
tenant_id,
assistant_id,
@@ -478,7 +480,8 @@ SELECT id,
created_at,
updated_at
FROM dws.dws_assistant_customer_stats
WHERE (site_id = (current_setting('app.current_site_id'::text))::bigint);
WHERE (site_id = (current_setting('app.current_site_id'::text))::bigint)
ORDER BY assistant_id, member_id, stat_date DESC;
;
CREATE OR REPLACE VIEW app.v_dws_assistant_daily_detail AS
@@ -976,8 +979,10 @@ SELECT relation_id,
WHERE (site_id = (current_setting('app.current_site_id'::text))::bigint);
;
-- CHANGE 2026-04-08 | DISTINCT ON 只取每个会员最新快照,避免多 stat_date 行膨胀
CREATE OR REPLACE VIEW app.v_dws_member_consumption_summary AS
SELECT id,
SELECT DISTINCT ON (member_id)
id,
site_id,
tenant_id,
member_id,
@@ -1024,7 +1029,8 @@ SELECT id,
recharge_amount_90d,
avg_ticket_amount
FROM dws.dws_member_consumption_summary
WHERE (site_id = (current_setting('app.current_site_id'::text))::bigint);
WHERE (site_id = (current_setting('app.current_site_id'::text))::bigint)
ORDER BY member_id, stat_date DESC;
;
CREATE OR REPLACE VIEW app.v_dws_member_newconv_index AS
@@ -1042,6 +1048,7 @@ SELECT newconv_id,
t_r,
t_a,
visits_14d,
visits_30d,
visits_60d,
visits_total,
spend_30d,
@@ -1167,6 +1174,7 @@ SELECT winback_id,
t_r,
t_a,
visits_14d,
visits_30d,
visits_60d,
visits_total,
spend_30d,

View File

@@ -789,6 +789,7 @@ CREATE TABLE dws.dws_member_newconv_index (
t_r numeric(6,2),
t_a numeric(6,2),
visits_14d integer DEFAULT 0 NOT NULL,
visits_30d integer DEFAULT 0 NOT NULL,
visits_60d integer DEFAULT 0 NOT NULL,
visits_total integer DEFAULT 0 NOT NULL,
spend_30d numeric(14,2) DEFAULT 0 NOT NULL,
@@ -907,6 +908,7 @@ CREATE TABLE dws.dws_member_winback_index (
t_r numeric(6,2),
t_a numeric(6,2),
visits_14d integer DEFAULT 0 NOT NULL,
visits_30d integer DEFAULT 0 NOT NULL,
visits_60d integer DEFAULT 0 NOT NULL,
visits_total integer DEFAULT 0 NOT NULL,
spend_30d numeric(14,2) DEFAULT 0 NOT NULL,
@@ -1297,6 +1299,7 @@ SELECT dws_member_winback_index.site_id,
dws_member_winback_index.t_r,
dws_member_winback_index.t_a,
dws_member_winback_index.visits_14d,
dws_member_winback_index.visits_30d,
dws_member_winback_index.visits_60d,
dws_member_winback_index.visits_total,
dws_member_winback_index.spend_30d,
@@ -1332,6 +1335,7 @@ UNION ALL
dws_member_newconv_index.t_r,
dws_member_newconv_index.t_a,
dws_member_newconv_index.visits_14d,
dws_member_newconv_index.visits_30d,
dws_member_newconv_index.visits_60d,
dws_member_newconv_index.visits_total,
dws_member_newconv_index.spend_30d,

View File

@@ -16,6 +16,7 @@ CREATE SEQUENCE IF NOT EXISTS biz.cfg_task_generator_params_id_seq AS bigint;
CREATE SEQUENCE IF NOT EXISTS biz.coach_task_history_id_seq AS bigint;
CREATE SEQUENCE IF NOT EXISTS biz.coach_task_transfer_log_id_seq AS bigint;
CREATE SEQUENCE IF NOT EXISTS biz.coach_tasks_id_seq AS bigint;
CREATE SEQUENCE IF NOT EXISTS biz.recall_events_id_seq AS bigint;
CREATE SEQUENCE IF NOT EXISTS biz.connectors_id_seq AS integer;
CREATE SEQUENCE IF NOT EXISTS biz.dws_assistant_task_monthly_id_seq AS bigint;
CREATE SEQUENCE IF NOT EXISTS biz.excel_upload_log_id_seq AS bigint;
@@ -153,6 +154,7 @@ CREATE TABLE biz.coach_tasks (
abandon_reason text,
completed_at timestamp with time zone,
completed_task_type character varying(50),
completion_type character varying(10),
parent_task_id bigint,
created_at timestamp with time zone DEFAULT now(),
updated_at timestamp with time zone DEFAULT now(),
@@ -161,6 +163,17 @@ CREATE TABLE biz.coach_tasks (
transferred_at timestamp with time zone
);
CREATE TABLE biz.recall_events (
id bigint DEFAULT nextval('biz.recall_events_id_seq'::regclass) NOT NULL,
site_id bigint NOT NULL,
assistant_id bigint NOT NULL,
member_id bigint NOT NULL,
pay_time timestamp with time zone NOT NULL,
task_id bigint,
task_type character varying(50),
created_at timestamp with time zone DEFAULT now()
);
CREATE TABLE biz.connectors (
id integer DEFAULT nextval('biz.connectors_id_seq'::regclass) NOT NULL,
connector_key character varying(50) NOT NULL,
@@ -341,6 +354,8 @@ ALTER TABLE biz.dws_assistant_task_monthly ADD CONSTRAINT dws_assistant_task_mon
ALTER TABLE biz.excel_upload_log ADD CONSTRAINT excel_upload_log_pkey PRIMARY KEY (id);
ALTER TABLE biz.notes ADD CONSTRAINT notes_task_id_fkey FOREIGN KEY (task_id) REFERENCES biz.coach_tasks(id);
ALTER TABLE biz.notes ADD CONSTRAINT notes_pkey PRIMARY KEY (id);
ALTER TABLE biz.recall_events ADD CONSTRAINT recall_events_pkey PRIMARY KEY (id);
ALTER TABLE biz.recall_events ADD CONSTRAINT recall_events_task_id_fkey FOREIGN KEY (task_id) REFERENCES biz.coach_tasks(id);
ALTER TABLE biz.salary_adjustments ADD CONSTRAINT salary_adjustments_upload_batch_id_fkey FOREIGN KEY (upload_batch_id) REFERENCES biz.excel_upload_log(id);
ALTER TABLE biz.salary_adjustments ADD CONSTRAINT salary_adjustments_pkey PRIMARY KEY (id);
ALTER TABLE biz.site_code_history ADD CONSTRAINT site_code_history_pkey PRIMARY KEY (id);
@@ -386,5 +401,7 @@ CREATE INDEX idx_task_monthly_site_month ON biz.dws_assistant_task_monthly USING
CREATE INDEX idx_excel_log_site ON biz.excel_upload_log USING btree (site_id, created_at DESC);
CREATE INDEX idx_notes_target ON biz.notes USING btree (site_id, target_type, target_id);
CREATE INDEX idx_salary_adj_assistant_month ON biz.salary_adjustments USING btree (assistant_id, salary_month);
CREATE UNIQUE INDEX idx_recall_events_site_assistant_member_day ON biz.recall_events USING btree (site_id, assistant_id, member_id, (date_trunc('day', pay_time AT TIME ZONE 'Asia/Shanghai')));
CREATE INDEX idx_recall_events_assistant_pay ON biz.recall_events USING btree (site_id, assistant_id, pay_time);
CREATE INDEX idx_salary_adj_site_month ON biz.salary_adjustments USING btree (site_id, salary_month);

View File

@@ -0,0 +1,242 @@
# 看板全面排查修复计划
> 排查日期2026-04-07
> 范围客户看板BOARD-28 维度 + 助教看板BOARD-14 维度
> 状态:待实施(新对话执行)
---
## 一、已完成的修复
| # | 问题 | 修复内容 | 文件 |
|---|------|----------|------|
| ✅ | SPI 消费口径用 `pay_amount` 而非 `items_sum` | 3 处 settlement_head 查询改为 `items_sum`DWD 规则 #1 | `apps/etl/connectors/feiqiu/tasks/dws/index/spending_power_index_task.py` |
| ✅ | 潜力标签阈值 60 但数据范围 0-10 | 阈值 60→6返回 `[{text, theme}]` 对象数组 | `apps/backend/app/services/fdw_queries.py` `_derive_potential_tags` |
---
## 二、待实施修复(共 10 项)
### P1 — 功能性 Bug
#### Fix-1客户看板项目筛选崩溃C-1
- **问题**`_project_filter_clause()` 硬编码 `vd.member_id`,但 6/8 维度主表别名不是 `vd`,选择项目筛选时后端 SQL 500
- **影响维度**recall / balance / recharge / spend60 / freq60 / loyal
- **不受影响**recent别名 `vd`、potential自写子查询
- **修复方案**:统一改为独立子查询模式 `member_id IN (SELECT member_id FROM app.v_dws_member_project_tag WHERE category_code = %s AND is_tagged = true)`,不依赖外层别名(参考 potential 维度已有写法)
- **文件**`apps/backend/app/services/fdw_queries.py``_project_filter_clause()` 函数及 6 个维度查询函数
#### Fix-2助教看板 task 维度 callback 映射错误A-3
- **问题**callback 统计映射到 `relationship_building`,但业务上"回访"应对应 `follow_up_visit`
- **修复**`board_service.py` `_query_coach_tasks()` 中 callback 的 task_type 从 `relationship_building` 改为 `follow_up_visit`
- **文件**`apps/backend/app/services/board_service.py`
### P2 — 数据口径错误
#### Fix-3recall 维度 visits_30d 实为 14dC-2
- **问题**`dws_member_winback_index``visits_30d` 字段,后端用 `visits_14d` 近似,前端显示"30天到店"
- **修复**
1. DDL`dws.dws_member_winback_index` 加列 `visits_30d INTEGER DEFAULT 0`
2. ETLWBI 计算任务增加 30 天到店次数统计
3. RLS 视图:`dws.v_dws_member_winback_index``app.v_dws_member_winback_index` 加新列
4. 后端:查询改用新字段
5. DDL 文档同步更新
- **文件**
- `db/etl_feiqiu/schemas/dws.sql`DDL
- `apps/etl/connectors/feiqiu/tasks/dws/index/winback_index_task.py`ETL
- `db/etl_feiqiu/schemas/app.sql`RLS 视图)
- `apps/backend/app/services/fdw_queries.py`(查询)
- `docs/database/ddl/etl_feiqiu__dws.sql` / `etl_feiqiu__app.sql`DDL 文档)
#### Fix-4balance 维度月均消耗和可用月数偏差 2 倍C-3 + C-4
- **问题**
- `monthlyConsume`:直接用 `consume_amount_60d`60天总额标签"月均消耗"应为 `consume_amount_60d / 2`
- `availableMonths``balance / consume_amount_60d`,应为 `balance / (consume_amount_60d / 2)``2 * balance / consume_amount_60d`
- **示例**:余额 49780 / 60天消费 14521 → 当前显示 3.4 个月,实际应 6.9 个月
- **文件**`apps/backend/app/services/fdw_queries.py``get_customer_board_balance()`
#### Fix-5freq60 柱状图数据源不一致C-7
- **问题**:汇总数据来自 `v_dws_member_consumption_summary`(消费维度),但 8 周柱状图来自 `v_dwd_assistant_service_log`(助教服务维度),口径不一致
- **修复**:柱状图改为从消费汇总或结算维度获取周数据,与汇总口径一致
- **文件**`apps/backend/app/services/fdw_queries.py` — freq60 相关查询
#### Fix-6助教看板 sv 维度不响应时间筛选A-4
- **问题**:函数接收 `start_date/end_date` 但 SQL 未使用,"消耗"始终是固定 60 天
- **修复**`sv_consume` 的查询加入 `start_date/end_date` 过滤,使其随时间筛选联动
- **文件**`apps/backend/app/services/fdw_queries.py``get_coach_sv_data()`
### P3 — 标签/文案修正
#### Fix-7loyal 维度标签"近60天"→"近90天"
- **问题**ETL 关系指数实际使用 90 天窗口(`lookback_days: 90`),非 60 天
- **修复**:下拉选项文本 `最专一 近60天``最专一 近90天`
- **文件**`apps/miniprogram/miniprogram/pages/board-customer/board-customer.ts``DIMENSION_OPTIONS`
#### Fix-8potential 维度"月均到店"→"近30天到店"
- **问题**:实际是 30 天到店天数(同一天多次只算一天),非"月均到店次数"
- **位置**board-customer 页面 → 最大消费潜力 tab → 4 列网格第 2 格
- **文件**`apps/miniprogram/miniprogram/pages/board-customer/board-customer.wxml`
#### Fix-9recharge 维度"充值"→"累计充值"
- **问题**:实际是 `SUM(pay_amount)` 历史累计充值总额,标签仅"充值"易误解为单次
- **位置**board-customer 页面 → 最近充值 tab → 4 列网格第 2 格
- **文件**`apps/miniprogram/miniprogram/pages/board-customer/board-customer.wxml`
#### Fix-10freq60 维度标签确认
- 此维度标签"最频繁 近60天"与实际口径一致(`visit_count_60d`),无需修改
---
## 三、DWD 合规检查结果(通过)
| 检查项 | 客户看板 | 助教看板 |
|--------|----------|----------|
| consume_money 禁止直接使用 | ✅ 全部走 DWS items_sum | ✅ |
| settle_type IN (1,3) | ✅ 不直查结算表 | ✅ |
| DQ-6 会员姓名通过 dim_member | ✅ | ✅ |
| DQ-7 会员卡通过 dim_member_card_account | ✅ | ✅ |
| 助教费用拆分 pd/cx | N/A | ✅ |
---
## 四、已知但不修的项
| 项 | 原因 |
|----|------|
| A-2sv 维度客户重复计算 | 业务定义确认:每个助教看"我的客户总余额",允许跨助教重叠,作为催促消耗的参考值 |
| A-5后端 `dim_type` 冗余 | 功能无影响,两端映射一致 |
---
## 五、recall_detector 修复 + 任务统计需求(同期实施)
> 需求确认日期2026-04-07
### 背景
`biz.coach_tasks` 当前 255 条记录completed = 0。排查确认
- 系统完成逻辑设计正确(`recall_detector.py` 匹配服务记录→标记 completed
- 但两个技术问题导致完成检测不可靠
- 缺少"助教主动标记完成"能力
- 任务完成统计需要三个维度
### 当前架构(调研结果)
| 服务 | 文件 | 职责 | 调度方式 |
|------|------|------|----------|
| `recall_detector` | `app/services/recall_detector.py` | 检测客户到店→标记召回任务完成→生成回访任务 | event: `etl_data_updated`(当前无人触发) |
| `task_generator` | `app/services/task_generator.py` | 根据 WBI/NCI/RS 指数生成召回/关系维护任务 | cron: `0 4 * * *`(每日凌晨 4 点) |
生成规则(四级漏斗):
- `max(WBI, NCI) > 7``high_priority_recall`
- `max(WBI, NCI) > 5``priority_recall`
- `1 < RS < 6``relationship_building`
- 不满足 → 不生成;`session_count > 0` 无任务时保底补充 `relationship_building`
回溯逻辑(已有):`recall_detector` 检测到新服务记录时,如匹配到活跃的 `follow_up_visit`,关闭旧任务(`superseded_by_new_visit`)并创建新回访。
### Fix-11recall_detector 增量时序 Bug + 编排顺序
**问题**`recall_detector``create_time > last_run_at` 全局增量指针。任务创建与客户到店时序不一致时漏匹配。
**示例时间线**
1. 4/5 08:00 — `recall_detector` 运行,`last_run_at = 08:00`
2. 4/5 14:00 — 客户张三到店,服务记录 `create_time = 14:00`
3. 4/6 02:00 — `task_generator` 为张三创建召回任务
4. 4/6 08:00 — `recall_detector` 再次运行,但 `last_run_at` 已更新,张三 4/5 的记录在上轮已扫描过(当时无任务)→ 永远不会重新匹配
**修复方案**
1. 废弃 `last_run_at` 增量指针,改为:对所有活跃任务,检查 `dwd_settlement_head` 中是否有 `pay_time > task.created_at` 的结算记录(`settle_type IN (1,3)`),匹配 `(site_id, assistant_id, member_id)`
2. 合并运行顺序ETL 完成后,统一编排器按顺序执行:
```
ETL 完成 → HTTP callback → 后端编排器:
Step 1: recall_detector.run() # 先检查完成(含回溯)
Step 2: task_generator.run() # 再生成新任务
```
3. 保留 `task_generator` 的每日 cron`0 4 * * *`)作为兜底
4. 两个服务保持独立文件,仅在调度层串联
**文件**`apps/backend/app/services/recall_detector.py`、调度编排器
### Fix-12ETL 完成后自动触发HTTP callback
**问题**`etl_data_updated` 事件无调用方recall_detector 仅靠手动触发。
**业务影响**ETL 每小时同步完新的飞球数据后,系统不会自动检查召回完成情况。客户回店后,助教看不到任务完成,必须有人手动在管理后台触发。
**修复方案**ETL `api_full` pipeline 完成后,通过 HTTP callback 通知后端。
实现:
1. 后端新增 API`POST /api/internal/etl-completed`(内部接口,仅限本机调用)
2. 该接口触发统一编排:`recall_detector.run()` → `task_generator.run()`
3. ETL orchestrator 在 pipeline 完成后调用此接口
4. 安全:校验来源 IP 或 shared secret
**文件**
- `apps/backend/app/routers/internal.py`(新增)
- `apps/etl/connectors/feiqiu/orchestration/` — pipeline 完成回调
- `apps/backend/app/services/trigger_scheduler.py` — 编排逻辑
### Fix-13任务完成统计三维度
**需求确认**:任务完成分三个统计维度:
| 统计维度 | 触发条件 | 是否需已有任务 | 判定数据源 |
|----------|----------|---------------|-----------|
| 召回完成(广义) | 关联客户来店 + 有结算单 | 不需要,只要是 `dws_member_assistant_relation_index` 中的关联客户 | `dwd_settlement_head``settle_type IN (1,3)` |
| 优先/高优先召回完成 | 已有 `priority_recall` / `high_priority_recall` 任务 + 客户到店 | 需要 | 同上 + `coach_tasks.status = 'completed'` |
| 回访完成 | 完成 `follow_up_visit` 任务(含回溯完成) | 需要 | `coach_tasks` + 回溯检测 |
关键定义:
- **"关联客户"**`dws_member_assistant_relation_index` 中有关系记录的客户
- **"来店"判定**`dwd_settlement_head` 中有结算单(`settle_type IN (1,3)`
- **时间窗口**:不限间隔,只要来了就算一次
- **统计时间范围**:全量(任务系统上线至今)
完成类型标记(两种都要记录):
| 完成类型 | 触发方式 | 当前状态 |
|----------|----------|----------|
| 自动完成 | 客户到店,系统匹配结算记录 | 逻辑存在但有 BugFix-11 + Fix-12 |
| 手动完成 | 助教在小程序中主动标记 | 当前不存在,需新建 |
手动完成需要:
- 小程序端:任务卡片增加"标记完成"按钮
- 后端 API`POST /api/xcx/tasks/{task_id}/complete`body: `{type: "manual", note?: string}`
- 数据库:`coach_tasks` 增加 `completion_type` 字段(`auto` / `manual`
- `coach_task_history` 增加 `action='manual_completed'`
### Fix-14助教看板 task 维度展示
**展示位置**:微信小程序 board-coach 页面 task_desc 排序维度
**统计口径**(按助教/按月):
- 召回完成数:`coach_task_history` 中 `action='completed'` 的任务数(优先+高优先)
- 回访完成数:`follow_up_visit` 类型的完成数(含自动+手动+回溯)
- 广义召回(关联客户来店):从 `dwd_settlement_head` + 关系表统计
**数据来源**`biz.coach_tasks` + `biz.coach_task_history` + ETL 关系/结算表
**时间范围**:全量
**后续**:更详细的趋势报表作为独立需求
### 实施依赖关系
```
Fix-11时序Bug+编排顺序)──┐
├→ Fix-14看板展示
Fix-12ETL 自动触发)────────┤
Fix-13手动完成+三维度统计)─┘
```
建议实施顺序Fix-11 → Fix-12 → Fix-13 → Fix-14

View File

@@ -0,0 +1,171 @@
# 财务看板优化 PRD
> 日期2026-04-08
> 触发:财务看板数据评估,发现数据失真、展示缺陷、指标缺失
---
## 一、Bug 修复
### 1.1 [P2] 会员卡余额快照不变动
**问题**`dws_finance_recharge_summary` 表中 `cash_card_balance`62,674`total_card_balance`67,760连续多日完全相同。当前实际余额约 118KETL 快照值 67K数字严重失真。
**根因**(已调研确认):
`finance_recharge_task.py` 中:
1. `_extract_card_balances()` 只调用一次(传入 `end_date`),所有日期复用同一快照(第 64、89 行)
2. SQL 查询只取 `scd2_is_current = 1``stat_date` 参数传入但未使用(第 197-216 行)
3. 赠送卡余额同样受影响
**DWD 维度表验证**(已确认可用):
- `dim_member_card_account` SCD2 版本充足(主卡类 55,526 个版本,覆盖 53 天)
- 按日有明确余额变化4/6: ¥3,745 → 4/7: ¥9,291 → 4/8: ¥1,587
- 可通过 `scd2_start_time <= stat_date AND scd2_end_time > stat_date` 做 as-of 查询
**修复方案**
- `_extract_card_balances()` 改为按日遍历,每天查该日生效的 SCD2 版本
- `transform()` 中按 `stat_date` 查表,不再共用同一快照
**状态**:待实施
### 1.2 [P2] 充值首充/续费笔数全为 0
**问题**`dws_finance_daily_summary``first_recharge_count``renewal_count` 全为 0`recharge_count` 有值。
**DWD 验证**(已确认源数据正确):
- `dwd_recharge_order.is_first` 字段完好
- 3 月:首充 6 笔 ¥14,996 / 续费 43 笔 ¥204,998
- ETL 聚合时未读取 `is_first` 字段进行分类统计
**修复方案**`finance_recharge_task.py` 中按 `is_first` 分组统计,写入 `first_recharge_count` / `renewal_count`
**状态**:待实施
---
## 二、文案优化
### 2.1 "储值卡结算冲销" → "储值卡消费抵扣"
**位置**
- `board_service.py:984` — 空降级值
- `board_service.py:1023` — 正常取值
- `fdw_queries.py:2898` — 查询层
**状态**:待实施
---
## 三、新增指标
### 3.1 经营一览 — 补充效率指标
在经营一览板块"实收流水"数据下方新增一行3 个指标横向排列:
```
┌──────────┬──────────┬──────────┐
│ 开台数 │ 客单价 │ 日均额 │
│ 3,262 │ ¥128.7 │ ¥13,991 │
└──────────┴──────────┴──────────┘
```
#### 计算方案
> 注意:客单价和日均额的分子使用**确认收入**(发生额-优惠后的实际入账),与所在位置"实收流水"语境一致。
| 指标 | 公式 | 数据来源 | 说明 |
|---|---|---|---|
| 开台数 | `SUM(order_count)` | `dws_finance_daily_summary.order_count` | 日期范围内结算单总数 |
| 客单价 | `SUM(confirmed_income) / SUM(order_count)` | 同上 | 确认收入 / 开台数 |
| 日均额 | `SUM(confirmed_income) / COUNT(DISTINCT stat_date)` | 同上 | 确认收入 / 营业天数 |
3 月实测值:开台 3,262 | 客单价 ¥128.7 | 日均额 ¥13,991
#### 展示方案
- 位置:经营一览板块"实收流水"数据下方
- 样式3 列等宽卡片,灰色标签 + 黑色数值
- 环比支持compare=1 时显示环比变化)
- 区域过滤area!=all 时,从 `dws_finance_area_daily` 对应 area_code 取 `order_count` + `confirmed_income`
#### 实现影响
- **后端**`fdw_queries.get_finance_overview()` 追加返回 `order_count``board_service._build_overview()` 计算 `avg_per_order``avg_daily`
- **前端**`board-finance.wxml` 经营一览实收流水下方加一行三列
### 3.2 预收资产 — 补充充值笔数
在储值卡充值实收行追加笔数信息:
```
储值卡充值实收 ¥222,994 50笔
```
> 首充/续费区分待 Bug 1.2 修复后展开为 `50笔首充6 / 续费44`
#### 计算方案
| 指标 | 公式 | 数据来源 |
|---|---|---|
| 充值笔数 | `SUM(recharge_count)` | `dws_finance_recharge_summary.recharge_count` |
#### 展示方案
- 位置:储值卡充值实收金额右侧
- 格式:`50笔`
- 环比:暂不做
### 3.3 应计收入 — 优惠扣减增加占比
每个优惠项显示金额 + 占总优惠的百分比:
```
团购优惠 ¥48,231 23.6%
会员折扣 ¥89,102 43.6%
手动调整 ¥31,540 15.4%
赠送卡抵扣 ¥28,230 13.8%
其他优惠 ¥7,209 3.5%
────────────────────────
总优惠 ¥204,312 100%
```
#### 计算方案
| 指标 | 公式 |
|---|---|
| 各项占比 | `该项金额 / discount_total * 100` |
#### 展示方案
- 位置:每个优惠项金额右侧
- 格式:`23.6%`,灰色小字
- 仅在总优惠 > 0 时显示占比
### 3.4 团购展示优化
#### 背景
团购数据上传时已上传的是**结算金额**(扣除平台各种扣费后的实际回款),因此不需要额外展示手续费和面值拆分。
#### 方案
将现金流入板块中"团购平台"标签改为"团购结算",明确表达该金额是平台结算后到账金额,避免与核销面值混淆。
**位置**:后端 `board_service.py` / `fdw_queries.py` 中 cashflow 板块的团购项 label
**状态**:待实施
---
## 四、实施优先级
| 优先级 | 事项 | 工作量 |
|---|---|---|
| P1 | 文案优化(储值卡消费抵扣) | 小 |
| P1 | 经营一览补充效率指标(开台数/客单价/日均额) | 中 |
| P1 | 团购标签改为"团购结算" | 小 |
| P2 | 优惠占比展示 | 小 |
| P2 | 充值笔数展示 | 小 |
| P2 | 卡余额快照修复Bug 1.1 | 中 |
| P2 | 充值首充/续费识别修复Bug 1.2 | 小 |