Files
Neo-ZQYY/docs/_overview/04c-feedback/P2-4-course-system-deep-research.md
Neo 509cf43284 chore(docs): Wave 0 调研产出 + P0/P1/P2 反馈调研
建立项目级标杆文档 docs/_overview/ 作为产品全景索引,
解决"PRD 零碎、文档膨胀、跨子系统调研无入口"的问题。

主要内容:
- 00-index 总索引 + 维护协议 + 与 CLAUDE.md 关系
- 01-product-overview 产品全景脑图(6 角色 / 6 子系统 / 数据流 /
  7 业务概念 / 8+1 AI 矩阵 / 22 术语)
- 02a-miniprogram-page-matrix 小程序 21 页业务指纹
- 02b-adminweb-page-matrix admin-web 19 路由业务指纹
- 03-test-spec 测试规范 (L1-L5 分层 + 走查模板 + 75-95 case 估算)
- 04-doc-conflicts 39 条冲突索引(P0×8 / P1×13 / P2×13 + 5 子项)
- 04a/b/c-conflicts-*-detail 业务故事卡(7 字段:关联/逻辑/影响/选项/判定)
- 05-orphan-pages-cleanup admin-web 6 孤儿页面处置(1 归档 + 4 保留)
- WAVES-MASTER-PLAN.md 全 Wave 主计划(0-5,共 22-32 工作日)
- WAVE-1-KICKOFF.md Wave 1 实施 kickoff
- GLOBAL-DECISION-DASHBOARD.md 全局决策仪表板

反馈调研产物:
- 04a-feedback/ P0 两轮反馈(8+8 项决策 + D-1/2/3 + F-1/2 子代理产出)
- 04b-feedback/ P1 两轮反馈(13+1+5 项 + E-1/2/3/4 + G-1/2 子代理产出)
- 04c-feedback/ P2 反馈(13 项 + 5 子项 + H-1/2/3 子代理产出)
- NEO-DECISIONS-LOG 累积决策记录

关键追加发现 8 处 D Bug(原蓝本 0):
- P0-3 看板沙箱接入(Wave 1 W1-T1)
- P0-5 致命 1 (4 处 fdw_etl 残留, 已修 commit 17f045a)
- P0-5 致命 2 (JWT aud 缺失, 已修 commit 17f045a)
- P0-6 clearAllTasks 守卫 (Wave 3)
- P0-8 DBViewer 黑名单漏 (已修 commit 17f045a)
- P1-3 task-detail 跳转传 task_id 而非 customer_id
- P2-7 board-finance 隐式 null
- 2 个独立 Bug (page_context.created_at + ClueCategory 字典)

参考: docs/_overview/00-index.md
2026-05-04 07:38:28 +08:00

19 KiB
Raw Blame History

P2-4 课程体系深度调研(第二轮)

日期:2026-05-04 触发:Neo 否决 H-1 调研结论(P2-4-and-P2-7-research.md):"课程的调研不对,不够深入,课程全部应该有 包厢课 麻将课 基础课 激励课 但应该分布在不同的表中" 范围:重新调研课程体系,把上游 SaaS 真实 skill、配置表、DWD 明细、DWS 派生列、ETL 代码、前端文案统一对齐 数据库:test_etl_feiqiu(只读 SELECT) 替代关系:本文件取代 P2-4-and-P2-7-research.md 中的 P2-4 部分。P2-7 部分仍以原文件为准。


一、Neo 提示的关键(逐字引用)

"category_code 不是课程,是项目分类。课程的调研不对,不够深入,课程全部应该有 包厢课 麻将课 基础课 激励课 但应该分布在不同的表中。"

解读:

  1. 课程 ≠ 场地分类(category_code):H-1 把 cfg_area_category 当成课程候选,这条路是错的(它在 category_code 列里映射的是 BILLIARD/SNOOKER/MAHJONG/KTV 等"项目分类",描述台桌而非课程)。
  2. 课程至少 4 类:基础课 / 包厢课 / 麻将课 / 激励课。
  3. 分布在不同表中:不是一张表的 4 行,是多张表的不同列、不同模块各自表达一部分。

