台球厅数仓 DWD 层数据库说明书
本说明书详细列出了台球厅经营系统的 DWD 层表结构。
每张表都包含字段名称、数据类型、来源、含义、是否属于主键/外键、业务重要性、未知作用标记，以及枚举值解释。说明书依据《*-Analysis.md》中提供的字段说明整理完成，未出现省略号，确保字段信息完整可追溯。
因业务需求，将一个表拆成主数据表和扩展数据表（Ex为后缀），如：维度表的门店数据表分为主表dim_site 和扩展表dim_site_Ex，主键相同，作为唯一关联标识。在业务代码处理的读和写时，使用统一处理方式，将数据视为一个表格。注意，极少数表，没有扩展表。
注意：考虑到后期分布式部署，以及测试的便利性。所有的“外键”的处理，使用业务处理，不在数据库中强制约束。
维度表（DIM）
dim_site
门店维度表，提取自各 ODS 中的 siteProfile 对象，如table_fee_transactions。记录门店的基本信息和配置，是其他事实表的外键。
dim_site_Ex
dim_table
台桌维度表，来自 site_tables_master。每行代表一张球台或包厢，包含区域和业务角色信息。
dim_table_Ex
dim_assistant
助教档案维表，对应 assistant_accounts_master。每行代表一位助教账号及其人事/账号状态。
dim_assistant_Ex
dim_member
会员档案维表，对应 member_profiles。每行记录租户内某会员的主档信息，包括等级、状态、注册信息等。
dim_member_Ex
dim_member_card_account
已开通的会员卡账户视图，来自 member_stored_value_cards。每行代表一张会员卡账户的快照，记录卡种、持卡人、余额、有效期及各种折扣/扣款配置。
重要说明：本视图不仅包含储值卡，还囊括活动抵用券、台费卡、酒水卡、月卡等多种卡种。
大多数折扣/扣款字段在当前数据中保持默认值（如 10.0 表示不打折、100.0 表示全额抵扣、0 表示不启用），业务上暂未使用，但为系统预留能力。
dim_member_card_account_Ex
dim_tenant_goods
租户级商品档案，来自 tenant_goods_master。每行代表一款商品标准定义。
dim_tenant_goods_Ex
dim_store_goods
门店级商品档案，来自 store_goods_master.json。每行代表门店自定义的商品 SKU，包括售价和折扣。关联到 dim_tenant_goods 和分类维度。
dim_store_goods_Ex
dim_goods_category
商品分类索引树，来自 stock_goods_category_tree.json。每行是一个分类节点。
categoryBoxes 是“某个分类节点下面的子分类列表”，整个文件里只有两层：根节点 + 子节点两级，不存在孙节点。
每个 categoryBoxes 里的元素结构与根节点完全一致（同样的 11 个字段），只是 pid 指向父节点的 id，categoryBoxes 为空。
同一个分类树在 JSON 里分页返回了两次，goodsCategoryList 和每个 categoryBoxes 在两个 page 中完全重复，真正的不同分类节点一共只有 26 个。
从数仓角度，树结构的“真实关系”完全由 id 和 pid 就可以表达，categoryBoxes 更像是前端为了直接画树而准备的冗余展开结果，在 DWD 里不需要原样存这一坨结构，只需要被“打散”成一行一个节点。
下面我把完整的 categoryBoxes 结构按业务和数据的视角展开给你看。
一、整体结构：categoryBoxes 是子分类数组，深度只有两层
stock_goods_category_tree.json 顶层是一个分页数组 pages，每个元素形如：
{
"code": 0,
"data": {
"total": 9,
"goodsCategoryList": [ 根分类1, 根分类2, ... 共9个 ]
}
}
每个“根分类”对象都有这些字段：
id
tenant_id
category_name
alias_name
pid
business_name
tenant_goods_business_id
open_salesman
categoryBoxes
sort
is_warehousing
其中：
pid = 0 表示根分类。
categoryBoxes 是一个数组，里面放的是子分类节点对象。
子分类对象和根分类字段完全一样，只是：
pid = 父节点 id
categoryBoxes = []
两个 page 的 goodsCategoryList 完全相同，所以你看到的 18 个“根”其实是相同的 9 个重复了两次，categoryBoxes 里的子节点也重复了两次。按照去重后的真实结构：
根节点 9 个。
子节点 17 个。
总共 26 个不同的 id。
所以“完整的 categoryBoxes”其实就是：9 个根节点各自带着若干子节点。
二、逐个根节点把 categoryBoxes 展开给你看
下面按“根分类 → categoryBoxes 子分类”的树形方式列一遍，便于你直观看到完整结构。
1. 根：槟榔
根节点：
id = 2790683528350533
category_name = 槟榔
business_name = 槟榔
pid = 0
categoryBoxes 只有一个子分类：
子分类 1：
id = 2790683528350534
category_name = 槟榔
business_name = 槟榔
pid = 2790683528350533
categoryBoxes = []
其它字段：tenant_id、tenant_goods_business_id、is_warehousing、open_salesman、sort 与父节点一致。
可以理解为：业务线“槟榔”，下面只有一个细分类“槟榔”。
2. 根：器材
根节点：
id = 2790683528350535
category_name = 器材
business_name = 器材
pid = 0
categoryBoxes 子分类 3 个：
子分类 1：
id = 2790683528350536
category_name = 皮头
pid = 2790683528350535
子分类 2：
id = 2790683528350537
category_name = 球杆
pid = 2790683528350535
子分类 3：
id = 2790683528350538
category_name = 其他
pid = 2790683528350535
这条业务线代表所有“器材相关商品”，细分为皮头、球杆、器材其他。
3. 根：酒水
根节点：
id = 2790683528350539
category_name = 酒水
business_name = 酒水
pid = 0
categoryBoxes 子分类 6 个：
子分类 1：饮料
id = 2790683528350540
category_name = 饮料
pid = 2790683528350539
子分类 2：酒水
id = 2790683528350541
category_name = 酒水
pid = 2790683528350539
子分类 3：茶水
id = 2790683528350542
category_name = 茶水
pid = 2790683528350539
子分类 4：咖啡
id = 2790683528350543
category_name = 咖啡
pid = 2790683528350539
子分类 5：加料
id = 2790683528350544
category_name = 加料
pid = 2790683528350539
子分类 6：洋酒
id = 2793221553489733
category_name = 洋酒
pid = 2790683528350539
这里是最典型的一棵分类树：业务线“酒水”，细分成饮料、普通酒水、茶水、咖啡、加料、洋酒。
4. 根：果盘（业务线：水果）
根节点：
id = 2790683528350545
category_name = 果盘
business_name = 水果
pid = 0
categoryBoxes 子分类 1 个：
子分类：
id = 2792050275864453
category_name = 果盘
business_name = 水果
pid = 2790683528350545
这里有个有意思的点：
分类名称用的是“果盘”，而业务大类 business_name 是“水果”，说明业务线从“水果”角度管理，这个店真正卖的具体品类是“果盘”。
5. 根：零食
根节点：
id = 2791941988405125
category_name = 零食
business_name = 零食
pid = 0
categoryBoxes 子分类 2 个：
子分类 1：
id = 2791948300259205
category_name = 零食
pid = 2791941988405125
子分类 2：
id = 2793236829620037
category_name = 面
pid = 2791941988405125
这说明“面”类商品也被算在零食这条业务线里（这完全是你们的门店本地习惯）。
6. 根：雪糕
根节点：
id = 2791942087561093
category_name = 雪糕
business_name = 雪糕
pid = 0
categoryBoxes 子分类 1 个：
子分类：
id = 2792035069284229
category_name = 雪糕
pid = 2791942087561093
7. 根：香烟
根节点：
id = 2792062778003333
category_name = 香烟
business_name = 香烟
pid = 0
categoryBoxes 子分类 1 个：
子分类：
id = 2792063209623429
category_name = 香烟
pid = 2792062778003333
8. 根：其他
根节点：
id = 2793217944864581
category_name = 其他
business_name = 其他
pid = 0
categoryBoxes 子分类 1 个：
子分类：
id = 2793218343257925
category_name = 其他2
pid = 2793217944864581
可以理解为“杂项类商品”的一级和二级拆分。
9. 根：小吃
根节点：
id = 2793220945250117
category_name = 小吃
business_name = 小吃
pid = 0
categoryBoxes 子分类 1 个：
子分类：
id = 2793221283104581
category_name = 小吃
pid = 2793220945250117
三、categoryBoxes 元素的字段与含义
无论是在 goodsCategoryList 还是 categoryBoxes 里，每个分类节点的字段集合完全一致：
id：分类节点主键，唯一。
tenant_id：租户 ID，本文件所有节点相同。
category_name：分类名，见上面的各种名称。
alias_name：分类别名，当前全部为空字符串。
pid：父级分类 ID，根节点为 0，子节点为父节点 id。
business_name：业务大类名，用于业务线聚合。
tenant_goods_business_id：业务大类 ID，对应 business_name，根节点与子节点在同一业务线中取值相同。
open_salesman：营业员开关，当前所有值为 2，表示没启用分类级差异。
categoryBoxes：子分类数组，只有根节点非空，子节点都是空数组。
sort：排序，小部分为 1，大部分为 0，目前排序未精细化使用。
is_warehousing：是否走库存，本文件全部为 1。
这一点很重要：categoryBoxes 里的元素不是“别的结构”，就是一套完整分类节点，只是挂在父节点下面而已。
四、从 DWD 建模角度怎么看 categoryBoxes
结合上面的完整展开，可以得出几个明确结论和建议：
真实树关系靠的是 id 和 pid
子节点的 pid 永远等于父节点的 id。
即使没有 categoryBoxes，你也完全可以自下而上拼出整棵树。
categoryBoxes 是前端友好的结构，不是建模必要字段。
当前树只有两层
根节点的 categoryBoxes 非空，子节点的 categoryBoxes 全为空。
对应 DWD 可以直接用一个 category_level 字段区分 1、2 层，再配一个 is_leaf 字段。
JSON 有分页重复
同一套分类树出现在两个 page 中，goodsCategoryList 与 categoryBoxes 内容重复。
ETL 时必须按 id 去重，否则维表会重复插入相同分类。
在 DWD 的 dim_goods_category 里，categoryBoxes 本身不需要落列
只保留每个节点一行：category_id、category_name、parent_category_id、category_level、is_leaf、tenant_goods_business_id、business_name 等即可。
如果你特别想保留源结构，可以另开一个 raw_json 字段存原始节点 JSON，日后排错用，但不建议在分析建模中依赖它。
dim_goods_category_Ex
dim_groupbuy_package
团购套餐定义，来自 group_buy_packages。每行代表一种团购套餐及其使用规则。
dim_groupbuy_package_Ex
事实表（DWD）
以下事实表均以“业务事件”为粒度，不做聚合。字段来源包括原始 JSON(或ODS) 中的明细数组以及对象属性。时间单位均统一为秒，并保留原始字段以备检查。金额按照源系统保持符号规则，不做符号转换。
dwd_settlement_head（结账记录）
来自 settlement_records的内层 settleList 对象，每行代表一次结账。该表在业务上是其他明细事实表的汇总头，用于串联台费、商品、助教、券等明细。
dwd_settlement_head_Ex（结账记录扩展）
dwd_table_fee_log（台费流水）
来自 table_fee_transactions的 siteTableUseDetailsList，忽略siteProfile（已在dim_site实现）。粒度为一次台费使用记录（包括包厢）。该表连结订单结账头、桌台、会员等维度。
dwd_table_fee_log_Ex（台费流水扩展）
dwd_table_fee_adjust（台费折扣/调整）
来自 table_fee_discount_records的table_fee_discount_records.data.taiFeeAdjustInfos. 路径下字段路径。每行代表一次台费打折或减免操作。由于结构相对简单，字段说明如下：
dwd_table_fee_adjust_Ex（台费折扣/调整扩展）
dwd_store_goods_sale（商品销售明细）
来自 store_goods_sales_records的 orderGoodsLedgers。每行代表订单中的一条商品销售明细。字段较多，以下列出关键字段及其作用。
dwd_store_goods_sale_Ex（商品销售明细扩展）
dwd_assistant_service_log（助教服务流水）
来自 assistant_service_records的 assistant_service_records.data.orderAssistantDetails.。每行表示一次助教提供服务的记录，包括服务时长、金额、助教与会员关联等。
dwd_assistant_service_log_Ex（助教服务流水扩展）
dwd_assistant_trash_event（助教废除事件）
来自 assistant_cancellation_records 的 abolitionAssistants。每行代表一次助教服务被废除的事件，无法直接与结算记录或助教流水关联，只能通过门店+台桌+助教+时间窗口软关联。
dwd_assistant_trash_event_Ex（助教废除事件扩展）
dwd_member_balance_change（会员余额变动）
来自 member_balance_changes.json，粒度为一次储值卡账户余额变动。此表是分析会员资金往来的核心事实表。
dwd_member_balance_change_EX（会员余额变动扩展）
dwd_groupbuy_redemption（团购券核销）
来自 group_buy_redemption_records.json 中各条记录。每行代表一次团购券使用/核销事件。
dwd_groupbuy_redemption_Ex（团购券核销扩展）
来自 group_buy_redemption_records.json 中各条记录。每行代表一次团购券使用/核销事件。
dwd_platform_coupon_redemption（第三方平台券核销）
来自 platform_coupon_redemption_records.json。每条记录代表一次第三方团购券的核销，用于追踪渠道引流和兑换。
dwd_platform_coupon_redemption_Ex（第三方平台券核销扩展）
dwd_recharge_order（充值结算）
来自 recharge_settlements.json的settleList.settleList.。每行是一条充值订单，记录充值金额、赠送金额及是否首充。
dwd_recharge_order_Ex（充值结算扩展）
dwd_payment（支付流水）
来自 payment_transactions.json。每行代表一笔支付或收款流水，与结算单、充值单等关联。只有 pay_status=2 的支付成功记录被导出。
dwd_refund（退款流水）
来自 refund_transactions.json。每行代表一笔退款，对应原支付流水。退款金额以负数存储在 pay_amount 字段；字段 refund_amount 全部为 0，实际退款金额需取 pay_amount 的绝对值。
dwd_refund（退款流水）
来自 refund_transactions.json。每行代表一笔退款，对应原支付流水。退款金额以负数存储在 pay_amount 字段；字段 refund_amount 全部为 0，实际退款金额需取 pay_amount 的绝对值。
总结
本说明书列出了经营数据仓库 DWD 层的主要维度表和事实表的字段结构及说明，尽可能在每个字段上标注其来源、含义及业务重要性。对于未在 MD 文档中解释的字段标记为 作用未知，在建模时建议保留字段但谨慎使用；对业务逻辑影响较小的展示类字段标记为 不重要。枚举字段均列出了观测到的取值和推断的含义，便于后续 ETL 做值域映射和数据清洗。随着业务扩展和数据补充，可继续完善枚举信息、用途说明和字段分类。