Преглед изворни кода

docs: 添加 superpowers 设计文档与确定性修复计划

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
asus пре 3 месеци
родитељ
комит
62cf96808f

+ 843 - 0
docs/superpowers/plans/2026-07-10-determinism-gap-fix-plan.md

@@ -0,0 +1,843 @@
+# 浠艾福平台 · 确定性差距修复实施计划
+
+**版本:** v1.0
+**日期:** 2026-07-10
+**前置审计:** docs/superpowers/plans/2026-07-10-determinism-gap-audit.md(基于 5 路并行 explore 代码审计)
+**总体工作量估算:** 38-52 人天(P0: 5-7d / P1: 15-20d / P2: 12-16d / P3: 6-9d)
+
+---
+
+## 实施总览
+
+本计划以"确定性"为主线,把审计报告中的 18 个修复任务按优先级分层,强调**安全先于叙事、闭环先于优化、闭环先于优化、闭环先于优化**。
+
+| 阶段 | 主题 | 任务数 | 估算人天 | 价值锚点 |
+|:----:|------|:------:|:--------:|----------|
+| **P0** | 隐私 + 营销根基 | 3 | 5-7 | 堵最大隐私漏洞;打开"成长可视化"叙事 |
+| **P1** | 信任 + 数据闭环 | 6 | 15-20 | 邀请/视角/订单/Dify 闭环;地址全国可用 |
+| **P2** | 数据质量 + 可维护性 | 5 | 12-16 | 迁移可追溯;schema 自洽;报告机读;推荐可解释 |
+| **P3** | 长期治理 | 4 | 6-9 | 死字段清理;PDF 多供应商;sfms 只读;前端去 mock |
+
+**关键里程碑:**
+- **M1(第 1 周)**:P0 完成。`familyId` 越权测试零案例;五维分数可图表化回放
+- **M2(第 3-4 周)**:P1 完成。地址全国可用;家长可看到"测评结果可看"通知;Dify 宕机对话不丢
+- **M3(第 6-7 周)**:P2 完成。每次重启跳过所有已跑迁移;schema.sql 重建数据库成功
+- **M4(第 9-10 周)**:P3 完成。死字段全部接入或删除;前端图表不出现假数据
+
+---
+
+## P0 关键安全与根基(立即修)
+
+> **共用约定**:所有新建表通过 `DatabaseInitializer.runMigrations()` 新增 `CREATE TABLE IF NOT EXISTS`;schema.sql 同步追加;新增 controller 用 `@PostMapping`;DI 用 `@Resource` 字段名匹配 Bean Name。
+
+---
+
+### G7. familyId 入参校验(最大隐私漏洞)
+
+**优先级:** P0 🔴
+**估算:** 1.5 人天
+**前置依赖:** 无
+**影响文件:** `JwtInterceptor.java`、`WebConfig.java`、7 个控制器
+
+#### 问题定位
+
+7 个控制器从 JSON body/参数中拿 `familyId` 后直接使用,不校验该家庭是否属于当前 JWT 用户:
+- `DanAssessmentController` 第 65/79/89/208 行
+- `InteractionLogController` 第 62 行
+- `FoodRecommendController` 第 50 行
+- `WishController` 第 229 行
+- `TeacherMessageController` 第 43 行
+- `GrowthRecordController` 第 90 行
+- `TraditionalMirrorController` 第 88 行
+
+#### 修复步骤
+
+1. [ ] **新增 `FamilyAccessInterceptor`**(`config/FamilyAccessInterceptor.java`)
+   - 拦截 `/api/**`(除 JwtInterceptor 已放行的 8 个公开路径)
+   - 在 preHandle 阶段:解析 body 中的 `familyId` 字段(用 `ContentCachingRequestWrapper` 缓存 body 以便可重复读)
+   - 从 `@RequestAttribute("userId")` 查 `User.familyId` 与 `User.teacherFamilyIds`
+   - 校验:body 中 `familyId` 必须等于 `user.familyId` **或** 在 `teacherFamilyIds` 列表中
+   - 不通过:`response.setStatus(403)`;返回 JSON `{"code":403,"message":"无权访问该家庭数据"}`
+   - 通过:放行
+
+2. [ ] **`WebConfig.addInterceptors`** 注册 `FamilyAccessInterceptor`,order 在 `JwtInterceptor` 之后
+
+3. [ ] **`User` 实体新增 helper** `getEffectiveFamilyIds()`:返回包含 `familyId` + 解析后的 `teacherFamilyIds` 的 `Set<Long>`
+
+4. [ ] **识别例外路径**:admin 调用路径(如 `/api/admin/*`)跳过此拦截器;teacher 绑定家庭的 `guideBindFamily` 由 G10 任务单独加审批,暂放行
+
+5. [ ] **测试**:新增 `FamilyAccessInterceptorTest`
+   - parent A 携 familyId=B → 403
+   - parent A 携 familyId=A → 200
+   - teacher T 携 familyId=已绑定家庭 → 200
+   - 缺 familyId 字段 → 放行(有些端点不强制)
+   - admin 路径 → 跳过
+
+6. [ ] **不要**修改原有 7 个控制器内部逻辑——拦截器统一处理
+
+#### 验证
+
+- [ ] `mvn clean compile` 通过
+- [ ] `mvn test -Dtest=FamilyAccessInterceptorTest` 通过
+- [ ] 临时启动后端,用 parent A 的 token + body `{familyId: B的家庭ID}` 调 `/api/dan-assessment/materials` 返回 403
+- [ ] 用 parent A + 自家 familyId 返回 200
+
+#### 完成条件
+
+- 所有 `/api/**` 路径(除 admin 子树)自动校验 familyId 归属
+- 越权测试用例全 PASS
+- 无控制器内部 familyId 重复校验代码
+
+---
+
+### G1. 五维能量分数持久化(解锁全部可视化叙事)
+
+**优先级:** P0 🔴
+**估算:** 2.5 人天
+**前置依赖:** 无
+**影响文件:** 新增 entity/mapper/service、`EnergyService.java`、`DatabaseInitializer.java`、`schema.sql`
+
+#### 问题定位
+
+`EnergyService.calculateFamilyEnergy()` 每次请求重算身/心/智/行/富 0-100 分;无任何表保存"X 孩子在 Y 日期的分数",导致:
+- 无法做趋势图、"上次 vs 本次"对比
+- 算法改一处,所有"历史成绩"瞬时漂移
+- AGENTS.md 宣传文案"自驱力 4.07→5.30"在系统中**根本查不到**
+
+#### 修复步骤
+
+1. [ ] **新建 `five_dimension_scores` 表**
+   ```sql
+   CREATE TABLE IF NOT EXISTS five_dimension_scores (
+     id BIGINT AUTO_INCREMENT PRIMARY KEY,
+     member_id BIGINT NOT NULL COMMENT '家庭成员ID(family_members.id)',
+     family_id BIGINT NOT NULL,
+     dimension_code VARCHAR(32) NOT NULL COMMENT 'body/mind/wisdom/action/wealth',
+     score INT NOT NULL COMMENT '0-100 分',
+     assessed_at DATETIME NOT NULL COMMENT '评估时间',
+     source_type VARCHAR(32) NOT NULL COMMENT 'assessment/task_complete/manual/periodic',
+     source_id BIGINT COMMENT '源对象ID(如dan_assessment_results.id)',
+     detail_json TEXT COMMENT '细分项快照(cognitive_scores/emi_scores/...)',
+     created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+     INDEX idx_member_dim_time (member_id, dimension_code, assessed_at),
+     INDEX idx_family_dim_time (family_id, dimension_code, assessed_at)
+   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='五维能量历史快照';
+   ```
+   - 通过 `DatabaseInitializer.runMigrations()` 添加
+   - 同步追加到 `schema.sql`
+
+2. [ ] **新建 `FiveDimensionScore` entity**(标准 MyBatis-Plus)
+   - `@TableName("five_dimension_scores")`, `@TableId(type=IdType.AUTO)`
+   - 所有字段对应表结构
+
+3. [ ] **新建 `FiveDimensionScoreMapper`**
+
+4. [ ] **新建 `FiveDimensionScoreService`**
+   - `recordSnapshot(memberId, familyId, dimension, score, sourceType, sourceId, detailJson)`:插入
+   - `getHistoryByMember(memberId, dimension, limit=20)`:返回时间序列
+   - `getLatestByFamilyMap(familyId)`:返回 Map<dimensionCode, score>
+   - `compareFirstVsLatest(memberId)`:返回 delta(先实现接口,G18 用)
+
+5. [ ] **`EnergyService.calculateFamilyEnergy()` 末尾插入持久化钩子**
+   - 在每次计算完每个孩子每个维度分数后,调用 `fiveDimensionScoreService.recordSnapshot(...)`
+   - **去重策略**:若同一 `memberId+dimension+assessed_at(精度=天)+source_type+source_id` 已存在,跳过插入
+   - **不要**改 `EnergyService` 的计算逻辑——只在尾部加 record
+
+6. [ ] **`EnergyController.getOverview`** 优先读 `fiveDimensionScores` 最新记录;fallback 到现有重算逻辑(保证向下兼容)
+
+7. [ ] **新增端点**:
+   - `POST /api/energy/score-history`:入参 `memberId, dimension, limit`,返回时间序列
+   - `POST /api/energy/family-score-history`:入参 `familyId, dimension, fromDate, toDate`
+
+#### 验证
+
+- [ ] `mvn clean compile` 通过
+- [ ] 调 `/api/energy/overview` 两次,DB 中应出现两条快照(去重生效时只有 1 条)
+- [ ] 调 `/api/energy/score-history?memberId=X&dimension=wisdom&limit=5` 返回时间序列
+- [ ] 修改 `EnergyService.calcChildWisdom()` 系数(如临时改为 0.5),重启,**历史分数不变**,新分数按新系数
+
+#### 完成条件
+
+- 五维分数写入 DB
+- 历史可查询、趋势可绘制(前端可用)
+- `EnergyService` 算法改动不再影响历史数据
+
+---
+
+### G18. 首次 vs 最新测评对比端点
+
+**优先级:** P0 🔴
+**估算:** 1.5 人天
+**前置依赖:** G1(推荐先完成,否则对比来源是重算数据)
+**影响文件:** `GrowthRecordController`/`AssessmentService`,或新建 `AssessmentComparisonController`
+
+#### 问题定位
+
+AGENTS.md 宣传文案"自驱力 4.07→5.30"在系统中**无法查到**——无 `compareAssessments` 端点,没有方法取首个和最近一次测评计算 delta。`cognitive-report.vue` 只显示单次报告。
+
+#### 修复步骤
+
+1. [ ] **新建 `AssessmentComparisonService`**
+   - `compareByChild(childId)`:取该孩子 `dan_assessment_results` 中最早 + 最近两条,计算维度 delta
+   - `compareByChildAndDimension(childId, dimensionCode)`:聚焦单维度
+   - `getProgressNarrative(childId)`:返回结构化 delta + 文本叙事("智维度提升 +12 分,主要由 logicScore 拉动 +18,专注力下降 -5,建议...")
+
+2. [ ] **数据源策略**:
+   - 优先从 `five_dimension_scores`(G1 新增)取历史值
+   - fallback 到 `dan_assessment_results` 直接 diff
+
+3. [ ] **新建端点**:
+   - `POST /api/assessment/compare/by-child` 入参 `childId`
+     返回:
+     ```json
+     {
+       "firstAssessment": { "id":42, "date":"2025-08-10", "scores":{...} },
+       "latestAssessment": { "id":88, "date":"2026-07-01", "scores":{...} },
+       "dimensionDeltas": [
+         {"dimension":"wisdom","delta":12.3,"direction":"up","drivers":["logicScore +18","memoryScore +6"]},
+         ...
+       ],
+       "overallDelta": 7.4,
+       "narrative": "智维度提升 12.3 分,主要由 logicScore..."
+     }
+     ```
+   - `POST /api/assessment/compare/by-family` 入参 `familyId`,返回家庭所有孩子对比数组
+
+4. [ ] **前端 `pages/wisdom-detail/` 新增 `assessment-comparison.vue`**:渲染对比卡片(先文案样式即可,不强求图表)
+
+#### 验证
+
+- [ ] 后端单元测试 `AssessmentComparisonServiceTest`:mock 两次测评,断言 delta 计算
+- [ ] `/api/assessment/compare/by-child?childId=X` 返回结构化 delta
+- [ ] 前端能看到"首次→最新"对比卡
+
+#### 完成条件
+
+- 平台本身能产出 AGENTS.md 营销文案所需数据
+- 不依赖运营人工抠数字
+
+---
+
+## P1 信任与数据闭环(本季度修)
+
+---
+
+### G8. 视角切换统一为一套机制 + 密码校验
+
+**优先级:** P1
+**估算:** 2.5 人天
+**前置依赖:** 无
+**影响文件:** `FamilyUserController`、`UserService`、`FamilyMembersController`、前端 `store/index.js`、`pages/profile/*`
+
+#### 修复步骤
+
+1. [ ] **保留机制 3**:`/api/family/member/switch`(返回 SwitchMemberVO,不动 DB)——这是正确模型
+2. [ ] **废弃机制 1、2**:
+   - `FamilyUserController.switchMode` → 改为 404 或重定向到 `/member/switch`
+   - `FamilyUserController.switchToChild` → 同上
+   - `UserService.switchToChild`、`switchMode` 加 `@Deprecated`,保留代码以兼容老 JWT 但记 log warning
+3. [ ] **`UserService.switchBackToParent()` 加入密码校验**:
+   - 接收 `password` 参数,调用 `passwordEncoder.matches(password, user.password)`
+   - 校验失败返回 `Result.error("密码错误")`
+   - 不允许 null/空密码通过
+4. [ ] **新增端点** `POST /api/family/user/switch-back-verify`:前端切回家长视角前调用,强制输入密码
+5. [ ] **前端 `store/index.js` 改造**:
+   - 移除 `isSwitchedChild`、`isSwitchedMember` 双 bool,合并为 `currentView: 'self'|'member'`
+   - `switchBackFromMember` 调用 `/switch-back-verify`,失败显示密码弹窗
+   - localStorage 中 `role` 单点真源,仍写但加签名(HMAC with server-issued nonce)防止篡改(可选,先做密码校验)
+6. [ ] **前端文案**:家长切回视角时弹"请输入密码以确认身份"
+
+#### 验证
+
+- [ ] 切到孩子视角后,清空 localStorage,强制刷新,调用 `/switch-back-verify` 必须密码通过才能切回
+- [ ] `mvn test -Dtest=UserServiceTest#switchBackWithWrongPassword` 失败
+- [ ] 三个端点 `/switch-mode`、`/switch-to-child` 返回 410 Gone 或 308
+
+#### 完成条件
+
+- 全平台只有一套视角切换机制
+- 切回家长必须密码
+- AGENTS.md 中"切换过来的孩子端退出需要密码"承诺兑现
+
+---
+
+### G9. 邀请码加过期+使用次数+吊销
+
+**优先级:** P1
+**估算:** 1.5 人天
+**前置依赖:** 无
+**影响文件:** `FamilyController`、`FamilyInviteController`、`FamilyInvitationService`、`UserAddress` 等
+
+#### 修复步骤
+
+1. [ ] **统一两个邀请流为一套**:废弃 `FamilyController /invite-code` 的简易码,全部走 `FamilyInviteController /generate` 生成 token-based invitation
+2. [ ] **`FamilyInvitation` 增强**:
+   - 添加 `maxUseCount INT DEFAULT 1`
+   - 添加 `usedCount INT DEFAULT 0`
+   - 添加 `inviterUserId BIGINT NOT NULL`
+   - 已有字段 `expiresAt`(7 天)保持
+3. [ ] **`validateInvitation()` 强化**:
+   - 检查 `usedCount < maxUseCount`
+   - 检查 `status='active'` 且未过期
+   - 校验失败返回明确原因
+4. [ ] **`acceptInvitation()`** 在事务中 `UPDATE ... SET usedCount = usedCount + 1`,并带 `WHERE usedCount < maxUseCount` 防并发
+5. [ ] **新增端点** `POST /api/family/invite/revoke`:入参 `invitationId`,仅 inviter 或 family admin 可调用,将 status 设为 `revoked`
+6. [ ] **`FamilyController.guideBindFamily`** 加 `familyId` 视角下AMILY.getId() ≠ 邀请绑定的(endpoint 自然受 G7 拦截器保护,可保留)
+7. [ ] **rate-limit**:每家庭每天最多生成 10 个邀请码(用 `Bucket4j` 或简单 Redis 计数)
+
+#### 验证
+
+- [ ] 同一邀请码使用次数超 maxUseCount 后 `accept` 返回 410
+- [ ] revoke 后立即失效
+- [ ] 老简易 invite-code 端点返回 410
+
+#### 完成条件
+
+- 邀请码泄露可吊销
+- 暴力枚举有 rate limit 防护
+
+---
+
+### G10. 规划师绑定家庭加审批
+
+**优先级:** P1
+**估算:** 2 人天
+**前置依赖:** G9(共用邀请码机制)
+**影响文件:** `FamilyController.guideBindFamily`、`TeacherFamilyBindingRequest`(新 entity)、`FamilyController`
+
+#### 修复步骤
+
+1. [ ] **新建 `teacher_family_binding_requests` 表**
+   ```sql
+   CREATE TABLE IF NOT EXISTS teacher_family_binding_requests (
+     id BIGINT AUTO_INCREMENT PRIMARY KEY,
+     teacher_id BIGINT NOT NULL,
+     family_id BIGINT NOT NULL,
+     invitation_id BIGINT NOT NULL,
+     status VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT 'pending/approved/rejected/cancelled',
+     requested_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+     decided_by BIGINT COMMENT '审批家长 user_id',
+     decided_at DATETIME,
+     reject_reason VARCHAR(200),
+     UNIQUE KEY uk_teacher_family (teacher_id, family_id) COMMENT '防重复申请',
+     INDEX idx_family_status (family_id, status)
+   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+   ```
+2. [ ] **`FamilyController.guideBindFamily()` 改造**:从直接 ADD `teacherFamilyIds` → 插入 binding_request,status=pending
+3. [ ] **新增端点**:
+   - `POST /api/family/teacher-bindings/pending`:家长查看自家待审批列表
+   - `POST /api/family/teacher-bindings/approve`:入参 `requestId`,调家长权限校验,status=approved,写入 `User.teacherFamilyIds`
+   - `POST /api/family/teacher-bindings/reject`:入参 `requestId, reason`
+4. [ ] **消息通知**:审批状态变化通过现有 `MessageService` 推送小程序模板消息给规划师
+5. [ ] **`MessageController` 增加类型**:`teacher_binding_approved` / `teacher_binding_rejected`
+
+#### 验证
+
+- [ ] 规划师调用 guideBindFamily 后,家长收到待审批消息
+- [ ] 家长 reject 前规划师不能在 `teacherFamilyIds` 看到 familyId
+- [ ] 同一 (teacher, family) 重复申请被 UK 拦截
+
+#### 完成条件
+
+- 陌生规划师凭邀请码无法直接看到家庭数据
+
+---
+
+### G2. 测评订单 → 结果 linkage + "results_ready" 状态
+
+**优先级:** P1
+**估算:** 2 人天
+**前置依赖:** 无
+**影响文件:** `AssessmentOrder`、`AssessmentOrderService`、`GuideRecordController`、`AssessmentService`
+
+#### 修复步骤
+
+1. [ ] **`assessment_orders` 加字段** `result_id BIGINT` + `results_ready_at DATETIME`(通过迁移)
+2. [ ] **`AssessmentOrder.status` 枚举扩展**:`pending|paid|results_ready|completed|cancelled|refunded`
+3. [ ] **`GuideRecordController.record()` 写结果后联动更新订单**:
+   - 用 `appointmentId` 反查 order,写入 `orders.result_id = danAssessmentResult.id`、`status='results_ready'`、`results_ready_at=now()`
+   - 若找不到对应 order(直接录入场景),跳过
+4. [ ] **`AssessmentOrderController` 新增端点** `POST /api/assessment/order/{id}/ack-results`:家长确认收到结果,订单 status → `completed`
+5. [ ] **家长端 polling 或消息推送**:订单变 `results_ready` 触发小程序模板消息
+   - 复用 `MessageService.sendTemplateMessage` 已有路径
+   - 消息类型 `assessment_results_ready`
+6. [ ] **`AssessmentOrderController.detail` 返回** `resultId`、`resultsReadyAt`、`resultSnapshot`(精简版 DanAssessmentResult)
+
+#### 验证
+
+- [ ] 规划师录入结果 → 订单 status=results_ready、result_id 非空
+- [ ] 家长端收到模板消息
+- [ ] `POST /order/{id}/ack-results` 后变 completed
+
+#### 完成条件
+
+- 付费→结果链闭合
+- 无"我付费了为啥看不到结果"客服投诉
+
+---
+
+### G13. 地址数据库全量导入 + 废弃静态列表
+
+**优先级:** P1
+**估算:** 3-4 人天
+**前置依赖:** 无
+**影响文件:** `DatabaseInitializer.java`(seed)、`RegionController.java`、`Street.java`、新建 `china_regions.csv`/SQL
+
+#### 修复步骤
+
+1. [ ] **获取权威数据源**:民政部《中华人民共和国县以上行政区划代码》最新版,或开源 `modood/Administrative-divisions-of-China` GitHub 仓库(CSV/JSON 格式,已包含省/市/区/街道 4 级)
+2. [ ] **生成 SQL 种子文件** `db/seed/china_regions.sql`:
+   - 用脚本把 CSV 转成 `INSERT INTO streets (...) VALUES ..., (...);` 批量语句
+   - 预计 ~3000 省/市/区/街道行(不动 countries)
+3. [ ] **`DatabaseInitializer` 添加迁移**:检测 streets 表行数 < 1000 时执行种子导入
+   - 跑导入前先 `DELETE FROM streets WHERE province_code != '44'` 清掉旧的广东测试数据(或保留并 IDUPSERT)
+   - 用 `INSERT IGNORE` 或 `ON DUPLICATE KEY UPDATE` 保证幂等
+4. [ ] **`RegionController` 改造**:
+   - 删除 `ALL_REGIONS` 静态硬编码列表
+   - 所有端点改为查询 `StreetService`
+   - `/api/region/streets` 真正返回街道数据
+5. [ ] **`UserAddress` 表加 `street_id BIGINT` 外键**(保留旧 `street` 字符串字段以兼容历史数据),新建地址时填 street_id
+6. [ ] **`user_address` 和 `danshop_addresses` 写入逻辑**:地址选择器只允许从 streets 表选,强制 street_id 非空
+7. [ ] **前端 `AddressPicker` 组件**:四联动从 `/api/region/provinces` → cities → districts → streets 拉 DB 数据
+8. [ ] **区域回退匹配保留**:`StreetService.matchByStreetWithFallback()` 仍然有效,但现在数据齐了,回退少触发
+
+#### 验证
+
+- [ ] `SELECT COUNT(*) FROM streets` ≈ 3000+
+- [ ] 选北京/上海/西藏任意地址都能选到街道级
+- [ ] `RegionController` 全部端点单测 PASS
+- [ ] `AddressPicker` 组件在家长地址页能选北京→朝阳→XX街道
+
+#### 完成条件
+
+- 95% 中国地区可填到街道
+- `RegionController` 中无任何硬编码
+
+---
+
+### G14. Dify 会话本地镜像表
+
+**优先级:** P1
+**估算:** 2.5 人天
+**前置依赖:** 无
+**影响文件:** 新建 `chat_conversations`、`chat_messages` 表 + entity + service;`AIService.sendMessage` 加镜像写入;端点 fallback 读本地
+
+#### 修复步骤
+
+1. [ ] **新建 `chat_conversations` 表**
+   ```sql
+   CREATE TABLE IF NOT EXISTS chat_conversations (
+     id BIGINT AUTO_INCREMENT PRIMARY KEY,
+     user_id BIGINT NOT NULL,
+     dify_conversation_id VARCHAR(64) NOT NULL,
+     assistant_type VARCHAR(32) NOT NULL COMMENT 'family/nutrition/tongue',
+     title VARCHAR(200),
+     last_message_at DATETIME,
+     created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+     UNIQUE KEY uk_dify_conv (dify_conversation_id),
+     INDEX idx_user (user_id)
+   ) ENGINE=InnoDB;
+   ```
+2. [ ] **新建 `chat_messages` 表**
+   ```sql
+   CREATE TABLE IF NOT EXISTS chat_messages (
+     id BIGINT AUTO_INCREMENT PRIMARY KEY,
+     conversation_id BIGINT NOT NULL,
+     dify_message_id VARCHAR(64),
+     role VARCHAR(20) NOT NULL COMMENT 'user/assistant',
+     content TEXT NOT NULL,
+     inputs_json TEXT,
+     metadata TEXT COMMENT '推荐标签解析结果等',
+     created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+     INDEX idx_conv (conversation_id)
+   ) ENGINE=InnoDB;
+   ```
+3. [ ] **新建 `ChatConversation` / `ChatMessage` entity、Mapper、Service**
+4. [ ] **`AIService.sendMessage` 改造**:
+   - 收到 Dify 响应后异步落库(`@Async` 或直接同步,量大时再优化)
+   - 给响应中 `conversationId` 自动 upsert `chat_conversations`(找不到插一条)
+   - 给 user message + assistant message 各插一条 `chat_messages`
+5. [ ] **`AIChatController.getConversations` / `getMessages` 加 fallback**:
+   - 优先调 Dify,状态码 != 200 时读本地 `chat_messages`
+   - 完全不调 Dify 时返回本地数据( потенц future 应用:纯本地查询模式)
+6. [ ] **新增端点** `POST /api/ai/chat/search-history`:用户全文搜索自己历史对话(搜 content LIKE)
+
+#### 验证
+
+- [ ] 调 `sendMessage` 后 `chat_messages` 表 2 条新记录
+- [ ] 关闭 Dify 访问(DNS 指向 127.0.0.1),调 `getMessages` 仍能返回历史(来自本地)
+- [ ] `search-history` 能从消息中搜到关键词
+
+#### 完成条件
+
+- Dify 短时不可用不影响用户查历史
+- Dify 永久切换 API key 时本地仍有完整对话(至少 LLM 返回过的)
+
+---
+
+## P2 数据质量与可维护性(下季度修)
+
+### G23. 迁移版本表 schema_versions
+
+**优先级:** P2
+**估算:** 3 人天
+**前置依赖:** 无
+**影响文件:** `DatabaseInitializer.java`、新建 `SchemaVersion` entity
+
+#### 修复步骤
+
+1. [ ] **新建 `schema_versions` 表**
+   ```sql
+   CREATE TABLE IF NOT EXISTS schema_versions (
+     id BIGINT AUTO_INCREMENT PRIMARY KEY,
+     version_code VARCHAR(64) NOT NULL UNIQUE COMMENT '如 migration_2026_07_10_001',
+     description VARCHAR(500),
+     checksum VARCHAR(64) COMMENT 'SHA256 of SQL content',
+     applied_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+     applied_by VARCHAR(100)
+   ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+   ```
+2. [ ] **重构 `DatabaseInitializer.runMigrations()`**:
+   - 把现有 200+ ad-hoc migration 归类到 batch(可保留 try-catch 包装每个,但每个 batch 完成后插一行 `schema_versions`)
+   - 提供 helper `migrationApplied(versionCode)` 查表,已跑则跳过
+   - 启动时第一阶段:创建 `schema_versions` 表
+   - 第二阶段:遍历 batch list,对每个未应用的 batch 执行 SQL then 插入记录
+3. [ ] **不立即重构所有现存迁移**(206 个分散的 try-catch),先冻结现状并按"重要 batch"切割为 5-10 个 version_code(如 `base_schema_v1`, `growth_record_rebuild_v1`, `address_seed_v1`)
+4. [ ] **新加迁移强制使用新机制**:未来所有 DDL 必须用 `runMigration("version_X", "描述", () -> { ... })` helper
+5. [ ] **`/api/admin/schema-versions`** 端点:admin 查看已跑迁移(只读)
+
+#### 完成条件
+
+- 启动跳过所有已跑 batch
+- 失败有日志可追溯(不只是吞掉)
+- 历史迁移保留兼容,新迁移走新机制
+
+---
+
+### G25. schema.sql 对账补齐
+
+**优先级:** P2
+**估算:** 2 人天
+**前置依赖:** G23(共享版本机制后对账更准)
+**影响文件:** `schema.sql`、新建脚本 `tools/validate-schema-sync.ps1`
+
+#### 修复步骤
+
+1. [ ] **跑脚本枚举 DatabaseInitializer + entity @TableName**:
+   ```ps
+   # 列出所有 DatabaseInitializer 中 CREATE TABLE 的表名
+   # 列出所有 @TableName 注解的 entity 表名
+   # 比对 schema.sql 中 CREATE TABLE 表名
+   ```
+2. [ ] **补齐 schema.sql 缺失的 ~40 张表**:
+   - danshop_* 10 张
+   - emotion_checkin、bazi_reading_template、numsoul_config、nutrition_indicator_mapping、nutrition_deficiency_record
+   - food_recommend_idx、family_invitations、member_discount_configs、article_reading_records、article_quiz_records
+   - health_dimension_scores、health_data_sources、health_norm_reference
+   - user_nutrition_profile、user_food_preference
+   - G1 新增的 five_dimension_scores、G14 新增的 chat_conversations/chat_messages、G9/G10 增强表
+3. [ ] **补齐已有表缺失的列**:扫描 entity 字段 vs schema.sql 列名,diff 出缺失字段补到 schema.sql
+4. [ ] **新增 CI 脚本** `tools/validate-schema-sync.ps1`:每次提交时跑对账,diff 非空时 fail build
+
+#### 完成条件
+
+- 从 schema.sql 重建数据库,应用启动时无"列不存在/表不存在"错误
+- CI 对账脚本 PASS
+
+---
+
+### G3. 测评报告核心结论结构化
+
+**优先级:** P2
+**估算:** 3 人天
+**前置依赖:** 无
+**影响文件:** `DanAssessmentResult`、`GuideRecordController`、`AssessmentService`、web 端规划师录入表单
+
+#### 修复步骤
+
+1. [ ] **`DanAssessmentResult` 新增字段 `structured_analysis JSON`**(保留 `analysis_report TEXT` 兼容历史)
+2. [ ] **结构化 schema**:
+   ```json
+   {
+     "summary": "一句话总评",
+     "dimensions": [
+       {"code":"wisdom","score":82,"level":"good","indicator":"logicScore","comment":"逻辑推理突出"},
+       ...5个维度...
+     ],
+     "highlight_strengths": ["专注力","记忆容量"],
+     "concern_areas": ["情绪管理"],
+     "suggestions": [
+       {"dimension":"mind","action":"每日 15 分钟正念练习","target":"3 周后 EMI 自我觉察 ≥75"},
+       ...
+     ]
+   }
+   ```
+3. [ ] **`GuideRecordController.create()`** 接收 structured_analysis JSON,验证字段必填
+4. [ ] **新增端点** `POST /api/dan-assessment/results/{id}/structured`:返回结构化数据(家长端用)
+5. [ ] **`ReportParseService` 改造**:从 AI prompt 解析时产出 structured_analysis 一起入库
+6. [ ] **家长端 `cognitive-report.vue`** 渲染结构化维度块、建议列表
+7. [ ] **web 端规划师录入表单**:从单 big textarea 改为分维度表单(强制总结 + 5 维度评分 + 至少 2 个 strength + 1 个 concern)
+
+#### 完成条件
+
+- 测评报告机读可解析
+- 家长端统一卡片式展示,不再看规划师散文
+
+---
+
+### G26. test-mode 启动断言
+
+**优先级:** P2
+**估算:** 0.5 人天
+**前置依赖:** 无
+**影响文件:** `WechatService`、`application.yml`
+
+#### 修复步骤
+
+1. [ ] **`WechatService` 加 `@PostConstruct` 启动检查**
+   - 注入 `@Value("${spring.profiles.active:dev}")` 与 `@Value("${wechat.test-mode:false}")`
+   - 若 prod 且 test-mode=true:`throw new IllegalStateException("wechat.test-mode=true in production profile!")`
+2. [ ] **CI 检查**:新增 `tools/check-test-mode.ps1` 扫描 `application-prod*.yml` 有 `test-mode: true` 时 fail
+3. [ ] **日志告警**:dev 环境下 test-mode=true 时 startup 日志输出 WARN(不是错误,但提醒)
+
+#### 完成条件
+
+- prod 下 test-mode=true 启动必失败
+- CI 拦截配置错误
+
+---
+
+### G15. 饮食推荐规则层 + 解释链 + 推荐日志
+
+**优先级:** P2
+**估算:** 3-4 人天
+**前置依赖:** 无(G14 同期,可并行)
+**影响文件:** `MealRecommendService`、新建 `RecommendationRuleService`、`recommendation_log` 表
+
+#### 修复步骤
+
+1. [ ] **新建 `recommendation_log` 表**
+   ```sql
+   CREATE TABLE IF NOT EXISTS recommendation_log (
+     id BIGINT AUTO_INCREMENT PRIMARY KEY,
+     user_id BIGINT NOT NULL,
+     family_member_id BIGINT,
+     recommendation_type VARCHAR(32) NOT NULL COMMENT 'meal/supplement/exercise',
+     recommended_item VARCHAR(200),
+     explanation_json TEXT COMMENT '规则命中的解释',
+     dify_response_snippet VARCHAR(500),
+     accepted BOOLEAN,
+     created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+     INDEX idx_user_time (user_id, created_at)
+   ) ENGINE=InnoDB;
+   ```
+2. [ ] **新建 `RecommendationRuleService`**:在 Dify 之前执行确定性规则层
+   - 输入:用户健康指标、营养偏好、缺失项
+   - 规则示例:
+     - 若缺铁 → 推荐含铁食材(来自 Food 表 `nutritionTags contains 'iron'`)+ reason="你的健康报告显示铁含量低,建议补充含铁食物"
+     - 若过敏原 `user_food_preference.allergens` 非空 → 排除 candidate foods 中含过敏原的
+   - 输出结构化 result:`{recommendations:[...], reason: "规则命中文本"}`
+3. [ ] **`MealRecommendService.buildDifyInputs()` 改造**:
+   - 先跑规则层得到候选食物列表
+   - 把规则层候选作为 Dify 的"must include"约束
+   - 规则层结果与 Dify 结果合并,规则层 reason 优先展示
+4. [ ] **`DietaryRestrictions` 不再硬编码**:从 `user_food_preference` 表读 allergens、restrictions
+5. [ ] **`MealRecommendController.recommend` 落库**:每次返回前插 `recommendation_log`
+6. [ ] **新增端点** `POST /api/meal/recommend/{logId}/accept`:用户标记采纳/拒绝,回填 accepted 字段
+7. [ ] **新增端点** `POST /api/meal/recommendations/history`:用户查历史推荐
+
+#### 完成条件
+
+- 用户问"为什么推荐菠菜" → 平台有确定性答案("你铁含量低,建议补铁")
+- 过敏原强制过滤生效
+- 推荐有审计日志
+
+---
+
+## P3 长期治理
+
+---
+
+### G4. 清理死字段 / 接入消费者
+
+**优先级:** P3
+**估算:** 2 人天
+**前置依赖:** G3(测评结构化后死字段已部分被替代)
+**影响文件:** `DanAssessmentResult`、`ReportParseService`
+
+#### 修复步骤
+
+1. [ ] **审计 4 个字段**:
+   - `resultDesc` + `processDesc`:规划师原来填的,G3 改为结构化后已经无意义 → **删除**(迁移 DROP COLUMN,实体删字段)
+   - `gameRecords JSON`:有价值(原始测试数据),但无消费者 → **接入**:新增端点 `POST /api/dan-assessment/results/{id}/game-records` 返回解析后数据;`EnergyService.calcChildWisdom` 可读 gameRecords 替代部分 INT 字段
+   - Big Five(openness..neuroticism 6 列):有学术价值但无解读 → **接入**:新增 `BigFiveNormService` 做百分位换算;`cognitive-report.vue` 增"人格倾向"模块
+2. [ ] **删字段迁移**:
+   ```java
+   try {
+       jdbcTemplate.execute("ALTER TABLE dan_assessment_results DROP COLUMN result_desc");
+       jdbcTemplate.execute("ALTER TABLE dan_assessment_results DROP COLUMN process_desc");
+   } catch (Exception e) {}
+   ```
+3. [ ] **同步删 entity 字段、`DanAssessmentResult.java`**
+
+#### 完成条件
+
+- DB 表无死字段
+- 保留字段全部有端点读
+
+---
+
+### G21. PDF 解析多供应商抽象 + OCR 后备 + 置信度
+
+**优先级:** P3
+**估算:** 3-4 人天
+**前置依赖:** 无
+**影响文件:** `PdfParseService` 重构为 `ReportParserRegistry`、`ReportParser`接口、各供应商 parser impl
+
+#### 修复步骤
+
+1. [ ] **定义接口 `ReportParser`**
+   ```java
+   interface ReportParser {
+     String supplierCode();
+     boolean canHandle(String pdfTextOrFilename);
+     ParsedReport parse(byte[] pdfBytes) throws ReportParseException;
+   }
+   ```
+2. [ ] **抽象 `ReportParserRegistry`**:注入所有 `ReportParser` bean,按 `canHandle` 自动路由
+3. [ ] **现有募极逻辑移到 `MujiGutReportParser implements ReportParser`**(保留 868 行现有代码)
+4. [ ] **新增通用 fallback `SimpleScoreReportParser`**:现有 ReportParseService 抽出
+5. [ ] **解析结果加 `ParseConfidence`**:枚举 HIGH/MEDIUM/LOW;分数全找到=HIGH,<50% 字段找到=LOW
+6. [ ] **`HealthReportDraft` 加 `confidence_level` 字段**,preview 端点返回
+7. [ ] **OCR 后备**:若 PDF 文本提取为空(扫描版),调用腾讯云 OCR 或阿里 OCR 把图像转文本后走相同 parser
+8. [ ] **`HealthReportController.parse-preview` 入参加 `supplierHint`**:可显式指定供应商
+
+#### 完成条件
+
+- 至少 2 个供应商 parser 并行工作
+- 扫描版 PDF 能 OCR 后解析
+- 解析失败有 confidence 信号
+
+---
+
+### G27. sfms DataSource 唯读强制
+
+**优先级:** P3
+**估算:** 0.5 人天
+**前置依赖:** 无
+**影响文件:** `SfmsDataSourceConfig`
+
+#### 修复步骤
+
+1. [ ] **HikariConfig 加 `setReadOnly(true)`**
+2. [ ] **连接初始化 SQL**:`connection-test-query: SET SESSION TRANSACTION READ ONLY`(MySQL 8 支持会话级 read only)
+3. [ ] **`sfmsJdbcTemplate.execute()` 包装层**:新增 `SfmsReadOnlyJdbcTemplate extends JdbcTemplate`,覆写所有 write 方法抛 `UnsupportedOperationException`
+
+#### 完成条件
+
+- 任何 write 操作 fail-fast
+- DataMigration 仍能 read 正常
+
+---
+
+### G22. 前端 mock 数据 fallback 处理
+
+**优先级:** P3
+**估算:** 1.5 人天
+**前置依赖:** G1(G1 完成后真正有数据可用)
+**影响文件:** `growth-curve.vue`、`health-trend.vue`、`stats/index.vue`、`dimension-detail.vue`
+
+#### 修复步骤
+
+1. [ ] **`growth-curve.vue` 第 135 行 mock 移除**
+   - API 调用失败时显示 Element 风格 EmptyState "暂无记录" 按钮"添加首次测量"
+   - 不允许显示假数据
+2. [ ] **`health-trend.vue` 第 109-132 行 mock 移除**,改为 EmptyState
+3. [ ] **统一 `EmptyState` 组件** `components/EmptyState.vue`:图标 + 提示 + CTA
+4. [ ] **dev 模式 mock 数据通过环境变量开关**:`process.env.NODE_ENV === 'development' && uni.getStorageSync('useMock')` 才显示 mock,且页面顶部加 "演示数据" 红色 banner
+
+#### 完成条件
+
+- 生产环境不出现假数据
+- dev 调试仍可用 mock,但显式可见
+
+---
+
+## 待办依赖与并行机会图
+
+```
+P0:
+  G7 ──┐
+  G1 ──┬──── G18 (建议 G1 先)
+       │
+P1:    │
+  G8 ───┤                  (G8 不依赖 G1)
+  G9 ───┬─ G10
+  G13 ──┤                  (G13 与 G1 并行)
+  G14 ──┤                  (G14 与 G1 并行)
+  G2 ───┘                  (G2 与其他独立)
+       │
+P2:    │
+  G23 ──┬─ G25 (G23 先,对账机制)
+  G26 ──┤              (G26 独立)
+  G3 ───┤              (G3 与 G23 并行)
+  G15 ──┤              (G15 与 G23 并行)
+       │
+P3:    │
+  G27 ──┐        (G27 独立)
+  G22 ──┤        (G22 等 G1)
+  G21 ──┤        (G21 独立)
+  G4 ───┘        (G4 等 G3)
+```
+
+### 可立即并行的任务(同周内)
+
+- 第 1 周:G7 + G1 + G18(按序完成,但 G1 启动后 G18 可同时开始)
+- 第 2 周:G8 / G9+G10 / G13 / G14 / G2 / G26(六个并行)
+- 第 4 周:G23 / G3 / G25(等G23) / G15 / G21 / G27(六个并行)
+
+---
+
+## 整体验收清单
+
+- [ ] 所有 P0 任务 `mvn clean compile` 通过 + 单测 PASS
+- [ ] `FamilyAccessInterceptorTest` 全 PASS(G7)
+- [ ] 调 `/api/energy/score-history` 返回时间序列(G1)
+- [ ] `/api/assessment/compare/by-child` 返回 delta(G18)
+- [ ] 切回家长提示输密码(G8)
+- [ ] 邀请码过期后 accept 返回 410(G9)
+- [ ] 规划师绑定家庭家长需要 approve(G10)
+- [ ] 订单 status=results_ready 触发模板消息(G2)
+- [ ] 北京地址能选到街道(G13)
+- [ ] 关闭 Dify 后能查历史对话(G14)
+- [ ] schema.sql 重建数据库可用(G25)
+- [ ] prod 启动含 test-mode=true 失败(G26)
+- [ ] "为什么推荐菠菜" 平台能答(G15)
+
+---
+
+## 风险与回滚策略
+
+- **每个任务独立迁移**:用 `try-catch` 包裹,失败不阻塞应用启动
+- **G7 拦截器**:先以日志模式上线(不实际拦截只打 warn),观察 3 天后切为强制模式
+- **G8 视角切换合并**:保留旧端点返回 308 redirect 一周,再删除
+- **G13 地址数据**:上线前先 `SELECT` 校验种子数据完整性,确保关键省/市/区无空缺
+- **G3 结构化报告**:保留 `analysis_report TEXT` 字段,新加 `structured_analysis JSON` 并行存在 3 个月,再淘汰旧字段
+- **G14 Dify 镜像**:先只写本地不切读取路径,观察 1 周数据完整后再 enable fallback read
+- **G1 五维快照**:探针期间每条线都计算 + persist;观察性能不降级再优化为异步
+
+---
+
+## 后续工作(不在本计划)
+
+- 家长端可视化(趋势图对比图)—— 等 G1 数据积累 1 个月再返工前端图表
+- AI 健康助手对历史对话做 RAG 检索 —— 等 G14 数据 2 周
+- 知识库与测评结果自动联动(G16)—— 等 G3 结构化分析上线后接入
+
+---
+
+## 修订记录
+
+| 版本 | 日期 | 修订内容 |
+|:----:|:----:|------|
+| v1.0 | 2026-07-10 | 首次发布,18 任务 4 优先级 |