二、表搜索全集(只读,2026-05-04)

2.1 schema 全表名搜索结果(命中 course/lesson/class/room/mahjong/incentive/bonus/skill)

schema table 命中关键字
dws cfg_skill_type skill
dws cfg_bonus_rules bonus
app v_cfg_bonus_rules bonus(RLS 视图)

注意:没有任何表名以 cfg_course_*dim_course_*mahjong_*incentive_*lesson_* 命名。也就是说"课程"在 NeoZQYY 中不是一个独立的实体,而是依附在助教服务记录上的一个类型属性

2.2 含课程相关字段的表(course/lesson/class_type/room_type/mahjong/bonus_type/incentive)

模块 字段 用途
配置 dws.cfg_skill_type skill_id, skill_name, course_type_code(BASE/BONUS/ROOM), course_type_name skill_id → 课程类型映射
配置 dws.cfg_assistant_level_price base_course_price, bonus_course_price 等级 × 课程类型定价
DWS 明细 dws.dws_assistant_daily_detail base_*bonus_*room_*(共 12 列:count/seconds/hours/ledger_amount × 3 类型) 助教日级三维拆分
DWS 工资 dws.dws_assistant_salary_calc base_hoursbonus_hoursroom_hoursbase_incomebonus_incomeroom_income 工资按 3 类计算
DWS 财务 dws.dws_assistant_finance_analysis revenue_baserevenue_bonusrevenue_roomroom_service_countroom_service_hours 助教营收按 3 类拆分
DWS 关系 dws.dws_member_assistant_intimacy basic_session_countincentive_session_count 会员-助教亲密度按 2 类拆分
DWS 关系 dws.dws_member_assistant_relation_index basic_session_countincentive_session_count 关系指数按 2 类拆分
DWS 区域 dws.dws_coach_area_hours base_hoursbonus_hoursroom_hours*_service_count 助教区域工时按 3 类
DWD 明细 dwd.dwd_assistant_service_log skill_id, skill_name(原始字符串) 单笔服务原始 skill
ODS 上游 ods.assistant_service_records skill_id, skillname(飞球 SaaS 原始字段) 来自飞球 API

三、4 类课程的实际分布

核心结论:NeoZQYY 中"课程"以三个不同抽象层、四个不同模块各自表达,不存在统一的课程表。

3.1 基础课(BASE) — 全链路一等公民

实例
ODS assistant_service_records.skillname='基础课',9 095 条有效订单(2025-09-16 ~ 2026-04-23)
配置 cfg_skill_type 映射 skill_id ∈ {2790683529513797(基础课), 2791903611396869(台球基础陪打)}course_type_code='BASE'(course_type_name='基础课')
代码 base_dws_task.py::CourseType.BASE
DWS 列 dws_assistant_daily_detail.base_service_count/base_hours/base_seconds/base_ledger_amount(已写入,2026 年 1-4 月有数据)
工资 dws_assistant_salary_calc.base_hours / base_income(按 cfg_assistant_level_price.base_course_price × 等级 计价,初/中/高/星 = 98/108/118/138 元)
关系指数 dws_member_assistant_intimacy.basic_session_count(注意:这里是 basic 不是 base,命名不一致)
前端文案 "基础课"(performance/coach-detail/task-list/customer-detail)
别名 "陪打"、"PD"、assistant_pd_money

3.2 包厢课(ROOM) — 设计存在但实际未生效

