# P2 文档冲突业务化详情(13 条)

> 生成日期:2026-05-04 / 来源:Wave 0 三个子代理产出 / 目的:把 P2 索引行展开为业务故事卡,供 Neo 拍板
> 配套索引见 `docs/_overview/04-doc-conflicts.md` § 一 P2 表格;P2-13 含 5 个子项,合并到一节呈现

---

## P2-1. tabBar 是 app.json 写死 3 项还是动态过滤

**关联**: 全部小程序页面;`apps/miniprogram/miniprogram/app.json` + `custom-tab-bar/` + `utils/auth-guard.ts`

**业务背景**: 不同角色看到的底栏不一样 — 助教/教练 3 项(任务/看板/我的),员工/管理人员只 2 项(看板/我的)。`app.json` 习惯写死 3 项,但实际渲染走 `custom-tab-bar` 按 `visibleTabs` 动态过滤。

**冲突**:
| 来源 | 说法 |
| --- | --- |
| `app.json` | 写死 3 项("任务/看板/我的") |
| `auth-guard.ts` + storyboard | 按角色动态过滤,首登选第一个可见 tab |

**影响**: 文档一致性(轻)。代码已按动态实现,旧文档若仍写"3 项 tabBar"会误导新开发者。

**选项**:
- **A**: 旧文档同步为"动态过滤" — 优:与代码一致 劣:需扫一遍引用旧表述的文档
- **B**: 在 `app.json` 配置层加注释"实际由 custom-tab-bar 接管" — 优:零成本 劣:不解决根本

**建议判定**: A,旧文档过期一律改文档

*反馈：选项A*

---

## P2-2. 维客线索 tag 字段格式

**关联**: `task-detail` / `customer-detail` 维客线索区;`biz.member_retention_clue.tag`(应用 6/8 输出)

**业务背景**: 维客线索的 tag 作为客户分类标签展示(如"客户基础/消费习惯")。前端 mock 出现 `"客户\n基础"`(含换行符),类型定义却是单字符串,与 SPEC 中 6 大类枚举(客户基础/消费习惯/玩法偏好/促销接受/社交关系/重要反馈)又不一致。

**冲突**:
| 来源 | 说法 |
| --- | --- |
| task-detail 内联 mock | `"客户\n基础"` 含换行 |
| `mock-data.ts` 类型定义 | 单字符串 |
| 契约 TASK-2 / SPEC | 6 大类枚举 key |

**影响**: 联调与展示(中)。如果 AI 真返回 `\n`,WXML 是否解析换行决定渲染样式;枚举/字符串不统一导致前端无法做颜色映射。

**选项**:
- **A**: 后端返回枚举 key + 中文 label 两字段,前端按 key 做色板映射 — 优:可扩展 劣:需改后端响应
- **B**: 后端返回中文长字符串(允许 `\n`),前端 white-space:pre-line 渲染 — 优:简单 劣:无法做样式区分

**建议判定**: A,与应用 6/8 的 JSON 输出对齐(分类 + Emoji + 摘要)

*反馈：选项A*
---

## P2-3. 维客线索 source 字段格式

**关联**: 同 P2-2;`biz.member_retention_clue.source`

**业务背景**: 客户线索来源用于在卡片底部展示"By:小燕"或区分人工/AI。当前前端 mock 用展示文案 `By:小燕`,类型定义用枚举 `manual | ai_consumption | ai_note`,两者语义层不同。

**冲突**:
| 来源 | 说法 |
| --- | --- |
| 前端 mock | `'By:小燕' / 'By:系统'`(显示文案) |
| 类型定义 / SPEC | `manual / ai_consumption / ai_note`(枚举) |

**影响**: 前后端契约(中)。混用会导致后端返回枚举时前端无法直接展示,或反之。

**选项**:
- **A**: 后端返回 `source`(枚举) + `recorded_by_name`(可空),前端拼"By:xxx"或"AI" — 优:语义清晰 劣:前端要改拼接逻辑
- **B**: 后端直接返回成品文案 — 优:前端零改造 劣:失去枚举信息,后续做筛选/统计困难

