|
|
@@ -0,0 +1,1038 @@
|
|
|
+# 家庭归因分析问卷小程序落地 · 阶段一(数据地基)+ 阶段二(核心闭环 ③→⑤)系统设计与任务分解
|
|
|
+
|
|
|
+> **作者**:Bob(架构师 software-architect-2)|**日期**:2026-10-28
|
|
|
+> **定位**:本设计是「归因分析 → 调整方案 → 任务生成」链路在小程序落地的**增量设计**(阶段一 + 阶段二),供派发实例(后端 lan-248 / 前端 lan-204 / pub-iwintrue)按任务列表实现。**只读调研产物,不含任何业务代码改动。**
|
|
|
+> **输入依据**:
|
|
|
+> 1. 目标态需求解读 PRD:`docs/superpowers/specs/2026-10-27-family-attribution-questionnaire-prd.md`(§A 16 条问题表 / §B 7 章骨架 / §C 6 阶段状态机 / §D 10 条红线 / §E 开放问题)
|
|
|
+> 2. 现状核查与整改方案 v1.1:`docs/superpowers/specs/2026-10-27-family-attribution-questionnaire-remediation.md`(§2 现状矩阵+缺口 G1-G10 / §3 阶段一/二 A1-A4、B1-B4 / §5 拍板记录)
|
|
|
+> 3. 源文档:`docs/参考资料/家庭归因分析-孟凡锐-马智强-马昕源-马安扬-v1.1.html`(第八章=问诊清单)、`docs/参考资料/全家干预方案-妈妈-女儿-儿子-v1.1.html`(7 章模板)
|
|
|
+> 4. 现状代码证据(本机实证,路径+行号见 §5 与各节)
|
|
|
+
|
|
|
+## 主理人已拍板约束(设计红线,不可违背)
|
|
|
+
|
|
|
+| 编号 | 拍板结论 | 对设计的硬约束 |
|
|
|
+|------|----------|----------------|
|
|
|
+| **E12** | 合规后置,技术开发先行 | 阶段一/二**仅 mock/测试数据**;真实用户放行 = 阶段四 D1 完成日。全流程入口需可开关(`app.attribution.enabled=false` 默认,仅测试环境/mock 开启) |
|
|
|
+| **E4** | 高风险项弱提示 | Q11/Q12/Q13 命中仅标记「建议专业评估 + 转介信息露出 + 家长确认」,**问卷不阻断**;合规收口前真实用户环境不得上线 |
|
|
|
+| **E7** | 品牌级展示 | 方案直接展示 Enagic 还原水/姜黄、PHGG、益生菌株名(长双歧 BB536、乳双歧 HN019、布拉氏酵母菌);**品牌露出不得附带功效承诺**(P1-5「不宣称治疗」仍强制) |
|
|
|
+| **E6** | 跟随源文档 3 人 | 调整方案仅覆盖**妈妈/女儿/儿子**;爸爸链外(另有《胰腺炎糖尿病进阶方案》);角色确认走 `family_relationships`/`generation`,不默认系统推断 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+# Part A:系统设计
|
|
|
+
|
|
|
+## 1. 实现方案(Implementation Approach)
|
|
|
+
|
|
|
+### 1.1 核心难点
|
|
|
+
|
|
|
+1. **归因推理引擎(G1)**:现有 `HealthPlanService.generatePlanResult` 是「通用健康方案生成」(调 LangGraph `/api/v1/health/plan/generate`,见 `HealthPlanServiceImpl.java:276-321`),**无归因推理**(常模比对 → 指标解读 → 五维归因 → 主因链 → 被证伪项)。这是阶段二最大难点,必须新建纯规则引擎,而非复用 LangGraph 健康方案链路。
|
|
|
+2. **动态确认问卷条件路由(G2)**:`SurveyController` 只有静态模板提交(`SurveyController.java:26-57`),无「按对象/结论条件下发」。第八章 16 条明显条件化(Q2 乳杆菌异常 / Q4 铁偏低 / Q9 无 DAN / Q10-13 女儿 / Q14-15 儿子)。
|
|
|
+3. **自然语言调整→结构化回填(G3)**:`AIChatController` + `AiGateway` 对话已有,但缺「对话结论回填归因结构化字段 + ⑤ 失效重生成」编排。
|
|
|
+4. **7 章方案 schema(G4)**:`health_plans.planJson` 无「分人×层级(P0/P1/日常/P2)×动作×产品×具体做法×复盘钩子」约定与渲染。
|
|
|
+5. **常模参照(G10)**:710 条菌群常模 + DAN 常模现随报告/知识库,无后端独立常模表。
|
|
|
+6. **合规红线(G9)**:10 条红线(测评≠诊断 / 不调药 / 儿童剂量医生定 / 不贴标签 / 不宣称治疗 / 先医后补 / 服药间隔 / 铁餐纪律)需在生成时**硬编码注入**,且按 E7 为品牌级口径。
|
|
|
+
|
|
|
+### 1.2 框架/库选型(沿用既有技术栈,不引入新框架)
|
|
|
+
|
|
|
+| 层面 | 选型 | 理由 |
|
|
|
+|------|------|------|
|
|
|
+| 后端框架 | Spring Boot + MyBatis-Plus(`cfc-backend`) | 现状即此栈,全部实体用 `@TableName` + `@TableId` + `@TableField` 范式 |
|
|
|
+| 数据库 | MySQL(`zxyj` 库,`DatabaseInitializer` 幂等迁移) | 迁移写入点唯一入口 `runMigrations()`,`ensureColumn` 幂等加列 |
|
|
|
+| 规则引擎 | **纯 Java 无框架**(分层 Pipeline) | 归因推理需确定性、可单测、可控合规;不引入 Drools 等重框架(过度设计) |
|
|
|
+| AI 叙述 | 复用 `AiGateway`(`service/AiGateway.java`) | 已含熔断/超时/PII 脱敏/Fallback;归因叙述走规则骨架 + AI 润色,AI 失败规则兜底 |
|
|
|
+| 对话 | 复用 `AIChatController` + `aiService`(`/api/ai/chat/send`) | ④ 自然语言调整直接复用,注入归因专用 prompt(七层 Socratic) |
|
|
|
+| JSON 解析 | Jackson `ObjectMapper`(已全局注入) | 解析 questionsJson / planJson / condition 表达式 |
|
|
|
+| 常量/枚举 | Java 常量类 + `@TableName` 可选表 | A4 红线建议常量类(纯后端、可单测、合规后置期零数据风险) |
|
|
|
+
|
|
|
+### 1.3 架构模式
|
|
|
+
|
|
|
+- **分层 Pipeline + 门面服务**:`AttributionAnalysisService` 为门面,内部 5 段确定性规则引擎(NormCompare → IndicatorInterpret → FiveDimAttribution → MainCauseChain → Falsification)+ 1 段 AI 叙述(Narrative)。
|
|
|
+- **状态机驱动**:归因流程状态机(`① Submitted → ② Confirmed → ③ Ready → ④ Confirmed → ⑤ Generated → ⑥ TaskConfirmed`,见 §4.2 Mermaid),每阶段一个 Service 门面,状态落库 `attribution_reports.status`。
|
|
|
+- **合规即服务(compliance-as-code)**:红线文案全部来自 `AttributionRedlines` 常量,AI 只负责叙述、**不负责合规**——合规由规则层硬注入,AI 无法越权。
|
|
|
+- **面向接口**:`AttributionAnalysisService` / `AttributionAdjustService` / `AttributionPlanService` / `ConditionEvaluatorService` 均为接口 + `impl` 实现(沿用 `DimensionScoreService` 接口范式)。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 2. 文件清单(File List,相对 `cfc-backend/src/main/java/com/etotem/cfc/`)
|
|
|
+
|
|
|
+### 2.1 阶段一(数据地基,A1-A4)
|
|
|
+
|
|
|
+| 相对路径 | 动作 | 归属 |
|
|
|
+|----------|------|------|
|
|
|
+| `config/DatabaseInitializer.java` | 修改:`runMigrations()` 追加 `migrateHealthPlansPlanType()`(迁移 424)+ 常模/红线种子 | A1/A3/A4 |
|
|
|
+| `resources/schema.sql` | 修改:`health_plans` 加 `plan_type` 列;新建 `attribution_norm_values` / `attribution_redlines` 建表语句 | A1/A3/A4 |
|
|
|
+| `entity/HealthPlan.java` | 修改:加 `@TableField("plan_type") private String planType;` | A1 |
|
|
|
+| `entity/AttributionNormValue.java` | 新建:常模实体 | A3 |
|
|
|
+| `mapper/AttributionNormValueMapper.java` | 新建:常模 Mapper | A3 |
|
|
|
+| `entity/AttributionRedline.java` | 新建:红线实体(可选表方案)/ 或常量 | A4 |
|
|
|
+| `constant/AttributionRedlines.java` | 新建:10 条红线常量类(**推荐**,P0/P1/P2 分级 + 品牌级口径) | A4 |
|
|
|
+| `resources/norm/attribution_norm_gut_flora.json` | 新建:菌群 710 常模可配置 JSON 导入模板(空壳 + 说明,待业务侧导入真实数据) | A3 |
|
|
|
+
|
|
|
+> 注:`survey_templates` 无需 DDL 改动——`questionsJson` 是 TEXT,schema 扩展为**纯 JSON 约定**(A2),不迁移数据库。
|
|
|
+
|
|
|
+### 2.2 阶段二(核心闭环 ③→⑤,B1-B4)
|
|
|
+
|
|
|
+| 相对路径 | 动作 | 归属 |
|
|
|
+|----------|------|------|
|
|
|
+| `service/AttributionAnalysisService.java` | 新建(接口):归因门面 | B1 |
|
|
|
+| `service/impl/AttributionAnalysisServiceImpl.java` | 新建(实现):门面 + 流程编排 | B1 |
|
|
|
+| `service/attribution/NormCompareEngine.java` | 新建:常模比对段(确定性) | B1 |
|
|
|
+| `service/attribution/IndicatorInterpretEngine.java` | 新建:指标解读段(确定性) | B1 |
|
|
|
+| `service/attribution/FiveDimAttributionEngine.java` | 新建:五维归因段(确定性) | B1 |
|
|
|
+| `service/attribution/MainCauseChainEngine.java` | 新建:主因链段(确定性) | B1 |
|
|
|
+| `service/attribution/FalsificationEngine.java` | 新建:被证伪项段(确定性) | B1 |
|
|
|
+| `service/attribution/AttributionNarrativeService.java` | 新建:AI 叙述段(复用 `AiGateway`,失败规则兜底) | B1 |
|
|
|
+| `service/ConditionEvaluatorService.java` | 新建(接口 + impl):condition 表达式求值 | B2 |
|
|
|
+| `controller/SurveyController.java` | 修改:加 `POST /api/survey/attribution/confirm-questions`;submit 扩展 riskLevel 命中 | B2 |
|
|
|
+| `service/AttributionAdjustService.java` | 新建(接口):自然语言调整编排 | B3 |
|
|
|
+| `service/impl/AttributionAdjustServiceImpl.java` | 新建(实现):对话 + 回填 + 失效重生成 | B3 |
|
|
|
+| `service/AttributionPlanService.java` | 新建(接口):7 章方案生成 | B4 |
|
|
|
+| `service/impl/AttributionPlanServiceImpl.java` | 新建(实现):归因结论 → planJson | B4 |
|
|
|
+| `entity/AttributionReport.java` | 新建:归因报告聚合根(存 report_json + status + redline_hits) | B1 |
|
|
|
+| `mapper/AttributionReportMapper.java` | 新建:报告 Mapper | B1 |
|
|
|
+| `controller/AttributionController.java` | 新建:归因入口(analyze / confirm-questions / adjust / confirm-adjust / plan) | B1/B3/B4 |
|
|
|
+| `dto/AttributionInput.java` / `AttributionReportVO.java` / `AdjustResult.java` / `FamilyPlanVO.java` | 新建:DTO/VO | B1/B3/B4 |
|
|
|
+
|
|
|
+> 注:`AiGateway.java` / `AIChatController.java` / `HealthPlanController.java` / `HealthPlanService.java` / `FamilyContextService.java` **复用不改**(或仅注入归因专用 prompt 常量)。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 3. 数据结构与接口(Data Structures & Interfaces)
|
|
|
+
|
|
|
+### 3.1 A1:`health_plans.plan_type` 迁移
|
|
|
+
|
|
|
+- **最新迁移编号**:423(`DatabaseInitializer.java:3340` 注释「迁移423: 体脂秤测量记录表」;实测 `createBodyFatScaleRecordsTable()` 为 `runMigrations()` 末尾调用)。**新迁移 = 424**。
|
|
|
+- **写入点**:`runMigrations()` 末尾(423 之后,`DatabaseInitializer.java:3341` `createBodyFatScaleRecordsTable();` 后追加)调用新增私有方法 `migrateHealthPlansPlanType()`:
|
|
|
+
|
|
|
+```java
|
|
|
+private void migrateHealthPlansPlanType() {
|
|
|
+ // 迁移424: health_plans 加 plan_type(区分健康方案/家庭归因干预方案;G5)
|
|
|
+ ensureColumn("health_plans", "plan_type",
|
|
|
+ "VARCHAR(32) DEFAULT 'health_plan' COMMENT '方案类型: health_plan(健康方案)/family_attribution_plan(家庭归因干预方案)'");
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+- **同步 `schema.sql`**:`health_plans` 建表语句(`schema.sql:5082-5101`)加一行:
|
|
|
+ `plan_type VARCHAR(32) DEFAULT 'health_plan' COMMENT '方案类型: health_plan/family_attribution_plan',`
|
|
|
+- **`HealthPlan.java`** 加字段(`entity/HealthPlan.java`,`plan_json` 之后):
|
|
|
+
|
|
|
+```java
|
|
|
+/** 方案类型: health_plan(健康方案)/family_attribution_plan(家庭归因干预方案) */
|
|
|
+@TableField("plan_type") private String planType;
|
|
|
+```
|
|
|
+
|
|
|
+- **列值枚举**:`health_plan`(默认,存量行自动命中)/ `family_attribution_plan`。
|
|
|
+
|
|
|
+### 3.2 A2:`SurveyTemplate.questionsJson` schema 扩展(纯 JSON 约定,不改表)
|
|
|
+
|
|
|
+**扩展后 JSON 顶层结构**:
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "schemaVersion": "1.1",
|
|
|
+ "target": "family_attribution",
|
|
|
+ "audience": ["妈妈", "女儿", "儿子", "爸爸", "四人", "全家"],
|
|
|
+ "questions": [ { "id":"...", "code":"...", "group":"...", "title":"...",
|
|
|
+ "type":"single", "options":[...], "unit":null, "required":true,
|
|
|
+ "condition":null, "riskLevel":"none", "refRange":null } ]
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**字段定义表**:
|
|
|
+
|
|
|
+| 字段 | 类型 | 必填 | 说明 |
|
|
|
+|------|------|------|------|
|
|
|
+| `id` | string | ✔ | 问题稳定标识,如 `Q1a` / `Q11`;`Q` 前缀保证与源文档对应 |
|
|
|
+| `code` | string | ✔ | 采集变量名(条件求值键),如 `l.reuteriSupp` / `ironLow`;蛇形 |
|
|
|
+| `group` | string | ✔ | 对象分组:`四人`/`孟凡锐`/`马智强`/`马昕源`/`马安扬`/`全家`(或语义化 `dad`/`mom`/`daughter`/`son`/`all`/`family`) |
|
|
|
+| `title` | string | ✔ | 题面(必问项原文) |
|
|
|
+| `type` | enum | ✔ | `date` / `single`(单选) / `multi`(多选) / `number` / `scale`(量表 1-5) / `text` / `boolean` / `image` / `photoBatch`(多图) |
|
|
|
+| `options` | array | 条件 | 单选/多选选项,如 `["是","否"]` / `["0-1","1-2","2-3",">3"]` |
|
|
|
+| `unit` | string | 条件 | 单位,如 `%` / `h` / `岁` / `次/周` |
|
|
|
+| `required` | boolean | ✔ | 是否必答(`required=false` 用于「尽量」如 Q16) |
|
|
|
+| `condition` | object/null | 条件 | 条件表达式(见下);`null`=无条件恒发 |
|
|
|
+| `riskLevel` | enum | ✔ | `none` / `low` / `high`(`high`=Q11/Q12/Q13,涉未成年人心理/自伤/欺凌/进食) |
|
|
|
+| `refRange` | string | 条件 | 参考范围提示(如 Q4 铁餐纪律、Q9 DAN 意愿),可内嵌合规文案 |
|
|
|
+
|
|
|
+**condition 表达式求值方式**(`ConditionEvaluatorService`):
|
|
|
+
|
|
|
+```json
|
|
|
+{ "op": "==", "var": "l.reuteriAbnormal", "val": true }
|
|
|
+```
|
|
|
+
|
|
|
+- **操作符(op)**:`==` / `!=` / `>` / `>=` / `<` / `<=` / `contains`(数组含值) / `in`(值在数组) / `exists`(var 非空) / `anyOf`(OR 数组)。
|
|
|
+- **上下文(context)**:由对象档案 + 归因中间态 + 已作答构造。归因中间态键(由 B1 计算后供 B2 引用):`l.reuteriAbnormal`、`ironLow`(铁<阈值)、`hasDan`、`isFemaleTeen`(女性青少年)、`ageBand`、`role`、`zincDeficient`、`gutDiversityAbnormal`、`bowelBarrierAbnormal`、`bileAcidHigh`、`gasHigh` 等。
|
|
|
+- **组合**:`condition: { "op": "anyOf", "items": [ {...}, {...} ] }` 支持 OR;AND 用嵌套对象(缺省默认 AND 语义)。
|
|
|
+- **求值器签名**:`boolean evaluate(JsonNode condition, Map<String, Object> context)`;`condition==null` 恒真;未知 var 返回 `false`(保守下发,避免误发敏感题)。
|
|
|
+
|
|
|
+**riskLevel 枚举**:`none`(Q1-Q10/Q14-Q16 默认)/ `low`(需注意但非高风险)/ `high`(Q11 自伤/拒学/失眠、Q12 欺凌、Q13 进食风险)。命中 `high` 由前端标记「建议专业评估 + 转介信息露出 + 家长确认」(E4 弱提示,不阻断)。
|
|
|
+
|
|
|
+**第八章 16 条完整 JSON 样例(可直接入库 `questionsJson`)**:
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "schemaVersion": "1.1",
|
|
|
+ "target": "family_attribution",
|
|
|
+ "audience": ["四人", "孟凡锐", "马智强", "马昕源", "马安扬", "全家"],
|
|
|
+ "questions": [
|
|
|
+ { "id": "Q1a", "code": "sampleDate", "group": "四人", "title": "采样日期",
|
|
|
+ "type": "date", "options": null, "unit": null, "required": true,
|
|
|
+ "condition": null, "riskLevel": "none", "refRange": null },
|
|
|
+ { "id": "Q1b", "code": "antibioticUsed30d", "group": "四人", "title": "采样前 1 个月是否用过抗生素",
|
|
|
+ "type": "single", "options": ["是", "否"], "unit": null, "required": true,
|
|
|
+ "condition": null, "riskLevel": "none", "refRange": "决定菌群失衡是结构问题还是药物印记" },
|
|
|
+ { "id": "Q1c", "code": "acidSuppressantUsed30d", "group": "四人", "title": "采样前 1 个月是否用过抑酸药",
|
|
|
+ "type": "single", "options": ["是", "否"], "unit": null, "required": true,
|
|
|
+ "condition": null, "riskLevel": "none", "refRange": null },
|
|
|
+ { "id": "Q1d", "code": "laxativeUsed30d", "group": "四人", "title": "采样前 1 个月是否用过泻药",
|
|
|
+ "type": "single", "options": ["是", "否"], "unit": null, "required": true,
|
|
|
+ "condition": null, "riskLevel": "none", "refRange": null },
|
|
|
+
|
|
|
+ { "id": "Q2", "code": "l.reuteriSupp", "group": "孟凡锐", "title": "是否在服含罗伊氏乳杆菌的益生菌?",
|
|
|
+ "type": "single", "options": ["是", "否"], "unit": null, "required": true,
|
|
|
+ "condition": { "op": "==", "var": "l.reuteriAbnormal", "val": true },
|
|
|
+ "riskLevel": "none", "refRange": "乳杆菌 7.268% 异常高,可能是补充剂印记,属可撤回项" },
|
|
|
+ { "id": "Q2b", "code": "l.reuteriDuration", "group": "孟凡锐", "title": "服了多久?",
|
|
|
+ "type": "text", "options": null, "unit": "月", "required": false,
|
|
|
+ "condition": { "op": "==", "var": "l.reuteriSupp", "val": "是" },
|
|
|
+ "riskLevel": "none", "refRange": null },
|
|
|
+
|
|
|
+ { "id": "Q3a", "code": "fatigue", "group": "孟凡锐", "title": "疲劳程度",
|
|
|
+ "type": "scale", "options": ["无", "轻度", "中度", "重度"], "unit": null, "required": true,
|
|
|
+ "condition": null, "riskLevel": "none", "refRange": "铁 8 是否已有体征" },
|
|
|
+ { "id": "Q3b", "code": "bloating", "group": "孟凡锐", "title": "腹胀程度",
|
|
|
+ "type": "scale", "options": ["无", "轻度", "中度", "重度"], "unit": null, "required": true,
|
|
|
+ "condition": null, "riskLevel": "none", "refRange": "产气 94 是否已有体征" },
|
|
|
+ { "id": "Q3c", "code": "bowelFreq", "group": "孟凡锐", "title": "排便频率",
|
|
|
+ "type": "single", "options": ["<1次/天", "1次/天", "2-3次/天", ">3次/天"], "unit": null, "required": true,
|
|
|
+ "condition": null, "riskLevel": "none", "refRange": null },
|
|
|
+ { "id": "Q3d", "code": "bowelForm", "group": "孟凡锐", "title": "排便形态(Bristol)",
|
|
|
+ "type": "single", "options": ["1", "2", "3", "4", "5", "6", "7"], "unit": "Bristol分", "required": true,
|
|
|
+ "condition": null, "riskLevel": "none", "refRange": "3-4 为理想" },
|
|
|
+ { "id": "Q3e", "code": "headacheRash", "group": "孟凡锐", "title": "头痛/皮疹",
|
|
|
+ "type": "multi", "options": ["头痛", "皮疹", "无"], "unit": null, "required": false,
|
|
|
+ "condition": null, "riskLevel": "none", "refRange": null },
|
|
|
+ { "id": "Q3f", "code": "allergyHx", "group": "孟凡锐", "title": "过敏史",
|
|
|
+ "type": "text", "options": null, "unit": null, "required": false,
|
|
|
+ "condition": null, "riskLevel": "none", "refRange": null },
|
|
|
+
|
|
|
+ { "id": "Q4a", "code": "ironMealWithMilkTea", "group": "孟凡锐", "title": "含铁餐是否与奶/茶/咖啡同餐",
|
|
|
+ "type": "single", "options": ["是", "否"], "unit": null, "required": true,
|
|
|
+ "condition": { "op": "==", "var": "ironLow", "val": true },
|
|
|
+ "riskLevel": "none", "refRange": "决定铁吸收是否被自己堵死;铁餐与奶/茶/咖啡错开≥2h、配维C" },
|
|
|
+ { "id": "Q4b", "code": "breakfastComposition", "group": "孟凡锐", "title": "早餐结构",
|
|
|
+ "type": "text", "options": null, "unit": null, "required": false,
|
|
|
+ "condition": { "op": "==", "var": "ironLow", "val": true },
|
|
|
+ "riskLevel": "none", "refRange": null },
|
|
|
+
|
|
|
+ { "id": "Q5a", "code": "diabetesDuration", "group": "马智强", "title": "糖尿病病程",
|
|
|
+ "type": "number", "options": null, "unit": "年", "required": true,
|
|
|
+ "condition": { "op": "==", "var": "hasDiabetes", "val": true },
|
|
|
+ "riskLevel": "low", "refRange": "⚠️临床项:决定 P0 随访紧迫度(本文不调药)" },
|
|
|
+ { "id": "Q5b", "code": "diabetesMeds", "group": "马智强", "title": "目前用药",
|
|
|
+ "type": "text", "options": null, "unit": null, "required": true,
|
|
|
+ "condition": { "op": "==", "var": "hasDiabetes", "val": true },
|
|
|
+ "riskLevel": "low", "refRange": null },
|
|
|
+ { "id": "Q5c", "code": "hba1cLatest", "group": "马智强", "title": "最近一次糖化血红蛋白",
|
|
|
+ "type": "number", "options": null, "unit": "%", "required": true,
|
|
|
+ "condition": { "op": "==", "var": "hasDiabetes", "val": true },
|
|
|
+ "riskLevel": "low", "refRange": "⚠️临床项" },
|
|
|
+
|
|
|
+ { "id": "Q6a", "code": "heightCm", "group": "马智强", "title": "身高",
|
|
|
+ "type": "number", "options": null, "unit": "cm", "required": true,
|
|
|
+ "condition": null, "riskLevel": "none", "refRange": "体重目标由医生定,但基线必须有" },
|
|
|
+ { "id": "Q6b", "code": "weightKg", "group": "马智强", "title": "体重",
|
|
|
+ "type": "number", "options": null, "unit": "kg", "required": true,
|
|
|
+ "condition": null, "riskLevel": "none", "refRange": null },
|
|
|
+ { "id": "Q6c", "code": "waistCm", "group": "马智强", "title": "腰围",
|
|
|
+ "type": "number", "options": null, "unit": "cm", "required": true,
|
|
|
+ "condition": null, "riskLevel": "none", "refRange": "算 BMI 与中心性肥胖" },
|
|
|
+
|
|
|
+ { "id": "Q7a", "code": "alcoholFreq", "group": "马智强", "title": "饮酒频率",
|
|
|
+ "type": "single", "options": ["从不", "<1次/周", "1-3次/周", "≥4次/周"], "unit": null, "required": true,
|
|
|
+ "condition": null, "riskLevel": "none", "refRange": null },
|
|
|
+ { "id": "Q7b", "code": "lateNightTakeoutFreq", "group": "马智强", "title": "夜宵/外卖频率",
|
|
|
+ "type": "number", "options": null, "unit": "次/周", "required": true,
|
|
|
+ "condition": null, "riskLevel": "none", "refRange": "高脂高蛋白主要来源" },
|
|
|
+ { "id": "Q7c", "code": "banquetPattern", "group": "马智强", "title": "应酬结构",
|
|
|
+ "type": "text", "options": null, "unit": null, "required": false,
|
|
|
+ "condition": null, "riskLevel": "none", "refRange": null },
|
|
|
+
|
|
|
+ { "id": "Q8a", "code": "sleepHours", "group": "马智强", "title": "睡眠时长",
|
|
|
+ "type": "number", "options": null, "unit": "h", "required": true,
|
|
|
+ "condition": null, "riskLevel": "none", "refRange": "与血糖控制、日间疲劳强相关" },
|
|
|
+ { "id": "Q8b", "code": "snoringApnea", "group": "马智强", "title": "是否打鼾/憋气(OSA 线索)",
|
|
|
+ "type": "single", "options": ["从不", "偶尔", "经常", "几乎每天"], "unit": null, "required": true,
|
|
|
+ "condition": null, "riskLevel": "none", "refRange": null },
|
|
|
+
|
|
|
+ { "id": "Q9", "code": "danConsent", "group": "马智强", "title": "是否愿意做 DAN(情绪+认知)",
|
|
|
+ "type": "single", "options": ["愿意", "暂缓", "不愿意"], "unit": null, "required": true,
|
|
|
+ "condition": { "op": "==", "var": "hasDan", "val": false },
|
|
|
+ "riskLevel": "none", "refRange": "补家庭系统最后一角;⚠️意愿项" },
|
|
|
+
|
|
|
+ { "id": "Q10a", "code": "menarcheAge", "group": "马昕源", "title": "初潮时间",
|
|
|
+ "type": "number", "options": null, "unit": "岁", "required": true,
|
|
|
+ "condition": { "op": "==", "var": "isFemaleTeen", "val": true },
|
|
|
+ "riskLevel": "none", "refRange": "14岁女性,铁42但需排除月经相关波动" },
|
|
|
+ { "id": "Q10b", "code": "menstrualRegularity", "group": "马昕源", "title": "周期规律",
|
|
|
+ "type": "single", "options": ["规律", "不规律"], "unit": null, "required": true,
|
|
|
+ "condition": { "op": "==", "var": "isFemaleTeen", "val": true },
|
|
|
+ "riskLevel": "none", "refRange": null },
|
|
|
+
|
|
|
+ { "id": "Q11a", "code": "mood3m", "group": "马昕源", "title": "近3个月情绪状态",
|
|
|
+ "type": "scale", "options": ["平稳", "波动", "低落", "明显低落"], "unit": null, "required": true,
|
|
|
+ "condition": null, "riskLevel": "high", "refRange": "⚠️高风险红线:P0 转介判断依据" },
|
|
|
+ { "id": "Q11b", "code": "sleep3m", "group": "马昕源", "title": "近3个月睡眠",
|
|
|
+ "type": "scale", "options": ["正常", "偶尔失眠", "频繁失眠", "持续失眠"], "unit": null, "required": true,
|
|
|
+ "condition": null, "riskLevel": "high", "refRange": "持续失眠→立即就医(E4弱提示+家长确认)" },
|
|
|
+ { "id": "Q11c", "code": "schoolRefusal", "group": "马昕源", "title": "是否拒学",
|
|
|
+ "type": "single", "options": ["无", "偶尔", "频繁"], "unit": null, "required": true,
|
|
|
+ "condition": null, "riskLevel": "high", "refRange": "拒学→立即就医" },
|
|
|
+ { "id": "Q11d", "code": "selfHarmIdea", "group": "马昕源", "title": "是否有自伤念头",
|
|
|
+ "type": "single", "options": ["无", "有"], "unit": null, "required": true,
|
|
|
+ "condition": null, "riskLevel": "high", "refRange": "自伤→立即就医,不靠产品硬扛" },
|
|
|
+
|
|
|
+ { "id": "Q12a", "code": "schoolStressors", "group": "马昕源", "title": "学校压力源",
|
|
|
+ "type": "multi", "options": ["学业", "同伴", "师生", "家庭", "无"], "unit": null, "required": true,
|
|
|
+ "condition": null, "riskLevel": "high", "refRange": "解释同伴亲近 3.3%ile" },
|
|
|
+ { "id": "Q12b", "code": "peerRelation", "group": "马昕源", "title": "同伴关系",
|
|
|
+ "type": "text", "options": null, "unit": null, "required": true,
|
|
|
+ "condition": null, "riskLevel": "high", "refRange": "同伴依恋项判定无效,需人工确认" },
|
|
|
+ { "id": "Q12c", "code": "bullied", "group": "马昕源", "title": "是否被排挤/欺凌",
|
|
|
+ "type": "single", "options": ["无", "偶尔", "频繁"], "unit": null, "required": true,
|
|
|
+ "condition": null, "riskLevel": "high", "refRange": "⚠️敏感项" },
|
|
|
+
|
|
|
+ { "id": "Q13a", "code": "dietingConcern", "group": "马昕源", "title": "是否节食/在意体重",
|
|
|
+ "type": "single", "options": ["无", "轻度在意", "明显在意", "正在节食"], "unit": null, "required": true,
|
|
|
+ "condition": null, "riskLevel": "high", "refRange": "排除进食行为风险(躯体外貌 5.4%ile)" },
|
|
|
+ { "id": "Q13b", "code": "bodyWeightConcern", "group": "马昕源", "title": "体重关注程度",
|
|
|
+ "type": "scale", "options": ["0", "1", "2", "3", "4", "5"], "unit": null, "required": true,
|
|
|
+ "condition": null, "riskLevel": "high", "refRange": "⚠️进食风险筛查" },
|
|
|
+
|
|
|
+ { "id": "Q14a", "code": "birthMode", "group": "马安扬", "title": "出生方式",
|
|
|
+ "type": "single", "options": ["顺产", "剖腹产"], "unit": null, "required": true,
|
|
|
+ "condition": null, "riskLevel": "none", "refRange": "解释铁锌缺乏与 GABA 1" },
|
|
|
+ { "id": "Q14b", "code": "feedingHx", "group": "马安扬", "title": "喂养史",
|
|
|
+ "type": "single", "options": ["母乳", "混合", "配方"], "unit": null, "required": true,
|
|
|
+ "condition": null, "riskLevel": "none", "refRange": null },
|
|
|
+ { "id": "Q14c", "code": "antibioticHx", "group": "马安扬", "title": "抗生素史",
|
|
|
+ "type": "single", "options": ["无", "有"], "unit": null, "required": true,
|
|
|
+ "condition": null, "riskLevel": "none", "refRange": null },
|
|
|
+ { "id": "Q14d", "code": "eczemaRhinAsthma", "group": "马安扬", "title": "湿疹/鼻炎/哮喘",
|
|
|
+ "type": "multi", "options": ["湿疹", "鼻炎", "哮喘", "无"], "unit": null, "required": true,
|
|
|
+ "condition": null, "riskLevel": "none", "refRange": null },
|
|
|
+
|
|
|
+ { "id": "Q15a", "code": "teacherFeedback", "group": "马安扬", "title": "老师反馈(注意力)",
|
|
|
+ "type": "text", "options": null, "unit": null, "required": false,
|
|
|
+ "condition": null, "riskLevel": "low", "refRange": "区分注意力问题与营养缺口" },
|
|
|
+ { "id": "Q15b", "code": "bowelHabit", "group": "马安扬", "title": "排便习惯",
|
|
|
+ "type": "single", "options": ["<1次/天", "1次/天", "2-3次/天", ">3次/天"], "unit": null, "required": true,
|
|
|
+ "condition": null, "riskLevel": "none", "refRange": null },
|
|
|
+ { "id": "Q15c", "code": "pickyFoodList", "group": "马安扬", "title": "挑食清单",
|
|
|
+ "type": "multi", "options": ["蔬菜", "肉", "蛋", "奶", "豆", "无"], "unit": null, "required": true,
|
|
|
+ "condition": null, "riskLevel": "none", "refRange": null },
|
|
|
+
|
|
|
+ { "id": "Q16", "code": "weeklyMealPhotos", "group": "全家", "title": "一周典型三餐照片(连拍7天)",
|
|
|
+ "type": "photoBatch", "options": null, "unit": "张", "required": false,
|
|
|
+ "condition": null, "riskLevel": "none", "refRange": "验证一个厨房的口味基线;负担最重,建议放宽为尽量" }
|
|
|
+ ]
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+> **说明**:源文档「16 条」按变量展开为 39 个采集项(`Q1a`…`Q16`);每条仍保留源文档的 `Q` 编号归属,`code` 为条件求值键。条件依赖的归因中间态键(`l.reuteriAbnormal` / `ironLow` / `hasDan` / `isFemaleTeen` / `hasDiabetes`)由 B1 常模比对段计算后注入 context(B2)。
|
|
|
+
|
|
|
+### 3.3 A3:`attribution_norm_values` 常模表(G10)
|
|
|
+
|
|
|
+**DDL**:
|
|
|
+
|
|
|
+```sql
|
|
|
+CREATE TABLE IF NOT EXISTS attribution_norm_values (
|
|
|
+ id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
|
|
+ norm_type VARCHAR(20) NOT NULL COMMENT '常模类型: gut_flora(菌群)/dan(DAN测评)',
|
|
|
+ definition_id BIGINT NULL COMMENT 'FK→indicator_definitions.id(归一化后关联,可空)',
|
|
|
+ code VARCHAR(64) NOT NULL COMMENT '指标编码(菌种学名如Bifidobacterium 或 DAN维度key如emotionScore)',
|
|
|
+ display_name VARCHAR(100) COMMENT '显示名',
|
|
|
+ member_age_band VARCHAR(32) NOT NULL COMMENT '年龄段带: 0-3/4-6/7-12/13-18/19-45/46-65/66+/all',
|
|
|
+ gender VARCHAR(10) DEFAULT 'all' COMMENT '性别: male/female/all',
|
|
|
+ p5 DECIMAL(10,4) NULL COMMENT 'P5百分位值',
|
|
|
+ p25 DECIMAL(10,4) NULL COMMENT 'P25百分位值',
|
|
|
+ p50 DECIMAL(10,4) NULL COMMENT 'P50百分位值(中位数)',
|
|
|
+ p75 DECIMAL(10,4) NULL COMMENT 'P75百分位值',
|
|
|
+ p95 DECIMAL(10,4) NULL COMMENT 'P95百分位值',
|
|
|
+ unit VARCHAR(20) COMMENT '单位(如 %)',
|
|
|
+ norm_version VARCHAR(20) NOT NULL DEFAULT 'v1' COMMENT '常模版本',
|
|
|
+ source VARCHAR(200) COMMENT '数据来源(谷禾710常模/DAN常模/导入文件名)',
|
|
|
+ status TINYINT DEFAULT 1 COMMENT '0=禁用 1=启用',
|
|
|
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
|
+ UNIQUE KEY uk_type_code_band_gender_ver (norm_type, code, member_age_band, gender, norm_version),
|
|
|
+ INDEX idx_type_code (norm_type, code),
|
|
|
+ INDEX idx_def (definition_id)
|
|
|
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭归因常模值';
|
|
|
+```
|
|
|
+
|
|
|
+**Entity `AttributionNormValue.java`**(MyBatis-Plus 范式,同 `CognitiveNorm`):
|
|
|
+
|
|
|
+```java
|
|
|
+@Data
|
|
|
+@TableName("attribution_norm_values")
|
|
|
+public class AttributionNormValue implements Serializable {
|
|
|
+ @TableId(type = IdType.AUTO)
|
|
|
+ private Long id;
|
|
|
+ private String normType; // gut_flora / dan
|
|
|
+ private Long definitionId; // 可空,关联 indicator_definitions
|
|
|
+ private String code; // 菌种学名 或 DAN 维度 key
|
|
|
+ private String displayName;
|
|
|
+ private String memberAgeBand; // 年龄段带
|
|
|
+ private String gender; // male/female/all
|
|
|
+ private BigDecimal p5;
|
|
|
+ private BigDecimal p25;
|
|
|
+ private BigDecimal p50;
|
|
|
+ private BigDecimal p75;
|
|
|
+ private BigDecimal p95;
|
|
|
+ private String unit;
|
|
|
+ private String normVersion;
|
|
|
+ private String source;
|
|
|
+ private Integer status;
|
|
|
+ private Date createdAt;
|
|
|
+ private Date updatedAt;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**Mapper `AttributionNormValueMapper.java`**:`public interface AttributionNormValueMapper extends BaseMapper<AttributionNormValue> {}`(沿用现有 Mapper 空接口范式)。
|
|
|
+
|
|
|
+**常模数据来源(⚠️ 需明确标注「未检索到的东西」)**:
|
|
|
+
|
|
|
+| 常模 | 现状 | 结构化源是否已存在 |
|
|
|
+|------|------|--------------------|
|
|
|
+| **菌群 710 条常模**(谷禾 21 维度 × 分龄/分性百分位) | 仅随报告(`health_gut_flora.populationLevel` 存**单条**人群百分位字符串)+ 知识库 | ❌ **未检索到**独立 p5-p95 结构化表/JSON。知识库 `统一知识库.json` 有 `菌种综合信息`(功能/关联疾病/检出报告数)、`菌属功能库`、`报告指标分级标准`(142 指标标准范围/状态分级,非 p5-p95)。→ **设计为「可配置 JSON 导入」**:`resources/norm/attribution_norm_gut_flora.json` 空壳模板(`[{code,memberAgeBand,gender,p5,p25,p50,p75,p95,unit,source,normVersion}]`),由业务侧/教练侧填充真实 710 条后导入 |
|
|
|
+| **DAN 常模**(情绪/大五/认知 p5-p95) | `cognitive_norms` 表已有(`CognitiveNorm.java`:dimensionKey/gender/ageMin(月)/ageMax(月)/p5-p95)+ `health_norm_reference` 表已有(`HealthNormReference.java`:dimension/gender/ageMin/ageMax/p5-p95,含 emotion/bigFive/drive/selfConcept/action/wealth 种子 18 条) | ✅ 已存在,**可直接复用**:B1 常模比对段优先查 `cognitive_norms`/`health_norm_reference`,缺失时才落 `attribution_norm_values` |
|
|
|
+
|
|
|
+> **设计取舍**:`attribution_norm_values` 作为**归因专用统一常模视图**,兼容两类 norm_type;DAN 常模优先复用既有 `cognitive_norms`/`health_norm_reference`(避免双写),仅在 DAN 需「年龄段带+性别」维度不满足时用 `attribution_norm_values` 补位。菌群 710 常模**必须**落 `attribution_norm_values`(当前无任何落点)。
|
|
|
+
|
|
|
+### 3.4 A4:合规红线模板(G9,10 条,按 PRD §D 分级 + E7 品牌级口径)
|
|
|
+
|
|
|
+**推荐:常量类 `constant/AttributionRedlines.java`**(纯后端、可单测、零数据风险,符合 E12 合规后置)。提供枚举 `Redline` 与 `LEVEL`:
|
|
|
+
|
|
|
+```java
|
|
|
+public final class AttributionRedlines {
|
|
|
+ public enum Level { P0, P1, P2 }
|
|
|
+ public static final class Redline {
|
|
|
+ public final String code; // RL01..RL10
|
|
|
+ public final Level level;
|
|
|
+ public final String title;
|
|
|
+ public final String message; // 品牌级口径文案
|
|
|
+ public final String scope; // appliesTo: 归因报告/调整方案/任务/全部
|
|
|
+ }
|
|
|
+ public static List<Redline> all() { ... }
|
|
|
+ public static List<Redline> byLevel(Level l) { ... }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**10 条红线明细**(`code`/`level`/`title`/`message(品牌级口径)`/`scope`):
|
|
|
+
|
|
|
+| code | level | title | message(E7 品牌级口径,品牌露出不附带功效承诺) | scope |
|
|
|
+|------|-------|-------|--------------------------------------------------|-------|
|
|
|
+| RL01 | P0 | 测评≠诊断 | 菌群「相似度」是模式比对,不是疾病;DAN 不是智力测验也不是临床诊断。所有关联均为关联,非因果。 | 全部 |
|
|
|
+| RL02 | P0 | 情绪红线 | 0.9 百分位的情绪总分不是谈心能处理的量级;出现自伤、拒学、持续失眠 → 立即就医。本方案不做情绪处理,角色是「识别+转介」。 | ③④ |
|
|
|
+| RL03 | P0 | 儿童补剂红线 | 儿童补铁补锌必须由儿科/儿保科按体重与检验结果定剂量,铁过量有毒,严禁自行加量。方案不出现儿童剂量数字,只写「医生定」。 | ⑤⑥ |
|
|
|
+| RL04 | P0 | 糖尿病红线 | 不调药、不荐药、不设血糖目标值。随访节奏、用药、减重目标全部由内分泌科定;教练只做生活方式支持与「让他去随访」。 | ③⑤ |
|
|
|
+| RL05 | P1 | 产品非治疗 | Enagic 还原水/姜黄、瓜尔豆益生元 PHGG、益生菌均为膳食或食品级支持,不宣称治疗任何疾病、不替代药物;有特殊病史(胆结石、抗凝药、肾功异常等)先咨询医生。 | ⑤ |
|
|
|
+| RL06 | P1 | 标签红线 | 不在孩子面前使用「多动症」「抑郁」「天才」「学霸」等定性词。 | ③⑤⑥ |
|
|
|
+| RL07 | P1 | 先医后补 | 所有涉及查血、补剂的动作都「先医后补」,不自行下诊断、不给剂量。P0 动作全部是查血/评估/随访,而非补。 | ⑤⑥ |
|
|
|
+| RL08 | P1 | 教练禁忌 | 永远不对家长说「你缺某一维」;不替家长做决定;不用评判式语言;不硬套模型。 | ④ |
|
|
|
+| RL09 | P2 | 服药间隔纪律 | 还原水不与药物同服(间隔≥1h);益生菌与抗生素间隔≥2h;PHGG 不与抗生素同服,用后停纤维 3-5 天再做菌群重建。 | ⑤ |
|
|
|
+| RL10 | P2 | 铁餐纪律 | 铁餐与奶/茶/咖啡错开≥2h、配维 C。 | ⑤ |
|
|
|
+
|
|
|
+**可选表方案**:如需管理端可编辑,提供 `attribution_redlines` 表(`id/code/level/title/message/scope/status/updated_at`),由 `runMigrations()` 迁移 424 段幂等建表 + 种子;**默认走常量类**,表方案为低优先级备选。
|
|
|
+
|
|
|
+### 3.5 B1:`AttributionAnalysisService` 归因引擎
|
|
|
+
|
|
|
+**接口签名**:
|
|
|
+
|
|
|
+```java
|
|
|
+public interface AttributionAnalysisService {
|
|
|
+ /** 核心入口:输入档案+确认问卷+检测数据+常模 → 十节归因报告 */
|
|
|
+ AttributionReportVO analyze(AttributionInput input);
|
|
|
+ /** 取报告(含十节结构) */
|
|
|
+ AttributionReportVO getReport(Long attributionId);
|
|
|
+ /** ④调整后重算(B3 回填后调用;作废⑤由 AttributionPlanService 处理) */
|
|
|
+ AttributionReportVO recompute(Long attributionId, Map<String,Object> fieldDeltas);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**`AttributionInput` 字段**:`familyId`、`memberIds`(List<Long>,3 人:妈妈/女儿/儿子,E6)、`gutReportIds`(菌群报告ID列表,桥接 `health_gut_flora`)、`danResultIds`(DAN 结果ID列表)、`confirmSurveyRecordId`(确认问卷记录,桥接 `survey_records.answersJson`)、`planId`(关联 health_plans,可空)。
|
|
|
+
|
|
|
+**数据流**(菌群JSON + DAN + 常模 + 确认问卷 → 十节归因报告):
|
|
|
+
|
|
|
+```
|
|
|
+[菌群 reportId→health_gut_flora] [DAN danResultId→dan_assessment_results]
|
|
|
+ │ │
|
|
|
+ ▼ ▼
|
|
|
+ ┌──────────────────────────────────────────────────────────┐
|
|
|
+ │ AttributionAnalysisServiceImpl.analyze(input) │
|
|
|
+ │ Pipeline: 5 段确定性规则 + 1 段 AI 叙述 │
|
|
|
+ │ │
|
|
|
+ │ ① NormCompareEngine 常模比对 → 每指标 percentileBand + status│
|
|
|
+ │ ② IndicatorInterpretEngine 指标解读 → 五维含义(异常/偏离/可撤回) │
|
|
|
+ │ ③ FiveDimAttributionEngine 逐人+家庭 五维归因(0-100 教练相对刻度) │
|
|
|
+ │ ④ MainCauseChainEngine 主因链(把四人的问题串成一条链) │
|
|
|
+ │ ⑤ FalsificationEngine 被证伪项(同伴依恋无效/乳杆菌可撤回等) │
|
|
|
+ │ ⑥ AttributionNarrativeService 十节叙述(AI润色+规则兜底) │
|
|
|
+ └──────────────────────────────────────────────────────────┘
|
|
|
+ │
|
|
|
+ ▼ 红线注入(RL01-RL08 由规则层硬注入,非 AI)
|
|
|
+ AttributionReportVO {
|
|
|
+ status: ANALYZING→READY,
|
|
|
+ reportJson: { 结论先行 / 数据速览 / 逐人归因 / 家庭系统归因
|
|
|
+ (五维地图+主因链+被证伪项) / 应对方案 / 12周目标 /
|
|
|
+ 教练提问序列 / 问诊清单(已回收v1.2) / 红线与转介 / 一句话收尾 },
|
|
|
+ redlineHits: [RL02...],
|
|
|
+ intermediateState: { l.reuteriAbnormal, ironLow, hasDan, ... } // 供B2/B3
|
|
|
+ }
|
|
|
+```
|
|
|
+
|
|
|
+**十节报告结构(对齐《归因分析 v1.1》一~十)**:
|
|
|
+
|
|
|
+| 节 | 标题 | 生成方式 | 关键字段 |
|
|
|
+|----|------|----------|----------|
|
|
|
+| 一 | 结论先行 | AI 叙述 + 规则事实注入 | `conclusionHeadline`(如「四条表象,两个主因维」) |
|
|
|
+| 二 | 四人数据速览 | 规则 | `overview[]`:指标×成员×百分位×干预含义 |
|
|
|
+| 三 | 逐人归因(表象→本质→五维归因→应对) | 规则主体 + AI 润色 | `perMember[]`:memberId/role/surface/essence/fiveDim/action |
|
|
|
+| 四 | 家庭系统归因(五维地图+主因链+被证伪项) | 规则 | `familyMap`/`mainCauseChain`/`falsified[]` |
|
|
|
+| 五 | 应对方案 | 规则(三条通道,主因维结构性动作) | `channels[]` |
|
|
|
+| 六 | 12 周可检验目标 | 规则 | `goals[]`:指标/现在/12周目标 |
|
|
|
+| 七 | 教练提问序列(七层 Socratic) | **AI 叙述**(复用 AiGateway) | `coachQuestions[]` |
|
|
|
+| 八 | 问诊清单(已回收→v1.2) | 规则(复用 A2 JSON) | `questionnaire` 引用 |
|
|
|
+| 九 | 红线与转介 | 规则(RL01-RL08) | `redlines[]` |
|
|
|
+| 十 | 一句话收尾 | AI 叙述 | `closingLine` |
|
|
|
+
|
|
|
+**AI 混合策略(`AiGateway`,`service/AiGateway.java` 已含熔断/超时/PII/Fallback)**:
|
|
|
+
|
|
|
+| 内容 | 规则 | AI(`AiGateway`) | AI 失败 fallback |
|
|
|
+|------|------|-------------------|------------------|
|
|
|
+| 一 结论先行 | 事实注入 | `generateHealthPlan` 或新增归因 prompt | 规则模板句 + 事实(如「四条表象,两个主因维」) |
|
|
|
+| 三 逐人归因叙述 | 主体结构化 | 润色 | 结构化字段直接渲染 |
|
|
|
+| 七 教练提问序列 | 七层骨架 | `aiGateway.chatForAnalysis`(归因 Socratic prompt) | 内置七层模板问题 |
|
|
|
+| 十 一句话收尾 | — | AI | 内置收尾模板 |
|
|
|
+| 二/四/五/六/八/九 | 纯规则 | — | —(不依赖 AI) |
|
|
|
+
|
|
|
+> **关键合规**:红线(RL01-RL08)、儿童剂量「医生定」、品牌「不宣称功效」**全部由规则层硬注入**,AI 仅产叙述,不能覆盖合规文案。AI 关闭/熔断/超时 → 规则模板兜底,**流程永不阻断**(对齐 `generatePlanResult` 的 `buildSamplePlan` fallback 范式)。
|
|
|
+
|
|
|
+**报告存储**:`attribution_reports` 表 + `AttributionReport` 实体(`id/family_id/member_ids/plan_id/status(ANALYZING/READY/ADJUSTING/ADJUSTED/CONFIRMED/GENERATED/INVALIDATED)/report_json/redline_hits/intermediate_state/created_at/updated_at`)。状态机见 §4.2。
|
|
|
+
|
|
|
+### 3.6 B2:动态确认问卷条件路由
|
|
|
+
|
|
|
+**新端点**(`controller/SurveyController.java` 扩展):
|
|
|
+
|
|
|
+```
|
|
|
+POST /api/survey/attribution/confirm-questions
|
|
|
+Body: { familyId, memberId, attributionId, answeredSummary? }
|
|
|
+Resp: { templateId, questions: [ {id,code,group,title,type,options,unit,required,riskLevel,refRange} ] }
|
|
|
+```
|
|
|
+
|
|
|
+**逻辑**:
|
|
|
+1. 取 `survey_templates` 中 `target='family_attribution'` 的模板 `questionsJson`(A2 结构)。
|
|
|
+2. 构造 context:`memberRole/gender/ageBand`(来自 `family_members` + `family_relationships`,E10)+ 归因中间态 `intermediateState`(来自 `attribution_reports.intermediate_state`,B1 产出)+ 已作答摘要。
|
|
|
+3. `ConditionEvaluatorService.evaluate(question.condition, context)` 逐条求值,`condition==null` 恒发;命中返回子集。
|
|
|
+4. 按对象过滤:仅下发该对象(group 匹配)+ 家庭级(四人/全家)题目。
|
|
|
+
|
|
|
+**`ConditionEvaluatorService`**(新建):
|
|
|
+
|
|
|
+```java
|
|
|
+public interface ConditionEvaluatorService {
|
|
|
+ boolean evaluate(JsonNode condition, Map<String,Object> context);
|
|
|
+ Map<String,Object> buildContext(Long familyId, Long memberId, Long attributionId);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+> 求值器归属:**`ConditionEvaluatorService` 独立服务**(非归因引擎内),供 B2 路由与 B3 回填共用;关联归因中间态通过 `attribution_reports.intermediate_state` 读取。
|
|
|
+
|
|
|
+### 3.7 B3:`AttributionAdjustService` 自然语言调整编排
|
|
|
+
|
|
|
+**接口**:
|
|
|
+
|
|
|
+```java
|
|
|
+public interface AttributionAdjustService {
|
|
|
+ /** 对话式调整:用户消息 → AI 回答 + 结构化回填(可空增量) */
|
|
|
+ AdjustResult adjust(Long attributionId, Long userId, String message, String conversationId);
|
|
|
+ /** 用户确认调整:回填生效 → 报告 CONFIRMED;若有已生成方案则作废重生成 */
|
|
|
+ AdjustResult confirm(Long attributionId, Long userId);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**接口**:`POST /api/attribution/adjust`(body:`attributionId/userId/message/conversationId`),`POST /api/attribution/confirm-adjust`(body:`attributionId`)。
|
|
|
+
|
|
|
+**数据流**(复用 `AIChatController` + `AiGateway`):
|
|
|
+1. 组装归因上下文(`attribution_reports.report_json` + 成员数据 + 红线 RL08 提示词),注入**归因教练 prompt**(七层 Socratic:澄清→探因→假设→证据→替代→行动→复盘)。
|
|
|
+2. `aiGateway.chat(query, userId, conversationId, inputs)` 对话。
|
|
|
+3. 解析 AI 回答中的**结构化结论标记**(如 `[ATTR_DELTA]{"ironLow":false,"l.reuteriAbnormal":false}`)→ 回填 `attribution_reports.intermediate_state` 与报告结构化字段。
|
|
|
+4. **红线校验**:若回填命中 RL02(自伤/拒学/持续失眠)→ 报告状态置 `ADJUSTED` 但标记 `redline_terminate=true` + 转介信息露出 + 家长确认(E4 弱提示,不阻断但需家长签名确认),**流程不自动终止**(E4 已拍板弱提示)。
|
|
|
+5. 若已有 `plan_id` 关联且 `status=GENERATED` → 作废置 `INVALIDATED`(⑤ 失效重生成,对齐状态机「④ 结论再变 → ⑤ Invalidated」)。
|
|
|
+
|
|
|
+### 3.8 B4:全家干预方案 7 章生成
|
|
|
+
|
|
|
+**接口**:
|
|
|
+
|
|
|
+```java
|
|
|
+public interface AttributionPlanService {
|
|
|
+ /** 归因结论(attribution_id) → planJson(7章结构),存 health_plans.plan_json(plan_type=family_attribution_plan) */
|
|
|
+ String generateFamilyPlan(Long attributionId, Long userId);
|
|
|
+ /** 按层级提取动作表,供⑥筛选确认成任务(阶段三复用) */
|
|
|
+ List<ActionItem> extractActionTable(String planJson, String level);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**planJson 7 章 schema**(对齐《全家干预方案 v1.1》骨架 + B 模板 4 属性):
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "schemaVersion": "1.0",
|
|
|
+ "planType": "family_attribution_plan",
|
|
|
+ "header": {
|
|
|
+ "summary": "一句话摘要", "scope": ["妈妈","女儿","儿子"],
|
|
|
+ "dataSources": ["谷禾菌群3份","DAN2份","父亲进阶方案(背景)"],
|
|
|
+ "relation": "归因报告负责定位+转介;本方案负责可执行的产品+营养+生活方式",
|
|
|
+ "redlines": ["RL01...","RL03...","RL05..."] // 红线前置,先读
|
|
|
+ },
|
|
|
+ "chapters": [
|
|
|
+ { "no": 1, "title": "三人干预相关数据速览",
|
|
|
+ "rows": [ { "indicator":"双歧杆菌属%", "mom":"0.504(偏低)", "daughter":"0.074(极低)", "son":"1.208(相对正常)", "meaning":"妈妈、女儿重点补双歧" } ] },
|
|
|
+ { "no": 2, "title": "主线产品:作用原理·合规口径·用法",
|
|
|
+ "products": [ { "name":"Enagic还原水", "brand":"Enagic", "what":"电解还原水…",
|
|
|
+ "why":"三人都存在水合与冲泡介质需求", "how":"日常直饮+做饭+冲泡(≤40℃)",
|
|
|
+ "compliance":["RL05","RL09"] } ] },
|
|
|
+ { "no": 3, "title": "分人方案",
|
|
|
+ "members": [ { "member":"妈妈", "direction":"减负担+通吸收",
|
|
|
+ "actions": [ { "level":"P0", "action":"查血不自行补铁", "product":null,
|
|
|
+ "how":"血常规+血清铁+铁蛋白", "reviewHook":"4-8周复查" },
|
|
|
+ { "level":"P1", "action":"降胆汁酸", "product":"厨房改造+PHGG",
|
|
|
+ "how":"油炸/外食≤1次/周", "reviewHook":"12周复测<95" } ] } ] },
|
|
|
+ { "no": 4, "title": "家庭厨房与饮水改造(共享基座)",
|
|
|
+ "channels": [ { "channel":"饮水通道", "minStep":"全屋切换还原水", "owner":"妈妈", "reviewHook":"一周饮水量自评" } ] },
|
|
|
+ { "no": 5, "title": "12周执行节奏与可检验目标",
|
|
|
+ "weeks": [ { "week":"第1-2周", "family":"还原水入户", "mom":"查血", "daughter":"DAN复测", "son":"儿科查铁锌" } ],
|
|
|
+ "goals": [ { "indicator":"女儿双歧/多样性/大肠杆菌", "now":"0.074%/39/15.04%", "target":"双歧≥0.19%;多样性↑;大肠杆菌↓" } ] },
|
|
|
+ { "no": 6, "title": "红线与转介(再强调)", "redlines": ["RL01","RL03","RL04","RL05","RL06","RL09","RL10"] },
|
|
|
+ { "no": 7, "title": "附录:产品选购与用法提示",
|
|
|
+ "appendix": [ { "product":"瓜尔豆益生元PHGG", "select":"食品级部分水解瓜尔胶", "use":"5g/天起", "pitfall":"不与抗生素同服" } ] }
|
|
|
+ ]
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+**结构要点(继承源模板 4 属性)**:
|
|
|
+1. **分级动作**:`level ∈ {P0(先做/多为就医转介), P1(本周), daily(日常), P2}` —— 天然对应流程⑥的「筛选确认成任务」;
|
|
|
+2. **复盘钩子**:每动作带 `reviewHook`(可验证目标,如「一周≥5天按纸上时间做」);
|
|
|
+3. **先共享基座后精准**:第 4 章(家庭共享)先于第 3 章(分人)?——源文档实际顺序是 三(分人) 在 四(共享) 前,但**执行顺序**强调「先共享基座后精准」;设计上:`chapters[3]=分人方案` 在前(展示顺序),但**第 4 章共享基座标注为「先做」**,且 `header.order` 注明「先共享基座后精准」,避免展示顺序与执行顺序混淆;
|
|
|
+4. **红线前置**:`header.redlines` + `chapters[5]`(第 6 章)再强调;生成时注入 RL01-RL10 文案(A4)。
|
|
|
+
|
|
|
+**生成函数数据流**(归因结论 → planJson):
|
|
|
+```
|
|
|
+attribution_reports(report_json + intermediate_state)
|
|
|
+ → ① 提取三人归因结论(五维/主因链/指标)
|
|
|
+ → ② 产品映射(品牌级: Enagic还原水/姜黄、PHGG、益生菌株名) —— 由规则表驱动,不调 AI
|
|
|
+ → ③ 分人×层级动作表(规则模板 + 指标驱动开关)
|
|
|
+ → ④ 共享基座通道(4通道) + 12周节奏 + 附录
|
|
|
+ → ⑤ 红线前置注入(RL01-RL10, E7 品牌级口径)
|
|
|
+ → ⑥ planJson 落 health_plans(plan_type=family_attribution_plan)
|
|
|
+```
|
|
|
+
|
|
|
+> **E6 人范围**:`generateFamilyPlan` 仅处理 `memberIds=[妈妈,女儿,儿子]`;爸爸不生成(另有进阶方案),若传入爸爸 memberId 直接跳过并记 warning。**E7 品牌级**:产品名直接展示品牌(Enagic 还原水/姜黄、PHGG、长双歧 BB536 等),但 `compliance` 强制带 RL05「不宣称治疗」+ RL03「儿童剂量医生定」。
|
|
|
+
|
|
|
+### 3.9 类图(Mermaid classDiagram)
|
|
|
+
|
|
|
+```mermaid
|
|
|
+classDiagram
|
|
|
+ class HealthPlan {
|
|
|
+ +Long id
|
|
|
+ +Long familyId
|
|
|
+ +String memberIds
|
|
|
+ +String dimensions
|
|
|
+ +String planJson
|
|
|
+ +String status
|
|
|
+ +String planType "A1: health_plan/family_attribution_plan"
|
|
|
+ +Integer totalDays
|
|
|
+ }
|
|
|
+ class SurveyTemplate {
|
|
|
+ +Long id
|
|
|
+ +String title
|
|
|
+ +String dimension
|
|
|
+ +String questionsJson "A2: 扩展JSON(schemaVersion/target/questions)"
|
|
|
+ +String cycleType
|
|
|
+ }
|
|
|
+ class SurveyRecord {
|
|
|
+ +Long id
|
|
|
+ +Long familyId
|
|
|
+ +Long memberId
|
|
|
+ +Long templateId
|
|
|
+ +String answersJson
|
|
|
+ }
|
|
|
+ class AttributionNormValue {
|
|
|
+ +Long id
|
|
|
+ +String normType
|
|
|
+ +Long definitionId
|
|
|
+ +String code
|
|
|
+ +String memberAgeBand
|
|
|
+ +String gender
|
|
|
+ +BigDecimal p5
|
|
|
+ +BigDecimal p25
|
|
|
+ +BigDecimal p50
|
|
|
+ +BigDecimal p75
|
|
|
+ +BigDecimal p95
|
|
|
+ +String normVersion
|
|
|
+ +String source
|
|
|
+ }
|
|
|
+ class AttributionReport {
|
|
|
+ +Long id
|
|
|
+ +Long familyId
|
|
|
+ +String memberIds
|
|
|
+ +Long planId
|
|
|
+ +String status "ANALYZING/READY/ADJUSTING/ADJUSTED/CONFIRMED/GENERATED/INVALIDATED"
|
|
|
+ +String reportJson "十节结构"
|
|
|
+ +String redlineHits
|
|
|
+ +String intermediateState "供B2/B3"
|
|
|
+ }
|
|
|
+ class AttributionRedlines {
|
|
|
+ <<final>>
|
|
|
+ +List~Redline~ all()
|
|
|
+ +List~Redline~ byLevel(Level)
|
|
|
+ }
|
|
|
+ class AiGateway {
|
|
|
+ +Map chat(query,userId,conversationId,inputs)
|
|
|
+ +Map chatForAnalysis(query,userId,conversationId,inputs)
|
|
|
+ +String generateHealthPlan(inputs)
|
|
|
+ }
|
|
|
+ class AIChatController {
|
|
|
+ +Result sendMessage(userId,params)
|
|
|
+ }
|
|
|
+ class ConditionEvaluatorService {
|
|
|
+ <<interface>>
|
|
|
+ +boolean evaluate(JsonNode condition, Map context)
|
|
|
+ +Map buildContext(familyId,memberId,attributionId)
|
|
|
+ }
|
|
|
+ class AttributionAnalysisService {
|
|
|
+ <<interface>>
|
|
|
+ +AttributionReportVO analyze(AttributionInput input)
|
|
|
+ +AttributionReportVO getReport(attributionId)
|
|
|
+ +AttributionReportVO recompute(attributionId, fieldDeltas)
|
|
|
+ }
|
|
|
+ class AttributionAnalysisServiceImpl {
|
|
|
+ +analyze(input)
|
|
|
+ -runPipeline(input)
|
|
|
+ }
|
|
|
+ class NormCompareEngine { +compare(input) List~IndicatorReading~ }
|
|
|
+ class IndicatorInterpretEngine { +interpret(readings) Map }
|
|
|
+ class FiveDimAttributionEngine { +attribute(interpreted) Map }
|
|
|
+ class MainCauseChainEngine { +chain(fiveDim) List~Link~ }
|
|
|
+ class FalsificationEngine { +falsify(chain) List~Falsified~ }
|
|
|
+ class AttributionNarrativeService { +narrate(sections, readings) String }
|
|
|
+ class AttributionAdjustService {
|
|
|
+ <<interface>>
|
|
|
+ +AdjustResult adjust(attributionId,userId,message,conversationId)
|
|
|
+ +AdjustResult confirm(attributionId,userId)
|
|
|
+ }
|
|
|
+ class AttributionAdjustServiceImpl
|
|
|
+ class AttributionPlanService {
|
|
|
+ <<interface>>
|
|
|
+ +String generateFamilyPlan(attributionId,userId)
|
|
|
+ +List~ActionItem~ extractActionTable(planJson,level)
|
|
|
+ }
|
|
|
+ class AttributionPlanServiceImpl
|
|
|
+ class SurveyController {
|
|
|
+ +Result confirmQuestions(params,userId,currentMemberId)
|
|
|
+ +Result submit(params,userId,currentMemberId)
|
|
|
+ }
|
|
|
+ class AttributionController {
|
|
|
+ +Result analyze(params,userId,familyId)
|
|
|
+ +Result confirmQuestions(params,userId,familyId)
|
|
|
+ +Result adjust(params,userId,familyId)
|
|
|
+ +Result generatePlan(params,userId,familyId)
|
|
|
+ }
|
|
|
+
|
|
|
+ AttributionAnalysisServiceImpl --> NormCompareEngine
|
|
|
+ AttributionAnalysisServiceImpl --> IndicatorInterpretEngine
|
|
|
+ AttributionAnalysisServiceImpl --> FiveDimAttributionEngine
|
|
|
+ AttributionAnalysisServiceImpl --> MainCauseChainEngine
|
|
|
+ AttributionAnalysisServiceImpl --> FalsificationEngine
|
|
|
+ AttributionAnalysisServiceImpl --> AttributionNarrativeService
|
|
|
+ AttributionNarrativeService ..> AiGateway
|
|
|
+ AttributionAnalysisServiceImpl ..> AttributionNormValue
|
|
|
+ AttributionAnalysisServiceImpl ..> AttributionRedlines
|
|
|
+ AttributionAnalysisServiceImpl ..> SurveyRecord
|
|
|
+ AttributionAdjustServiceImpl ..> AttributionReport
|
|
|
+ AttributionAdjustServiceImpl ..> AIChatController
|
|
|
+ AttributionAdjustServiceImpl ..> AttributionRedlines
|
|
|
+ AttributionPlanServiceImpl ..> AttributionReport
|
|
|
+ AttributionPlanServiceImpl ..> HealthPlan
|
|
|
+ AttributionPlanServiceImpl ..> AttributionRedlines
|
|
|
+ SurveyController ..> ConditionEvaluatorService
|
|
|
+ SurveyController ..> SurveyTemplate
|
|
|
+ AttributionController ..> AttributionAnalysisService
|
|
|
+ AttributionController ..> AttributionAdjustService
|
|
|
+ AttributionController ..> AttributionPlanService
|
|
|
+ AttributionController ..> SurveyController
|
|
|
+ HealthPlan "1" --> "1" AttributionReport : planId
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 4. 程序调用流程(Program Call Flow)
|
|
|
+
|
|
|
+### 4.1 核心闭环 ③→⑤ 时序图(Mermaid sequenceDiagram)
|
|
|
+
|
|
|
+```mermaid
|
|
|
+sequenceDiagram
|
|
|
+ autonumber
|
|
|
+ actor U as 用户(家长)
|
|
|
+ participant C as AttributionController
|
|
|
+ participant S as SurveyController
|
|
|
+ participant AS as AttributionAnalysisService
|
|
|
+ participant CE as ConditionEvaluatorService
|
|
|
+ participant NE as NormCompareEngine
|
|
|
+ participant NV as AttributionNarrativeService
|
|
|
+ participant AI as AiGateway
|
|
|
+ participant AD as AttributionAdjustService
|
|
|
+ participant PS as AttributionPlanService
|
|
|
+ participant HP as HealthPlan
|
|
|
+
|
|
|
+ Note over U,HP: ② 动态确认问卷(B2)
|
|
|
+ U->>C: POST /api/survey/attribution/confirm-questions {familyId,memberId,attributionId}
|
|
|
+ C->>S: confirmQuestions(params)
|
|
|
+ S->>CE: buildContext(familyId,memberId,attributionId)
|
|
|
+ CE-->>S: context(role/ageBand/intermediateState)
|
|
|
+ S->>CE: evaluate(question.condition, context) 逐条
|
|
|
+ CE-->>S: 下发子集(仅该对象+家庭级)
|
|
|
+ S-->>U: questions[] (含riskLevel)
|
|
|
+
|
|
|
+ U->>S: POST /api/survey/submit {answersJson,riskHits}
|
|
|
+ S-->>C: 确认问卷已回收(→v1.2)
|
|
|
+
|
|
|
+ Note over U,HP: ③ 归因分析(B1)
|
|
|
+ U->>C: POST /api/attribution/analyze {familyId,memberIds,gutReportIds,danResultIds,confirmSurveyRecordId}
|
|
|
+ C->>AS: analyze(input)
|
|
|
+ AS->>NE: compare(input) 常模比对(attribution_norm_values/cognitive_norms)
|
|
|
+ NE-->>AS: IndicatorReading[](percentileBand/status)
|
|
|
+ AS->>AS: IndicatorInterpretEngine → FiveDimAttributionEngine → MainCauseChainEngine → FalsificationEngine
|
|
|
+ AS->>NV: narrate(sections, readings)
|
|
|
+ NV->>AI: chatForAnalysis(归因叙述prompt)
|
|
|
+ AI-->>NV: 叙述 或 null(熔断/超时)
|
|
|
+ NV-->>AS: 规则模板兜底(若null)
|
|
|
+ AS->>AS: 红线注入(RL01-RL08)
|
|
|
+ AS-->>U: AttributionReportVO{status:READY, reportJson, intermediateState}
|
|
|
+
|
|
|
+ Note over U,HP: ④ 自然语言调整(B3)
|
|
|
+ U->>C: POST /api/attribution/adjust {attributionId,message,conversationId}
|
|
|
+ C->>AD: adjust(attributionId,userId,message,conversationId)
|
|
|
+ AD->>AI: chat(归因Socratic prompt)
|
|
|
+ AI-->>AD: answer + [ATTR_DELTA]标记
|
|
|
+ AD->>AD: 解析回填intermediateState/report字段
|
|
|
+ AD->>AD: 红线校验(RL02自伤/拒学/持续失眠→标记建议专业评估+家长确认,E4)
|
|
|
+ AD->>AD: 若有GENERATED方案→置INVALIDATED
|
|
|
+ AD-->>U: AdjustResult{answer,fieldDeltas,redlineTerminate}
|
|
|
+ U->>C: POST /api/attribution/confirm-adjust {attributionId}
|
|
|
+ C->>AD: confirm(attributionId,userId)
|
|
|
+ AD-->>U: status=CONFIRMED
|
|
|
+
|
|
|
+ Note over U,HP: ⑤ 调整方案(B4)
|
|
|
+ U->>C: POST /api/attribution/plan {attributionId}
|
|
|
+ C->>PS: generateFamilyPlan(attributionId,userId)
|
|
|
+ PS->>PS: 归因结论→产品映射(品牌级,E7)+分人×层级动作+共享基座+12周+红线前置
|
|
|
+ PS->>HP: 存health_plans(plan_type=family_attribution_plan)
|
|
|
+ HP-->>U: planJson 7章
|
|
|
+```
|
|
|
+
|
|
|
+### 4.2 归因流程状态机(Mermaid stateDiagram,对齐 PRD §C)
|
|
|
+
|
|
|
+```mermaid
|
|
|
+stateDiagram-v2
|
|
|
+ [*] --> Submitted : ① 基本问卷提交
|
|
|
+ Submitted --> Pending : 归因中间态就绪(待确认问题)
|
|
|
+ Pending --> Confirmed : ② 确认问卷回收(含riskLevel命中标记)
|
|
|
+ Confirmed --> Analyzing : ③ 触发归因
|
|
|
+ Analyzing --> Ready : 规则引擎完成(常模比对→五维→主因链→被证伪)
|
|
|
+ Ready --> Adjusting : ④ 用户发起调整
|
|
|
+ Adjusting --> Adjusted : AI回填增量
|
|
|
+ Adjusted --> Confirmed : 用户确认(回填生效)
|
|
|
+ Confirmed --> Generated : ⑤ 生成调整方案(7章)
|
|
|
+ Generated --> TaskConfirmed : ⑥ 筛选确认成任务(阶段三)
|
|
|
+ Confirmed --> Invalidated : ④结论再变(作废⑤)
|
|
|
+ Generated --> Invalidated : 回退调整
|
|
|
+ Invalidated --> Adjusting : 重新调整
|
|
|
+ Ready --> Confirmed : 用户直接确认(跳过调整)
|
|
|
+ Analyzing --> Ready : mock兜底(AI失败不阻断)
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 5. Anything UNCLEAR(未确定 / 假设 / 未检索到)
|
|
|
+
|
|
|
+1. **⚠️ 菌群 710 条常模结构化源未检索到**:`health_gut_flora.populationLevel` 存单条百分位;知识库 `统一知识库.json` 仅有 `菌种综合信息`/`菌属功能库`/`报告指标分级标准`(142 指标标准范围/状态分级,非 p5-p95)。→ A3 设计为**可配置 JSON 导入**(`resources/norm/attribution_norm_gut_flora.json` 空壳),真实 710 条需业务侧/教练侧提供并填充后导入。**这是最大数据不确定性**,建议阶段一先落空壳 + 导入器,阶段二 B1 常模比对段对缺失常模返回「待补」状态(不猜值,对齐源文档「待补」项不猜测)。
|
|
|
+2. **五维归因的权重/阈值未在源文档给出数值**:文档只给「教练定位用相对刻度(0-100)」与「主因维」概念,未给分维权重表。→ 设计为 `AttributionWeights` 常量类(规则层集中配置),默认值按源文档语义(行/身 双主因示例),**具体数值待教练侧确认**(阶段四 D1 或后续校准),当前用保守默认。
|
|
|
+3. **归因 prompt 是否在 LangGraph 有对应图**:`AiGateway.generateHealthPlan` 走 `/api/v1/health/plan/generate`(通用健康方案);归因叙述需**新增归因专用 prompt 模板**(`PromptConfig` 注入),LangGraph 侧图/端点是否已有 **未检索到**。→ B1 设计为:优先走 `chatForAnalysis`(已有 `/api/v1/chat`),无归因图时用规则模板兜底;**不强依赖 LangGraph 新增端点**。
|
|
|
+4. **Q16 21 张照片上传**:`health_gut_flora` 无照片通道;Q16 走 `file_record`/OSS 复用现有上传链路,**照片-归因关联**属阶段三 C3 数据源桥接,本设计仅定义 `photoBatch` 类型 + `required=false`(负担最重,建议放宽为「尽量」)。
|
|
|
+5. **角色确认(E10)**:`family_relationships`(generation/relationship_key) 是角色来源,但源文档承认「按姓名年龄推断、未见关系证明」是盲区。→ B2 context 构建用 `family_relationships`,**用户须主动确认角色**(前端阶段三加确认步),本设计不默认系统推断。
|
|
|
+6. **合规后置边界**:E12 要求仅 mock/测试数据。→ 全流程加配置开关 `app.attribution.enabled`(默认 false,仅测试环境开启),`AttributionController` 入口统一校验;真实用户放行 = 阶段四 D1。
|
|
|
+7. **A4 表 vs 常量类取舍**:默认常量类(零数据风险);`attribution_redlines` 表为管理端可编辑备选,**阶段一不建表**(除非需求方明确要运营可改)。
|
|
|
+8. **主工作树排除**:`E:/cfc` 本机主工作树不作为派发目标;本设计文档单独 `git add <该文件>` 提交,不用 `add -A`(他人未提交改动,见 §7 纪律)。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+# Part B:任务分解(Task Decomposition)
|
|
|
+
|
|
|
+## 6. 所需第三方包(Required Packages)
|
|
|
+
|
|
|
+阶段一/二**纯后端增量,无新增第三方依赖**——全部复用现有栈:
|
|
|
+- Spring Boot 2.x / Spring Web / `spring-boot-starter-jdbc`
|
|
|
+- MyBatis-Plus(`com.baomidou:mybatis-plus-boot-starter`)——实体/Mapper 范式已用
|
|
|
+- Jackson(`com.fasterxml.jackson.core:jackson-databind`)——JSON 解析
|
|
|
+- Lombok —— `@Data`/`@TableName`/`@TableField`
|
|
|
+- 无前端依赖(阶段三才涉及前端框架/组件)
|
|
|
+
|
|
|
+## 7. 任务列表(Task List,按实现顺序 + 依赖)
|
|
|
+
|
|
|
+> 建议派发实例:后端 `lan-248`(DB/Java 后端);前端 `lan-204`;综合 `pub-iwintrue`。阶段一/二**全部后端**,建议 `lan-248` 主责,`pub-iwintrue` 协审(只读 review)。
|
|
|
+
|
|
|
+### 阶段一(数据地基,无依赖,可独立上线)
|
|
|
+
|
|
|
+#### ATT-S1-A1|health_plans.plan_type 迁移(G5)
|
|
|
+- **目标文件**:`cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java`、`cfc-backend/src/main/resources/schema.sql`、`cfc-backend/src/main/java/com/etotem/cfc/entity/HealthPlan.java`
|
|
|
+- **实现要点**:
|
|
|
+ 1. `runMigrations()` 末尾(`createBodyFatScaleRecordsTable();` 之后,`DatabaseInitializer.java:3341`)追加 `migrateHealthPlansPlanType()`;
|
|
|
+ 2. 用 `ensureColumn("health_plans","plan_type","VARCHAR(32) DEFAULT 'health_plan' COMMENT '...'")`(**幂等**,列已存在跳过);
|
|
|
+ 3. 同步 `schema.sql:5082` health_plans 建表加 `plan_type` 行;
|
|
|
+ 4. `HealthPlan.java` 加 `@TableField("plan_type") private String planType;`
|
|
|
+- **验收标准**:① 启动后 `SHOW COLUMNS FROM health_plans` 含 `plan_type`;② 存量行默认 `health_plan`;③ 实体映射 `getPlanType()/setPlanType()` 可用;④ `git diff` 仅涉 3 文件。
|
|
|
+- **派发**:`lan-248`
|
|
|
+
|
|
|
+#### ATT-S1-A2|SurveyTemplate.questionsJson schema 扩展 + 第八章 16 条 JSON 样例(G2 半)
|
|
|
+- **目标文件**:`cfc-backend/src/main/resources/`(或 `config/`)下新增问题模板种子(`family_attribution_questions.json` 或经 `runMigrations()` 幂等写入 `survey_templates` 一行 `target='family_attribution'`)、`service/ConditionEvaluatorService.java`(接口+impl,含 ops 求值)
|
|
|
+- **实现要点**:
|
|
|
+ 1. 按 §3.2 定义扩展 JSON 结构(`schemaVersion/target/questions`,字段 `id/code/group/title/type/options/unit/required/condition/riskLevel/refRange`);
|
|
|
+ 2. 写入 16 条(39 采集项)完整 JSON 样例(§3.2 代码块,可直接入库 `questionsJson`);
|
|
|
+ 3. `ConditionEvaluatorService`:`evaluate(JsonNode,Map)` 支持 `==/!=/>/>=/</<=/contains/in/exists/anyOf`;`buildContext` 组装 role/ageBand/intermediateState;
|
|
|
+ 4. 幂等种子:`survey_templates` 按 `title`/`target` 判重,避免重复插入。
|
|
|
+- **验收标准**:① 模板 `questionsJson` 解析为 §3.2 结构;② `ConditionEvaluatorService.evaluate` 对 `{var:"l.reuteriAbnormal",op:"==",val:true}` 在 context 命中时返回 true;③ 单测覆盖 6 种操作符 + null 恒真 + 未知 var 保守 false。
|
|
|
+- **派发**:`lan-248`
|
|
|
+
|
|
|
+#### ATT-S1-A3|attribution_norm_values 常模表 + 可配置 JSON 导入(G10)
|
|
|
+- **目标文件**:`cfc-backend/src/main/java/com/etotem/cfc/entity/AttributionNormValue.java`、`cfc-backend/src/main/java/com/etotem/cfc/mapper/AttributionNormValueMapper.java`、`cfc-backend/src/main/resources/schema.sql`、`cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java`、`cfc-backend/src/main/resources/norm/attribution_norm_gut_flora.json`
|
|
|
+- **实现要点**:
|
|
|
+ 1. DDL(§3.3)幂等建表 + 唯一键 `uk_type_code_band_gender_ver`;
|
|
|
+ 2. Entity + Mapper(MyBatis-Plus 范式);
|
|
|
+ 3. 常模导入器:启动时读 `classpath:norm/attribution_norm_gut_flora.json`(空壳)幂等导入 `norm_type=gut_flora`;DAN 常模**优先复用** `cognitive_norms`/`health_norm_reference`,不重复建;
|
|
|
+ 4. `attribution_norm_gut_flora.json` 空壳模板含字段说明 + `{"placeholder":true,"note":"真实710条待业务侧填充"}`。
|
|
|
+- **验收标准**:① 表存在且唯一键生效;② 空壳导入不报错(0 行导入);③ 填充样例行后可查;④ `git diff` 仅涉上述文件。
|
|
|
+- **⚠️ 未检索到**:710 条真实常模数据源,本任务只交付导入器 + 空壳;真实数据为业务侧前置条件。
|
|
|
+- **派发**:`lan-248`
|
|
|
+
|
|
|
+#### ATT-S1-A4|合规红线模板(10 条,P0/P1/P2 分级 + E7 品牌级)(G9)
|
|
|
+- **目标文件**:`cfc-backend/src/main/java/com/etotem/cfc/constant/AttributionRedlines.java`(+ 可选 `entity/AttributionRedline.java`)
|
|
|
+- **实现要点**:
|
|
|
+ 1. 枚举 `Level{P0,P1,P2}` + 内部类 `Redline{code,level,title,message,scope}`;
|
|
|
+ 2. 10 条红线按 §3.4 明细硬编码(RL01-RL10),**E7 品牌级口径**(品牌露出不附带功效承诺)+ **E4 弱提示**(RL02 措辞改为「建议专业评估+转介信息露出+家长确认」而非「阻断」);
|
|
|
+ 3. `all()` / `byLevel(Level)` / `byScope(String)`;
|
|
|
+ 4. 单测:10 条完整、level 分布 P0×4/P1×4/P2×2、每条 message 非空且不含「治愈/根治」等禁词。
|
|
|
+- **验收标准**:① 10 条全覆盖;② 单测通过;③ `byLevel(P0)` 返回 4 条(RL01-RL04)。
|
|
|
+- **派发**:`lan-248`
|
|
|
+
|
|
|
+### 阶段二(核心闭环 ③→⑤,依赖阶段一 A1-A4)
|
|
|
+
|
|
|
+#### ATT-S2-B1|归因分析引擎 AttributionAnalysisService(G1/G7/G8 半)
|
|
|
+- **目标文件**:`service/AttributionAnalysisService.java`、`service/impl/AttributionAnalysisServiceImpl.java`、`service/attribution/{NormCompareEngine,IndicatorInterpretEngine,FiveDimAttributionEngine,MainCauseChainEngine,FalsificationEngine}.java`、`service/attribution/AttributionNarrativeService.java`、`entity/AttributionReport.java`、`mapper/AttributionReportMapper.java`、`dto/{AttributionInput,AttributionReportVO}.java`、`controller/AttributionController.java`(analyze)、`resources/schema.sql`(attribution_reports 建表)、`config/DatabaseInitializer.java`(建表)
|
|
|
+- **实现要点**:
|
|
|
+ 1. 5 段确定性 Pipeline(常模比对→指标解读→五维归因→主因链→被证伪项),纯 Java 无框架;
|
|
|
+ 2. `AttributionNarrativeService` 走 `AiGateway.chatForAnalysis`(或 `generateHealthPlan` 归因 prompt),AI null → 规则模板兜底(永不阻断);
|
|
|
+ 3. 红线 RL01-RL08 由规则层硬注入(非 AI);
|
|
|
+ 4. 报告存 `attribution_reports`(status + report_json + intermediate_state + redline_hits);
|
|
|
+ 5. `app.attribution.enabled` 开关(默认 false,仅 mock/测试数据,E12);
|
|
|
+ 6. 常模缺失 → 指标返回「待补」不猜值。
|
|
|
+- **验收标准**:① mock 4 人家庭跑通 analyze 产出十节结构;② AI 关闭时产出规则模板报告(不 null);③ 红线注入 RL01/RL02 存在;④ intermediate_state 含 `l.reuteriAbnormal/ironLow/hasDan/isFemaleTeen/hasDiabetes` 键;⑤ 状态 ANALYZING→READY。
|
|
|
+- **派发**:`lan-248`(主)+ `pub-iwintrue`(协审)
|
|
|
+
|
|
|
+#### ATT-S2-B2|动态确认问卷条件路由(G2)
|
|
|
+- **目标文件**:`controller/SurveyController.java`(扩展)、`service/ConditionEvaluatorService.java`(复用)、`dto/ConfirmQuestionVO.java`
|
|
|
+- **实现要点**:
|
|
|
+ 1. `POST /api/survey/attribution/confirm-questions`:按 familyId+memberId+attributionId 条件下发 16 条子集;
|
|
|
+ 2. `ConditionEvaluatorService.buildContext` 读 `attribution_reports.intermediate_state`;
|
|
|
+ 3. 按对象(group 匹配)+ 家庭级(四人/全家)过滤;
|
|
|
+ 4. `submit` 扩展接收 `riskHits`(Q11/12/13 命中标记,E4 弱提示不阻断)。
|
|
|
+- **验收标准**:① 女儿成员下发含 Q10-13;② 铁偏低时发 Q4;③ 无 DAN 时发 Q9;④ Q11/12/13 带 `riskLevel=high`。
|
|
|
+- **派发**:`lan-248`
|
|
|
+
|
|
|
+#### ATT-S2-B3|自然语言调整编排 AttributionAdjustService(G3)
|
|
|
+- **目标文件**:`service/AttributionAdjustService.java`、`service/impl/AttributionAdjustServiceImpl.java`、`dto/AdjustResult.java`、`controller/AttributionController.java`(adjust/confirm-adjust)、`config/PromptConfig.java`(归因 Socratic prompt 注入)
|
|
|
+- **实现要点**:
|
|
|
+ 1. 复用 `AIChatController` + `AiGateway` 对话,注入归因教练 prompt(七层 Socratic);
|
|
|
+ 2. 解析 `[ATTR_DELTA]{...}` 标记 → 回填 `attribution_reports` 结构化字段 + intermediate_state;
|
|
|
+ 3. 红线校验(RL02 命中 → 标记建议专业评估 + 转介信息露出 + 家长确认,E4 弱提示);
|
|
|
+ 4. 若关联方案 status=GENERATED → 置 INVALIDATED(⑤ 失效重生成);
|
|
|
+ 5. `confirm-adjust` 置 CONFIRMED。
|
|
|
+- **验收标准**:① 对话返回 answer + fieldDeltas;② 回填写库;③ 关联方案自动 INVALIDATED;④ RL02 命中输出 `redlineTerminate=true` 但流程不阻断(E4)。
|
|
|
+- **派发**:`lan-248`
|
|
|
+
|
|
|
+#### ATT-S2-B4|全家干预方案 7 章生成(G4/G5/G6 半)
|
|
|
+- **目标文件**:`service/AttributionPlanService.java`、`service/impl/AttributionPlanServiceImpl.java`、`dto/FamilyPlanVO.java`、`controller/AttributionController.java`(plan)、`config/DatabaseInitializer.java`(plan_type 已 A1 就绪)
|
|
|
+- **实现要点**:
|
|
|
+ 1. 归因结论 → 产品映射(品牌级 Enagic/PHGG/益生菌株名,E7);
|
|
|
+ 2. 分人×层级(P0/P1/日常/P2)×动作×产品×具体做法×复盘钩子 结构化;
|
|
|
+ 3. 先共享基座后精准(第 4 章标注「先做」+ header.order 注明);
|
|
|
+ 4. 红线前置(header.redlines + 第 6 章 RL01-RL10,A4 注入);
|
|
|
+ 5. 人范围 E6:仅妈妈/女儿/儿子,爸爸跳过;
|
|
|
+ 6. 存 `health_plans`(plan_type=family_attribution_plan)。
|
|
|
+- **验收标准**:① planJson 含 7 章 + header;② 每动作带 level/reviewHook;③ 产品名含品牌但 compliance 带 RL05「不宣称治疗」;④ 儿童动作带「医生定」无剂量数字;⑤ 不含爸爸成员。
|
|
|
+- **派发**:`lan-248`
|
|
|
+
|
|
|
+> **任务总数 = 8 个**(≤ 建议上限;阶段一 4 个无依赖、阶段二 4 个依赖阶段一)。每组任务文件分组按功能模块,未按单文件拆分。
|
|
|
+
|
|
|
+## 8. 共享知识(Shared Knowledge,跨任务约定)
|
|
|
+
|
|
|
+- **API 响应格式**:统一 `Result{code,data,message}`(`common/Result.java`);错误码 4001=付费墙/权限,4000=参数错。
|
|
|
+- **请求身份**:`@RequestAttribute("userId")` / `("familyId")` / `("currentMemberId")` / `("role")` 从 JWT 注入(与 `SurveyController`/`HealthPlanController` 一致)。
|
|
|
+- **迁移纪律**:所有 DDL 迁移**只经** `DatabaseInitializer.runMigrations()` 唯一入口,用 `ensureColumn`/`tableExists` 幂等;迁移编号**递增**(新=424,423 已占用);不同时用 `add -A` 卷入他会话未提交内容。
|
|
|
+- **合规红线(不可谈判)**:RL01-RL10 由规则层硬注入;AI 只产叙述;`app.attribution.enabled=false` 默认(E12 合规后置),真实用户放行 = 阶段四 D1。
|
|
|
+- **常模口径**:常模缺失 → 指标「待补」不猜值;DAN 常模优先复用 `cognitive_norms`/`health_norm_reference`;菌群 710 常模落 `attribution_norm_values`(可配置 JSON 导入)。
|
|
|
+- **日期**:ISO 8601 UTC 存储;`Date` 类型沿用现有 `DATETIME DEFAULT CURRENT_TIMESTAMP`。
|
|
|
+- **JSON 字段**:`questionsJson`/`planJson`/`report_json` 均为 TEXT 列,schema 走 JSON 约定(Jackson),不改表结构(除 A1/A3 明确新增列/表)。
|
|
|
+- **人范围(E6)**:成员角色经 `family_relationships` 确认;爸爸链外不生成方案。
|
|
|
+
|
|
|
+## 9. 任务依赖图(Task Dependency Graph)
|
|
|
+
|
|
|
+```mermaid
|
|
|
+graph TD
|
|
|
+ A1[ATT-S1-A1 plan_type迁移] --> B1[ATT-S2-B1 归因引擎]
|
|
|
+ A1 --> B4[ATT-S2-B4 7章方案]
|
|
|
+ A2[ATT-S1-A2 questionsJson+条件求值] --> B1
|
|
|
+ A2 --> B2[ATT-S2-B2 动态确认路由]
|
|
|
+ A2 --> B3[ATT-S2-B3 调整编排]
|
|
|
+ A3[ATT-S1-A3 常模表+导入] --> B1
|
|
|
+ A4[ATT-S1-A4 红线模板] --> B1
|
|
|
+ A4 --> B3
|
|
|
+ A4 --> B4
|
|
|
+ B1 --> B2
|
|
|
+ B1 --> B3
|
|
|
+ B1 --> B4
|
|
|
+ B2 --> B3
|
|
|
+ B3 --> B4
|
|
|
+```
|
|
|
+
|
|
|
+> 依赖说明:阶段一 A1-A4 无相互依赖(可并行);阶段二 B1 依赖 A1-A4;B2 依赖 A2+A1(归因中间态落库);B3 依赖 A2+A4+B1;B4 依赖 A1+A4+B1(+B3 结论确认)。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+# 附录:阶段三/四 建议性边界(供后续参考,暂不设计)
|
|
|
+
|
|
|
+> 本设计只覆盖阶段一(数据地基)+ 阶段二(核心闭环 ③→⑤)。以下为阶段三/四**建议边界**,供主理人与后续设计参考,**非本次派发范围**。
|
|
|
+
|
|
|
+## 阶段三:任务化 + 前端(⑥ + ①-⑥ 编排)
|
|
|
+- **C1** 方案→任务扩展:`HealthPlanController.confirm` 增加 P0 强制不可跳过 + 任务携带复盘钩子/时间窗/优先级(复用 `planTasks`→`List<Task>`)。
|
|
|
+- **C2** 前端 6 步向导:`family-attribution/` 目录(basic-survey / confirm-survey / result / adjust / plan / tasks),复用 `SurveyTemplate` 渲染 + `ai/chat.vue` 对话 + `planJson` 展示;含角色确认步(E10)。
|
|
|
+- **C3** 数据源桥接:菌群/DAN/常模按家庭成员绑定到归因方案(`health_gut_flora`/`dan_assessment_results` → `attribution_reports`)。
|
|
|
+
|
|
|
+## 阶段四:合规 + 验证(真实用户放行唯一闸门)
|
|
|
+- **D1** 合规收口:① E4 高风险项复审(弱提示 + 转介信息露出 + 家长确认);② E7 品牌级展示广告法/医疗合规审查;③ 未成年人敏感数据保护 + 隐私声明。**真实用户放行 = D1 完成日**(E12)。
|
|
|
+- **D2** 端到端验证:mock 4 人家庭(含 1 父亲无 DAN、1 青春期女儿)跑通 ①→⑥。
|
|
|
+- **D3** 教师复核链可选接入(`pending-review/*`)。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+*文档说明:本设计为增量设计(阶段一+二),只读调研产物,不含业务代码改动;已标注未检索到的数据源(菌群 710 常模结构化源、LangGraph 归因图端点)。*
|