实例
ODS skillname='包厢课',174 条有效订单(2026-01-06 ~ 2026-04-26)
配置 cfg_skill_type 中"包厢课"(skill_id=3039912271463941)和"包厢服务"(skill_id=2807440316432198)被映射为 course_type_code='BASE'(注释说"包厢服务:归入基础课统计,统一按 138 元/小时计价";"包厢课:与'包厢服务'同类")
代码 base_dws_task.py::CourseType.ROOM 枚举存在;get_course_type()if code == 'ROOM': return CourseType.ROOM 分支存在,但永不命中(因配置表无 ROOM 行)
DWS 列 dws_assistant_daily_detail.room_service_count/room_hours/room_seconds/room_ledger_amount 列存在,全 0(2942 行均为 0)
工资 dws_assistant_salary_calc.room_hours / room_income 列存在,全 0(10 个月全 0)
财务 dws_assistant_finance_analysis.revenue_room SQL 中 WHEN ... = 'ROOM' 分支存在,永不命中
现状 174 条飞球原始包厢课订单实际被合并进 BASE(因为 cfg_skill_type 把它们都标成 BASE)
文档 CLAUDE.md 写"包厢课统一 138 元/小时(dws.salary.room_course_price)";assistant_finance_task.py:146 注释写 ROOM 分类

这是 H-1 漏掉的最关键发现:ROOM 是设计预留 + 死代码组合,不是 H-1 说的"纯死代码可移除"。 174 条真实包厢订单存在,Neo 心目中"包厢课要独立"是合理需求,只是配置表没把它们标出来。

3.3 麻将课(MAHJONG) — 数据库与代码层完全空白

实例
ODS assistant_service_records.skillname没有"麻将课"(只有"基础课"/"附加课"/"包厢课"/NULL 共 4 种)
飞球上游 飞球 SaaS 没有"麻将"作为助教 skill
配置 cfg_skill_type 没有 MAHJONG 行
代码 CourseType 枚举没有 MAHJONG(只有 BASE/BONUS/ROOM)
DWS 列 没有 mahjong_*
文档 仅在 cfg_area_category.category_code='MAHJONG' 中出现(场地分类,不是课程)
前端 没有"麻将课"文案

结论:"麻将课"在 NeoZQYY 完全不存在。它是 Neo 在业务规划层提出的概念,当前所有表、代码、配置、上游都没有对应的载体

可能的语义:店铺用麻将台桌时,助教提供的服务可能也叫"麻将课"。但飞球助教 skill 当前不区分这种,落到 ODS 时 skillname 仍是"基础课"。

3.4 激励课(INCENTIVE) — 实际就是 BONUS,有别名混乱

实例
ODS skillname='附加课',985 条有效订单
配置 cfg_skill_type skill_id ∈ {2790683529513798(附加课), 2807440316432197(台球超休服务)}course_type_code='BONUS',course_type_name='附加课',description='附加课:超休/激励课,固定 190 元/小时'
代码 CourseType.BONUS;relation_index_task.py:268 is_incentive = course_type == CourseType.BONUS
DWS 列 bonus_service_count/bonus_hours/bonus_seconds/bonus_ledger_amount(已写入,2 942 行中 574 行有 bonus)
关系 incentive_session_count(BD 手册写"附加课服务次数",但列名用 incentive)
工资 bonus_income(单价 190,bonus_deduction_ratio 35%~50%)
前端文案 "激励课" / "超休" / "打赏课" 三种说法在前端都用performance.tsperformance-records.ts 中明确写 '超休': 'incentive', '激励课': 'incentive', '打赏课': 'incentive'(三个上游别名都映射到 incentive CSS 标签)
BD-DWD-DOC 称为"助教激励课正价 / assistant_cx_money",规则 2 强制要求 assistant_cx_money 表示 BONUS
别名总集 "附加课"(配置/ODS) / "激励课"(前端文案/Neo 用语) / "超休"(用户口语) / "打赏课"(用户口语) / BONUS(代码枚举) / cx(SQL/费用字段)

结论:"激励课"和"BONUS"和"附加课"和"超休"是同一个东西,只是叫法不一致。Neo 提的"激励课"不是新维度,只是更口语化的命名。