**建议判定**: A,与 P10 SPEC 的 `member_retention_clue` 字段定义一致

*反馈：选项A*

---

## P2-4. 课程类型 class 前缀(tag- 前缀 vs 业务名)

**关联**: `performance-records` / `coach-service-records` 服务记录卡片

**业务背景**: 同样是课程类型徽章(基础课/包厢课/激励课),两个页面用了不同的 CSS class 命名约定,WXSS 选择器与 COURSE_TAG_MAP 也是双轨。

**冲突**:
| 来源 | 说法 |
| --- | --- |
| performance-records | `tag-basic / tag-vip / tag-tip` |
| coach-service-records | `basic / room / incentive` |

**影响**: 体验(轻)。两个页面同色块来源不同样式表,改色板需要改两处。

**选项**:
- **A**: 统一为 `tag-basic / tag-vip / tag-tip`(显式语义前缀) — 优:与 design-system 一致 劣:改 coach-service-records 的 WXSS
- **B**: 统一为业务名 `basic / room / incentive`(更贴近后端 course_type) — 优:与后端一致 劣:与已有 design-system 命名冲突

**建议判定**: A,与 `design-system/DISPLAY-STANDARDS.md` 标注一致

*反馈：这个你再调研下，从数据库真实数据到后端处理再到前端，我记得是两套课程体系，或者有2级课程体系，要确认好所有信息，再做最终决定。*

---

## P2-5. ChatMessage timestamp vs 契约 created_at

**关联**: `pages/chat/chat`;`biz.ai_messages.created_at`

**业务背景**: AI 对话页消息列表展示发送时间。前端类型用 `timestamp`,后端契约表是 `created_at`。这是命名风格(camel/snake)与含义层级(时间戳/创建时间)的双重不一致。

**冲突**:
| 来源 | 说法 |
| --- | --- |
| 前端 ChatMessage type | `timestamp` |
| 契约 / 数据库 | `created_at` |

**影响**: 联调(中)。后端按 CamelModel 转 camelCase 时会变 `createdAt`,前端访问 `msg.timestamp` 会拿到 undefined。

**选项**:
- **A**: 前端类型改为 `createdAt` — 优:与后端 CamelModel 自动序列化对齐 劣:WXML/TS 多处改名
- **B**: 后端 alias 输出 `timestamp`(对前端) — 优:前端零改造 劣:与全站 camelCase 规范背离

**建议判定**: A,沿用 CamelModel 默认行为

*反馈：选项A*
---

## P2-6. ChatHistoryItem title 是后端返回还是前端截断

**关联**: `pages/chat-history`;`biz.ai_conversations`

**业务背景**: 对话记录列表每条要展示一个"标题"(如"关于客户王昕的咨询")。当前契约 CHAT-1 没有 title 字段,前端 mock 是写死的。可选方案:让 AI 摘要、取首条用户消息截断、用客户名拼接。

**冲突**:
| 来源 | 说法 |
| --- | --- |
| 契约 CHAT-1 | 无 title 字段 |
| 前端展示 | 必须有标题 |

**影响**: 体验(中)。无标题时列表只有"最后消息摘要 + 时间",辨识度低。

**选项**:
- **A**: 后端在 `ai_conversations` 加 `title`(由应用 1 首轮自动摘要生成 ≤16 字) — 优:语义最佳 劣:增加一次 AI 调用成本
- **B**: 后端取首条 user 消息前 20 字截断 — 优:零额外成本 劣:可读性差
- **C**: 前端按客户名/时间拼接(如"王昕的对话 03-10") — 优:零后端改造 劣:无业务语义

**建议判定**: A,与应用 1 SSE 流式输出兼容,首轮结束时落库

*反馈：选项A，是不是要修改百炼APP1的系统Prompt?*
---

## P2-7. board-coach time=last_6m + sort=sv_desc 是否后端拒绝