+ 186 - 0
docs/superpowers/specs/2026-07-10-certainty-ppt-design.md

@@ -0,0 +1,186 @@
+# 浠艾福项目总体介绍 PPT — 确定性叙事重构设计
+
+## 背景
+
+项目总体介绍 PPT 原版本存在两个问题:
+1. **叙事视角以"收入引擎"切入**,与用户(家庭)的价值感知脱节
+2. **内容重复**:slide07(会员订阅收入)重复 slide04、slide09(推广分润体系)重复 slide05
+3. **不可编辑**:采用 PNG 图片嵌入 PPTX,文字不可编辑
+
+本次重构从"确定性"角度重新组织叙事。
+
+## 核心框架:五大焦虑 → 五大确定性
+
+在充满不确定性的时代,家长的核心焦虑可归纳为五个维度,平台对应的提供五重确定性承诺:
+
+| 焦虑 | 确定性 | 一句话承诺 |
+|------|--------|-----------|
+| 健康焦虑 | 健康确定性 | 随时有人守护全家健康 |
+| 成长焦虑 | 成长确定性 | 每一步成长都可看见、可衡量 |
+| 经济焦虑 | 经济确定性 | 花得明白、省得安心、赚得从容 |
+| 关系焦虑 | 关系确定性 | 用科学方法经营最重要的关系 |
+| 未来焦虑 | 未来确定性 | 为孩子的核心竞争力持续投资 |
+
+## 整体结构(16页)
+
+### Part 1: 焦虑时代呼唤确定性(2页)
+
+**Slide 01 — 封面**
+- 品牌标识 + 标题:"浠艾福 — 给全家的五重确定性承诺"
+- 副标题:五维图标
+- 底部:亿图腾科技
+
+**Slide 02 — 这是一个充满焦虑的时代**
+- 左侧:五大焦虑关键词(数据可视化或关键词云):
+  - 健康焦虑:孩子反复生病怎么办?
+  - 成长焦虑:如何培养才有未来?
+  - 经济焦虑:投入能不能有回报?
+  - 关系焦虑:亲子关系如何改善?
+  - 未来焦虑:AI时代学什么才能不被淘汰?
+- 右侧:浠艾福的五重确定性承诺(5个图标+一句话)
+- 底部:确定性 = 焦虑的反面
+
+### Part 2: 五大确定性承诺(5页)
+
+每页统一结构:
+```
+顶部:确定性名称 + 一句话主张
+中部左侧:焦虑场景(2-3条,灰色/浅色)
+中部右侧:平台解决方案(4-5条,带图标)
+底部:确定性主张(品牌色突出显示)
+```
+
+**Slide 03 — 健康确定性**
+- 主张:你的家庭健康有人24小时守护
+- 场景:反复生病、营养不均衡、体质状况不明
+- 方案:AI健康顾问7×24h / 精准营养方案(菌群检测+AI)/ 认证管家随访(L1中级/L2资深)/ 全家健康档案(≤15人)/ 第三方检测合作
+
+**Slide 04 — 成长确定性**
+- 主张:每一步成长都可看见、可衡量
+- 场景:不知道孩子天赋方向、怕错过关键期、成长没有系统规划
+- 方案:五维能量体系(身/智/富/行/心全景画像)/ 专业测评体系(DAN+心智+综合素质)/ 成长档案追踪(里程碑+趋势)/ 规划师陪伴(个性化指导)/ 任务与激励系统(目标拆解+心愿兑换)
+
+**Slide 05 — 经济确定性**
+- 主张:花得明白、省得安心、赚得从容
+- 场景:教育投入无底洞、消费决策不透明、家庭经济压力大
+- 方案:L1一生一世¥1,314/年(全家≤10人,日均¥3.6)/ 情感定价透明(1314=一生一世)/ 会员折扣体系(商品8-9折)/ 消费分润返还(P点制,消费即投资)/ 推广增收通道(R0-R4月入¥0-20k)
+
+**Slide 06 — 关系确定性**
+- 主张:好的家庭关系是可以学习和经营的能力
+- 场景:亲子冲突频繁、夫妻育儿分歧、与长辈沟通不畅
+- 方案:家庭关系心理学(专业评估+改善建议)/ 亲子活动体系(线下+线上)/ 情绪认知与疏导(AI+专业双重支持)/ 家庭关系图(可视化家族网络)/ 双角色设计(家长端+孩子端互相理解)
+
+**Slide 07 — 未来确定性**
+- 主张:不让孩子成为'随机变量'
+- 场景:AI时代学什么才能不被淘汰、综合素质如何培养
+- 方案:核心素养量化(从测评数据看全面发展)/ 综合素质培养(任务/游戏/实践多维训练)/ 长期成长档案(K12全周期跟进)/ 趋势预测报告(AI分析+规划师解读)/ 家庭传承设计(心+智双传承)
+
+### Part 3: 确定性背后的引擎(4页)
+
+**Slide 08 — 四大商业引擎支撑确定性**
+- 重新组织原"商业模式总览"内容,围绕"确定性如何被支撑"展开:
+  - 会员订阅 → 经济确定性(透明定价) + 服务确定性(长期承诺)
+  - 交易抽佣 → 服务确定性(专业交付) + 质量确定性(平台背书)
+  - 推广分润 → 经济确定性(增收通道) + 信任确定性(社交推荐)
+  - AI增值 → 健康确定性(智能分析) + 成长确定性(数据驱动)
+- 四引擎图标+对应的确定性承诺
+
+**Slide 09 — 会员体系 + 推广体系(合并,解决重复)**
+- 左侧:L0注册会员 / L1一生一世¥1,314/年 / L2久久一生¥1,314/月
+- 右侧:R0利他者 → R1传福人 → R2聚能师 → R3启慧者 → R4传承者
+- 中间连接箭头:会员付费获得服务 → 推广分享获得收益
+- 底部:两大体系如何共同支撑经济确定性
+
+**Slide 10 — 三方价值闭环**
+- 沿袭三圆三角布局:家庭用户 × 推广管家 × 专业服务商
+- 箭头标注价值流转:家庭付费→平台→服务商交付;用户推荐→管家分润
+- 底部总结:确定性如何在整个生态中流转
+
+**Slide 11 — AI + 技术底座**
+- AI精准健康 / AI深度分析 / AI成长规划 / AI心理咨询
+- 技术架构示意(简化):前端(小程序+Web)+ 后端(Spring Boot)+ AI层(Dify+大模型)
+- 技术如何保障确定性的可靠交付
+
+### Part 4: 确定性已被验证 + 展望(4页)
+
+**Slide 12 — 五维能量 + 产品全景**
+- 五维体系:身/智/富/行/心
+- 核心服务矩阵:测评 / 健康 / 商城 / 活动 / 任务 / 档案
+
+**Slide 13 — 数据里程碑**
+- 用户数据 / 订单数据 / 测评数据 / 续费数据
+- 增长曲线图示意
+
+**Slide 14 — 路线图**
+- 已上线功能 + 规划中功能
+- 时间轴:过去 → 现在 → 未来
+
+**Slide 15 — 结尾:愿景**
+- 品牌标语:"给全家的一站式幸福提案"
+- 五维图标
+- 联系方式
+
+## 输出格式:可编辑 PPTX
+
+### 技术方案
+
+不再使用 Pillow 渲染 PNG + 嵌入 PPTX。
+
+改用 **python-pptx 原生元素**创建可编辑的 PPTX:
+
+1. **文本元素**:python-pptx TextFrame + Paragraph
+   - 每一段文字独立可编辑
+   - 支持设置字体(微软雅黑)、字号、颜色、加粗
+2. **形状元素**:MSO_SHAPE 矩形/圆角矩形/椭圆
+   - 用于卡片背景、装饰条、标签
+   - 可设置填充色、边框、透明度
+3. **布局**:每页手动计算元素位置(英寸)
+   - PPTX 坐标系:从左到右、从上到下,单位英寸
+   - 16:9 宽屏:13.333" × 7.5"
+4. **样式体系**:与之前统一
+   - 主色:B(#4A9BD7) O(#F97316) T(#4ECDC4) D(#1E1E28)
+   - 五维色:身(#FF8C42) 智(#6366F1) 富(#F59E0B) 行(#10B981) 心(#FF6B9D)
+
+### 限制与取舍
+
+- python-pptx 不支持渐变填充(仅纯色),背景用纯色或双色块拼接
+- python-pptx 不支持抗锯齿文字渲染(由 PowerPoint 引擎处理,效果足够)
+- 不支持投影效果(改用深色边框或分层色块替代)
+- 每个文本框需精确计算位置,版式复杂度低于 Pillow 渲染
+- 优势:每一页、每一段文字双击即可修改
+
+## 与旧版本的关系
+
+- 保留原封面设计风格(蓝橙主色、浅色商务风)
+- 删除 slide07(会员订阅收入)和 slide09(推广分润体系)完全避免重复
+- 将 slide03(商业模式)重构为 slide08(四引擎支撑确定性)
+- 将 slide11/16/17/18/19 压缩合并为 slide12/13/14
+
+## 设计规范
+
+### 字体
+- 标题:微软雅黑 Bold,28-36pt
+- 正文:微软雅黑 Regular,14-18pt  
+- 数据:微软雅黑 Bold,20-24pt
+
+### 色彩
+- 主色:Sky Blue #4A9BD7
+- 辅助色:Warm Orange #F97316
+- 强调色:Teal #4ECDC4
+- 文字色:Dark #1E1E28 / Medium #37374C / Light #A0A2AF
+- 五维色:身#FF8C42 智#6366F1 富#F59E0B 行#10B981 心#FF6B9D
+
+### 页边距
+- 左右内边距:0.8英寸
+- 顶部标题区:0.6英寸
+- 内容区:1.4-6.8英寸
+- 底部栏:6.8-7.5英寸
+
+## 成功标准
+
+1. 所有 16 页文字在 PPTX 中可直接编辑(双击修改)
+2. 无内容重复:每页有唯一的信息价值
+3. "确定性"叙事贯穿始终,五大确定性是核心信息
+4. 视觉风格统一,符合浅色商务风定位
+5. 总页数 ≤ 16 页
+6. 文件大小合理(估计 500KB-2MB,无大图片)