四、4 类是否真的"分布在不同表中"

回答 Neo 的核心判断:部分正确,但分布的不是"4 类"而是"多种抽象组合"

类别 是否独立存在 在哪些表/列体现
基础课(BASE) 是,完整 cfg_skill_type、dwd_assistant_service_log.skill_id、dws..base_(daily/salary/finance/coach_area) + intimacy.basic_session_count
激励课(BONUS) 是,完整 同上替换为 bonus_*、intimacy.incentive_session_count
包厢课(ROOM) 半残:DWD 有 174 条原始订单,DWS 列预留但全 0,代码 ROOM 分支永不命中 dws..room_(daily/salary/finance/coach_area)全 0;cfg_skill_type 把它们标成 BASE 而不是 ROOM
麻将课(MAHJONG) 不存在:仅在台桌分类中有 MAHJONG 区(cfg_area_category),从未作为课程类型落库 没有任何相关列、配置、代码

所以"分布在不同表"只对前两类成立(基础课和激励课确实在 daily/salary/finance/intimacy/relation_index 多表多列各自展开)。 包厢课是半成品,麻将课是空白


五、与 H-1 调研对照(必须修正的错误)

H-1 报告(P2-4-and-P2-7-research.md 第 87-115 行)说:

H-1 结论 实际情况 修正
"cfg_skill_type 只有 2 行" 6 行(基础课/附加课各 3 个 skill_id 别名都已建好) H-1 漏掉了 SKill_id 4/5/6(2026-03-24 通过 add_missing_cfg_skill_type.sql 补齐的"基础课/附加课/包厢课"原始 skill)
"ROOM 是死代码,所有 ROOM 分支永不命中" 命中是真的不命中(配置无 ROOM 行),但DWD 有 174 条真实包厢订单正在被错误合并进 BASE 不是"死代码可移除",而是"配置缺失导致包厢数据被吞"
"包厢课在 NeoZQYY 不需要" Neo 明确要求"包厢课"独立 需要补齐(改 cfg_skill_type 把包厢类 skill 标成 ROOM)
完全没提"麻将课" Neo 提的 4 类之一 需在产品层决策"是否新增麻将课作为第 4 类"
没区分"激励课 / 附加课 / 超休 / 打赏课"的别名问题 前端 4 套文案,后端 BONUS,配置"附加课",上游 SaaS"附加课/超休服务" 应在"统一术语"层面收口

H-1 漏掉的 5 个关键点:

  1. 174 条飞球包厢课订单已落 ODS / DWD,被错误归入 BASE(因 cfg_skill_type 把它们标成 BASE)
  2. room_ 列在 4 张 DWS 表(daily/salary/finance/coach_area)同时预留,全 0*
  3. assistant_finance_task.py:146 写了 WHEN ... = 'ROOM' 的 SQL 分支(永不命中,但代码意图清晰)
  4. 关系指数模块用 basic_session_count / incentive_session_count 命名,不与 base_* / bonus_* 对齐(同概念两套命名)
  5. 前端文案 4 个别名("激励课"/"超休"/"打赏课"/"附加课")混用,没有统一术语表

六、修正后的 P2-4 选项(给 Neo 决策)

选项 A:统一术语 + 启用 ROOM(最小改动,贴近现状)

  1. cfg_skill_type 中"包厢课"和"包厢服务"两行的 course_type_codeBASE 改为 ROOM
  2. 重跑 dws_assistant_daily_detail / salary_calc / finance_analysis / coach_area_hours,让 174 条订单进入 room_*
  3. 决定 room_course_price 真实值(目前 CLAUDE.md 写 138 元/小时,但 cfg_assistant_level_price 没有 room_price 列 — 需要补字段或借用现有规则)
  4. 统一前端"激励课"术语:performance.ts 已经做了别名映射,但其他页面要全检
  5. 不引入麻将课