**关联**: `pages/board-coach`;契约 BOARD-1

**业务背景**: 助教看板支持时间筛选(month/quarter/last_6m)与排序(perf/salary/sv/task)。前端 TIME_OPTIONS 注释"last_6m + sv_desc 不兼容",但未说明是后端会 400 拒绝、还是返回空数据、还是允许但语义无效。

**冲突**:
| 来源 | 说法 |
| --- | --- |
| 前端注释 | 该组合不兼容 |
| 后端契约 | 未明示约束 |

**影响**: 体验(轻)。用户先选 sort=sv_desc 再切 time=last_6m 时不报错但结果可能为空。

**选项**:
- **A**: 后端校验该组合,返回 422 + 错误文案"近 6 月不支持储值排序" — 优:体验明确 劣:前端要捕获 422
- **B**: 前端 disable 该组合(切 last_6m 时把 sv_desc 选项变灰) — 优:UI 提前拦截 劣:前端规则要硬编码
- **C**: 后端允许返回空,前端显示空态 — 优:零约束 劣:用户不知道为何为空

**建议判定**: B,前端拦截最不易引发歧义
*反馈：选项B,但要双向禁止/变灰，即切 last_6m 时把 sv_desc 选项变灰，切 sv_desc 时把 last_6m 选项变灰。再看下还有没有类型的切换限制的问题，一同修复？*

---

## P2-8. dev-tools 角色列表缺 site_admin / tenant_admin

**关联**: `pages/dev-tools`;`auth-guard.ts` ROLE_LIST

**业务背景**: dev-tools 是 manager 调试用的"切角色"页。当前 ROLE_LIST 只列了 `[coach, staff, head_coach, manager]` 4 个,小程序的实际角色枚举还有 `site_admin / tenant_admin`(虽然这两个角色主战场在 tenant-admin 后台,小程序只查阅看板)。

**冲突**:
| 来源 | 说法 |
| --- | --- |
| dev-tools ROLE_LIST | 4 角色 |
| auth-guard 全角色枚举 | 6 角色(含 site_admin / tenant_admin) |

**影响**: 调试覆盖(轻)。开发期无法在小程序模拟门店管理员视角。

**选项**:
- **A**: ROLE_LIST 补齐 6 个 — 优:全角色调试 劣:dev-switch-role 后端要支持这两个 aud 在小程序 token 内
- **B**: 文档明确 dev-tools 只调试 4 个 C 端角色,site_admin/tenant_admin 在 tenant-admin Web 调试 — 优:边界清晰 劣:小程序看板的角色显示无法覆盖测试

**建议判定**: A,与小程序 tabBar 角色映射表(全 6 角色)对齐

*反馈：选项B,因为site_admin/tenant_admin在小程序端没有相应的配置信息可以读取。两套角色相互平行，不能混用。*
---

## P2-9. no-permission 管理员姓名"厉超"硬编码

**关联**: `pages/no-permission`

**业务背景**: 申请被拒/账号禁用页底部显示"如有疑问请联系管理员 厉超"。当前是 WXML 硬编码,`_hardcode-summary.md` 第 6 项决策"保持硬编码(只有一个管理员)"。但多门店上线后每个门店可能有不同的 site_admin。

**冲突**:
| 来源 | 说法 |
| --- | --- |
| `_hardcode-summary.md` | 决策硬编码"厉超" |
| 多门店现实 | 不同店应有不同管理员 |

**影响**: 体验(中)。新门店上线后用户被拒看到的还是"厉超",找不到本店管理员。

**选项**:
- **A**: 后端 `/me` 返回时附带 `siteAdminContact:{ name, phone? }`,无值回落"管理员" — 优:多门店可用 劣:后端要 JOIN auth.tenant_admins
- **B**: 改为不出现具体姓名("请联系门店管理员") — 优:零改造 劣:用户不知道找谁
- **C**: 在 `apply` 表单时记录 `site_id`,被拒时按该 site 查管理员 — 优:精准 劣:实现成本与 A 接近

