建立项目级标杆文档 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 残留, 已修 commit17f045a) - P0-5 致命 2 (JWT aud 缺失, 已修 commit17f045a) - P0-6 clearAllTasks 守卫 (Wave 3) - P0-8 DBViewer 黑名单漏 (已修 commit17f045a) - 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
19 KiB
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 不是课程,是项目分类。课程的调研不对,不够深入,课程全部应该有 包厢课 麻将课 基础课 激励课 但应该分布在不同的表中。"
解读:
- 课程 ≠ 场地分类(
category_code):H-1 把cfg_area_category当成课程候选,这条路是错的(它在category_code列里映射的是BILLIARD/SNOOKER/MAHJONG/KTV等"项目分类",描述台桌而非课程)。 - 课程至少 4 类:基础课 / 包厢课 / 麻将课 / 激励课。
- 分布在不同表中:不是一张表的 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_hours、bonus_hours、room_hours、base_income、bonus_income、room_income |
工资按 3 类计算 |
| DWS 财务 | dws.dws_assistant_finance_analysis |
revenue_base、revenue_bonus、revenue_room、room_service_count、room_service_hours |
助教营收按 3 类拆分 |
| DWS 关系 | dws.dws_member_assistant_intimacy |
basic_session_count、incentive_session_count |
会员-助教亲密度按 2 类拆分 |
| DWS 关系 | dws.dws_member_assistant_relation_index |
basic_session_count、incentive_session_count |
关系指数按 2 类拆分 |
| DWS 区域 | dws.dws_coach_area_hours |
base_hours、bonus_hours、room_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.ts 和 performance-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 个关键点:
- 174 条飞球包厢课订单已落 ODS / DWD,被错误归入 BASE(因 cfg_skill_type 把它们标成 BASE)
- room_ 列在 4 张 DWS 表(daily/salary/finance/coach_area)同时预留,全 0*
assistant_finance_task.py:146写了WHEN ... = 'ROOM'的 SQL 分支(永不命中,但代码意图清晰)- 关系指数模块用
basic_session_count/incentive_session_count命名,不与base_*/bonus_*对齐(同概念两套命名) - 前端文案 4 个别名("激励课"/"超休"/"打赏课"/"附加课")混用,没有统一术语表
六、修正后的 P2-4 选项(给 Neo 决策)
选项 A:统一术语 + 启用 ROOM(最小改动,贴近现状)
- 把
cfg_skill_type中"包厢课"和"包厢服务"两行的course_type_code从BASE改为ROOM - 重跑 dws_assistant_daily_detail / salary_calc / finance_analysis / coach_area_hours,让 174 条订单进入
room_*列 - 决定
room_course_price真实值(目前 CLAUDE.md 写 138 元/小时,但 cfg_assistant_level_price 没有 room_price 列 — 需要补字段或借用现有规则) - 统一前端"激励课"术语:performance.ts 已经做了别名映射,但其他页面要全检
- 不引入麻将课
适用场景:Neo 只是想看到包厢课独立,不想全面铺开。 代价:1 次 cfg_skill_type 数据修改 + 1 次回填 + 前端术语清理 + 价格字段补齐。
选项 B:课程类型完整化(4 类全开,新增 MAHJONG)
cfg_skill_type.course_type_codeCHECK 约束扩展支持BASE/BONUS/ROOM/MAHJONGdws_assistant_daily_detail/salary_calc/coach_area_hours增加mahjong_*列(count/seconds/hours/ledger_amount)cfg_assistant_level_price增加room_course_price、mahjong_course_price两列- 关系指数表增加
room_session_count、mahjong_session_count(以及把basic_session_count重命名为base_session_count对齐) - ETL 4 任务全部改写
- 飞球上游目前没有"麻将课" skill 数据 — 需要店铺侧手动在飞球后台新建 skill,或在 NeoZQYY 侧用台桌区域(area_category=MAHJONG)反推
- 前端 task-list / performance / coach-detail / customer-detail / board-coach 全部增加第 3、4 类展示
适用场景:Neo 真的要按 4 类做长期产品规划。 代价:大改动,4 张 DWS 表 schema + 5 个 ETL 任务 + 前端多页 + 飞球上游配合。
选项 C:三层澄清(暂不增数据列,先消除认知冲突)
- 不动 schema,只产出权威术语对照表归档
docs/_overview/04c-glossary-courses.md - 明确:"激励课 = 附加课 = 超休 = 打赏课 = BONUS"是同一概念
- 明确:"包厢课"目前合并在 BASE,需要后续单独建模(选项 A)
- 明确:"麻将课"目前不存在,需要后续业务决策(选项 B 或不做)
- 把现有
room_*列 +CourseType.ROOM枚举 +mahjong_*概念 全部标记为"预留,未启用" - 修正 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_code 是 BASE — 这是配置数据有意为之的合并,不是 bug。
要让 ROOM 列生效,需要把它们改成 ROOM + 给 cfg_assistant_level_price 加 room_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 的归类口径。
十、总结(给主流程)
- 课程实际分布:不是 H-1 说的"BASE+BONUS 两类",也不是 Neo 提的"4 类独立分布",而是 "BASE + BONUS 完整,ROOM 半残空表,MAHJONG 空白"。
- 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(原始)。 - 关键 H-1 错误:漏报 cfg_skill_type 实际 6 行(不是 2 行)、漏报 174 条真实包厢课订单被错误合并进 BASE、未提"激励课"4 套别名混用、未识别关系指数模块的命名错位。
- 不存在统一课程表:NeoZQYY 用"skill_id → course_type_code"映射 + DWS 派生列展开的方式表达课程,没有
dim_course/cfg_course这种独立维度。 - 决策清单 6 项:D1(包厢课独立) / D2(麻将课新增) / D3(术语统一) / D4(ROOM 处置) / D5(关系指数命名) / D6(包厢定价字段)。