适用场景:Neo 只是想看到包厢课独立,不想全面铺开。 代价:1 次 cfg_skill_type 数据修改 + 1 次回填 + 前端术语清理 + 价格字段补齐。

选项 B:课程类型完整化(4 类全开,新增 MAHJONG)

  1. cfg_skill_type.course_type_code CHECK 约束扩展支持 BASE/BONUS/ROOM/MAHJONG
  2. dws_assistant_daily_detail / salary_calc / coach_area_hours 增加 mahjong_* 列(count/seconds/hours/ledger_amount)
  3. cfg_assistant_level_price 增加 room_course_pricemahjong_course_price 两列
  4. 关系指数表增加 room_session_countmahjong_session_count(以及把 basic_session_count 重命名为 base_session_count 对齐)
  5. ETL 4 任务全部改写
  6. 飞球上游目前没有"麻将课" skill 数据 — 需要店铺侧手动在飞球后台新建 skill,或在 NeoZQYY 侧用台桌区域(area_category=MAHJONG)反推
  7. 前端 task-list / performance / coach-detail / customer-detail / board-coach 全部增加第 3、4 类展示

适用场景:Neo 真的要按 4 类做长期产品规划。 代价:大改动,4 张 DWS 表 schema + 5 个 ETL 任务 + 前端多页 + 飞球上游配合。

选项 C:三层澄清(暂不增数据列,先消除认知冲突)

  1. 不动 schema,只产出权威术语对照表归档 docs/_overview/04c-glossary-courses.md
  2. 明确:"激励课 = 附加课 = 超休 = 打赏课 = BONUS"是同一概念
  3. 明确:"包厢课"目前合并在 BASE,需要后续单独建模(选项 A)
  4. 明确:"麻将课"目前不存在,需要后续业务决策(选项 B 或不做)
  5. 把现有 room_* 列 + CourseType.ROOM 枚举 + mahjong_* 概念 全部标记为"预留,未启用"
  6. 修正 BD 手册中"附加课"/"激励课"不一致的注释

适用场景:Neo 只是想确认"我提的 4 类系统当前实际分布",不一定要立刻改实现。 代价:零代码改动,只写文档。


七、给 Neo 的决策清单

编号 决策点 选项 影响范围
D1 "包厢课"是否独立成第 3 类 (a)合并 BASE 不变 (b)启用 ROOM(改 cfg_skill_type+回填) (c)不动 schema 只标记 DWS 4 表回填 + 前端展示
D2 "麻将课"是否新增为第 4 类 (a)不引入 (b)新增 MAHJONG(大改) DWS 列新增 + 前端 + 飞球上游配合
D3 "激励课/附加课/超休/打赏课" 是否统一术语 (a)前端统一为"激励课" (b)统一为"附加课" (c)保持现状 前端 5+ 页面 + BD 手册 + 后端 schema 注释
D4 room_* / CourseType.ROOM 当前状态 (a)启用并回填 (b)删除并清理 (c)保留预留并文档化 4 张 DWS 表 + ETL 4 任务 + 后端服务
D5 关系指数模块 basic_session_count vs DWS 通用 base_* 命名不一致 (a)统一命名(改列名) (b)在文档说明同义 (c)保持现状 关系表迁移 + 后端 schema
D6 cfg_assistant_level_price.room_course_price 缺失 (a)增加列 (b)用单一常量 138 写死 (c)等 D1 决策后再处理 cfg_assistant_level_price 表结构

八、命名一致性问题(派生发现)

概念 DWS 主流命名 关系指数命名 前端 CSS 后端字段 上游
基础课 base_* basic_session_count course-tag--base assistant_pd_money(PD) "基础课"/"台球基础陪打"
激励课 bonus_* incentive_session_count course-tag--incentive assistant_cx_money(CX) "附加课"/"台球超休服务"
包厢课 room_*(全 0) "包厢课"/"包厢服务"
麻将课