**建议判定**: A,与 P10 tenant-admin 账号体系联动

*反馈：选项A,但不能直接显示管理员的用户名，应该在tenant-admin中，加入一个管理入口，专门编辑no-permission的显示信息，这个功能你帮我设计下，并进行实现且按照我们的约定方式，进行测试。*

---

## P2-10. customer-records 字段 consumption60D 大写 D Pydantic alias 规范

**关联**: `pages/customer-records`;后端 `/customer/{id}/profile`

**业务背景**: 该字段后端命名 `consumption_60d`(snake),前端类型却是 `consumption60D`(大写 D),代码注释里标"踩坑"。这是 Pydantic alias 转 camelCase 时数字+字母的处理差异(`60d` → `60D` vs `60d` 不同库不同行为)。

**冲突**:
| 来源 | 说法 |
| --- | --- |
| 后端 snake | `consumption_60d` |
| Pydantic CamelModel 输出 | `consumption60D`(踩坑) |
| 业界惯例 | `consumption60d` 全小写 |

**影响**: 全站类型一致性(中)。同类字段(如 `visits_30d`)如果在另一个接口被序列化为 `visits30D` 会产生连锁不一致。

**选项**:
- **A**: 后端字段名改 `consumption60`(去掉单位 d),保留单位在文档说明 — 优:绕开 alias 边界 劣:语义不清
- **B**: 给该类字段显式写 alias `Field(..., alias='consumption60d')` — 优:可控 劣:每个字段都要手写
- **C**: 接受 `60D` 大写,在全站规范里写明"数字+字母时字母大写" — 优:规则清晰 劣:不直观

**建议判定**: B,与 CamelModel 默认行为脱钩,每个 60d/30d/90d 字段显式 alias

*反馈：选项B。*

---

## P2-11. AI 需求 2 表头标 6 个实际 8 个(笔误)

**关联**: `docs/prd/AI需求2.md`(产品脑图 §六 引用)

**业务背景**: AI 需求文档第 26 行表头写"6 个 AI 应用的详细需求",实际表格列出应用 1-8 共 8 个。SPEC 总览 P5 也是 8 个。

**冲突**:
| 来源 | 说法 |
| --- | --- |
| AI需求2.md 表头 | 6 个 |
| AI需求2.md 表格内容 | 8 个(应用 1-8) |
| SPEC 总览 P5 | 8 个 |

**影响**: 文档一致性(轻)。仅文字笔误,不影响实现。

**选项**:
- **A**: 改表头为"8 个" — 优:1 行修复 劣:无
- **B**: 保留并注释"含 7/8 整合应用" — 优:解释来源 劣:啰嗦

**建议判定**: A,直接改表头

*反馈：选项A。*

---

## P2-12. admin-web /admin/tenant-admins 与 tenant-admin 职责边界

**关联**: `apps/admin-web` `/tenant-admins` 路由 vs `apps/tenant-admin` 整个后台

**业务背景**: 两个 Web 应用看似都涉及"租户管理":
- admin-web `/admin/tenant-admins` 操作 `auth.tenant_admins` 表(账号 CRUD)
- apps/tenant-admin 整个应用是这些账号登录后用的工作台

PRD NS4/NS4.1 主要描述 tenant-admin,未明文边界。

**冲突**:
| 来源 | 说法 |
| --- | --- |
| admin-web | 创建/编辑/重置密码租户管理员账号 + 简写 ID 管理 |
| tenant-admin | 用户审核 / Excel 上传 / 维客线索管理 |

**影响**: 文档边界(中)。两个团队/角色读不同 PRD 容易认为对方负责创建账号。

**选项**:
- **A**: 文档加边界声明 — admin-web 操作"账号本身"(创建/启停/重置),tenant-admin 操作"门店运营"(用户/Excel/线索) — 优:零代码改 劣:仅文档
- **B**: tenant-admin 内部加自助"重置密码"入口 — 优:用户体验好 劣:超出 NS4 范围
- **C**: 把 `/admin/tenant-admins` 从 admin-web 移到独立超管页 — 优:权责清 劣:大重构