5 套命名混用是高熵问题,长期容易踩坑。建议在 D3/D5 决策时一并收口。


九、实际 ODS / DWD 课程数据样本(2026-05-04 测试库,脱敏)

ODS 上游 assistant_service_records skillname 实际分布(有效订单,is_trash=0 AND is_delete=0):

skillname 订单数 首次日期 最近日期
基础课 9 095 2025-09-16 2026-04-23
(NULL) 7 382 2025-07-21 2025-11-30
附加课 985 2025-09-18 2026-04-18
包厢课 174 2026-01-06 2026-04-26

注意:NULL 7 382 条主要在 2025-07~11 期间,2025-12 起 skillname 全部填充。说明飞球 SaaS 在 2025-12 之后才补全 skillname 字段 — 这是另一个数据质量问题。

cfg_skill_type 全量(6 行):

id skill_id skill_name course_type_code description
1 2791903611396869 台球基础陪打 BASE 基础课:陪打服务,按助教等级计价
2 2807440316432197 台球超休服务 BONUS 附加课:超休/激励课,固定 190 元/小时
3 2807440316432198 包厢服务 BASE 包厢服务:归入基础课统计,统一按 138 元/小时计价
4 2790683529513797 基础课 BASE 基础课:飞球系统原始课程类型,与"台球基础陪打"同类
5 2790683529513798 附加课 BONUS 附加课:飞球系统原始课程类型,与"台球超休服务"同类
6 3039912271463941 包厢课 BASE 包厢课:飞球系统原始课程类型,与"包厢服务"同类

注意:第 3、6 行注释里写"包厢服务/包厢课"但 course_type_codeBASE — 这是配置数据有意为之的合并,不是 bug。 要让 ROOM 列生效,需要把它们改成 ROOM + 给 cfg_assistant_level_priceroom_course_price 列(或使用现有的 BASE 价 + 一个 ROOM 专属规则)。

DWS dws_assistant_daily_detail 整体统计(2 942 行):

指标 base bonus room
非零行数 2 833 574 0
累计服务次数 5 237 645 0
累计金额 1 546 983.80 301 910.00 0.00

DWS dws_assistant_salary_calc 月度统计(10 个月):room_hours / room_income 全部为 0.00

DWS dws_member_assistant_intimacy 整体统计(219 行):

指标 数值
总会话 698
basic_session_count 698
incentive_session_count 0

注意:亲密度模块的 incentive_session_count 也全 0。这与 daily_detail 的 bonus(574 行非零)不一致,值得在选项 A 决策后再深查 relation_index_task.py 的归类口径。


十、总结(给主流程)

  1. 课程实际分布:不是 H-1 说的"BASE+BONUS 两类",也不是 Neo 提的"4 类独立分布",而是 "BASE + BONUS 完整,ROOM 半残空表,MAHJONG 空白"
  2. 5 处分布载体:cfg_skill_type(配置)、cfg_assistant_level_price(定价)、dws_assistant_daily_detail/salary_calc/finance_analysis/coach_area_hours(派生汇总)、dws_member_assistant_intimacy/relation_index(关系)、dwd_assistant_service_log + ods.assistant_service_records(原始)。
  3. 关键 H-1 错误:漏报 cfg_skill_type 实际 6 行(不是 2 行)、漏报 174 条真实包厢课订单被错误合并进 BASE、未提"激励课"4 套别名混用、未识别关系指数模块的命名错位。
  4. 不存在统一课程表:NeoZQYY 用"skill_id → course_type_code"映射 + DWS 派生列展开的方式表达课程,没有 dim_course / cfg_course 这种独立维度。
  5. 决策清单 6 项:D1(包厢课独立) / D2(麻将课新增) / D3(术语统一) / D4(ROOM 处置) / D5(关系指数命名) / D6(包厢定价字段)。