**建议判定**: A,在 NS4 加一节"职责边界",admin-web 侧也加 README 说明

*反馈：同意你的建议。*

---

## P2-13. AIPrewarm + 事件枚举 + 侧边栏 ?tab + Login 路径 + ai-group 展开(5 子项)

**关联**: `apps/admin-web` 多个页面;NS3 PRD

**业务背景**: 这 5 个小冲突共同指向"NS3 PRD 与 admin-web 现状对照不足"。统一处理。

### P2-13.1 AIPrewarm 72 组合二分逻辑未在 NS3 写明

**冲突**: 注释"72 组合"实际是 `area=all 走 app2_finance 8 组合 + 8 区域走 app2a_finance_area 64 组合`,NS3 / API-REFERENCE 是否同步该二分?

**选项**:
- **A**: NS3 加一节"72 组合二分逻辑" — 优:文档清晰 劣:维护新章节
- **B**: 仅在 AIPrewarm.tsx 顶部注释扩写 — 优:就近 劣:非前端开发者读不到

**建议**: A
*反馈：A。*

### P2-13.2 event_type 枚举完整表缺失

**冲突**: AIOperations.EVENT_TYPE_OPTIONS 列 4 项(consumption / dws_completed / note_created / task_assigned),AIPrewarm 用 dws_completed,AITriggerJobs 不限制。NS3 是否有完整枚举表?

**选项**:
- **A**: NS3 加 `event_type` 枚举完整表(含触发条件 + 后置应用链) — 优:全局一致 劣:工作量
- **B**: 在 `biz/cfg_event_types.py` 维护源,文档自动生成 — 优:不漂移 劣:需要工具

**建议**: A 起步,长期 B
*反馈：B。*

### P2-13.3 侧边栏 ?tab= 双入口 UX

**冲突**: "AI 管理 → 触发器设置"与"系统设置 → 触发器配置"都跳到 `/triggers?tab=ai|biz`,两个菜单 entry 共用一个页面。

**选项**:
- **A**: 接受现状(两入口不同语境) — 优:零改造 劣:用户疑惑"为何两处都有"
- **B**: 合并为一个入口,在 `/triggers` 顶部加面包屑显示来源 — 优:UX 清晰 劣:菜单收窄

**建议**: A,在 PRD 解释保留双入口的理由(操作语境不同)
*反馈：A。*


### P2-13.4 Login 路径前缀

**冲突**: `useAuthStore.login` 调 `/api/auth/login`,401 拦截器引用 `/auth/refresh`。是否含 `/admin` 前缀?本调研未读 `apps/backend/app/routers/auth.py`。

**选项**:
- **A**: 校对 auth.py 实际路径并同步 NS1 文档 — 优:无猜测 劣:需要读代码
- **B**: 在 admin-web README 写明前缀来源 — 优:就近 劣:其他端读不到

**建议**: A,补 NS1 后端 API 章节
*反馈：同意你的建议。*

### P2-13.5 ai-group 默认展开

**冲突**: 从 ai 子菜单跳到 `/triggers?tab=ai` 时,侧边栏 `ai-group` 不会自动展开(`getDefaultOpenKeys` 注释"无法判断 tab")。

**选项**:
- **A**: `getSelectedKeys` 改造,根据 `pathname + search` 同时展开多个 group — 优:UX 一致 劣:需要测多种入口组合
- **B**: 接受现状 — 优:零改造 劣:用户跳转后菜单状态丢失

**建议**: A
*反馈：A。*

**P2-13 整体建议判定**: 全部走 A,集中在 NS3 同步 + admin-web 侧边栏小重构

---

> 完成日期:2026-05-04 / 用途:为 Neo 提供 P2 13 条的紧凑业务化判定材料,确认后批量进入 Wave 5 文档收尾或对应模块小工单
