Browse Source

merge(S-cfc-192): 同步 origin/cfclub(S-cfc-194 等)后推送

Sisyphus 2 hours ago
parent
commit
5da32aeb43

+ 295 - 0
cfc-backend/src/test/java/com/etotem/cfc/unit/TeacherDimensionScoreContractTest.java

@@ -0,0 +1,295 @@
+package com.etotem.cfc.unit;
+
+import com.etotem.cfc.controller.family.ManualDimensionScoreController;
+import com.etotem.cfc.dto.ManualScoreSubmitDTO;
+import com.etotem.cfc.entity.FamilyMember;
+import com.etotem.cfc.entity.FiveDimensionScore;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
+import com.etotem.cfc.mapper.FiveDimensionScoreMapper;
+import com.etotem.cfc.mapper.FiveDimensionSelfCheckMapper;
+import com.etotem.cfc.service.FiveDimensionSelfCheckService;
+import com.etotem.cfc.service.impl.ManualDimensionScoreServiceImpl;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * 契约级单测:成长规划师五维变化录入/打分(能力键 {@code teacher.dimension.score})。
+ *
+ * <p><b>需求/设计锚点(EVD-10 契约,2026-11-07)</b>:
+ * {@code docs/superpowers/specs/2026-11-07-planner-zone-contract.md} SC-T-30/31/32
+ * + {@code docs/superpowers/api/API_REFERENCE.md} §4.57 成长规划师域 + sc-key-map.md
+ * (SC-T-30/31/32 均「未跑通」,本测试为其补契约 JUnit 证据)。</p>
+ *
+ * <p><b>被校验的契约行为</b></p>
+ * <ul>
+ *   <li>SC-T-30 测评录入触发五维维度变化:{@code DanAssessmentController /result/record}
+ *       → {@code AssessmentService.recordResult} → {@link com.etotem.cfc.service.DimensionScoreService#refreshFromDanAssessment}</li>
+ *   <li>SC-T-31 手动维度打分覆盖/调整:{@code ManualDimensionScoreController /api/family/manual-score/*}
+ *       (submit/latest/history)→ 落 {@code five_dimension_scores}(source_type=manual)</li>
+ *   <li>SC-T-32 五维自评录入与变化:{@code FiveDimensionSelfCheckController /api/family/self-check/*}
+ *       (questions/submit/latest/history/status/ignore/generate-plan/trend-analysis/ai-generate-questions)
+ *       → <b>自评分数应桥接落五维变化</b>(契约原文「五维自评 → 变化落 EnergyController」)</li>
+ * </ul>
+ *
+ * <p><b>实现现状(origin/cfclub 实测核实)</b>:
+ * SC-T-30/31 载体已落地(测评桥接钩子、手动打分 controller+service 均存在);
+ * <b>SC-T-32 的「自评 → 五维变化桥接」未落地</b>——{@code FiveDimensionSelfCheckService}
+ * 仅声明 {@code selfCheckMapper/familyMemberMapper/wuxingSourcingService/onboardingService/
+ * ignoreMapper/selfCheckAnalysisService/selfCheckTrendService} 等字段,<b>无任何五维变化落点
+ * (DimensionScoreService / EnergyService / FiveDimensionScoreMapper)引用</b>;
+ * schema.sql 中 {@code five_dimension_scores.source_type} 枚举注释
+ * (periodic/task/assessment/manual)也<b>无 selfcheck 来源</b>。故 SC-T-32 桥接用例预期红。</p>
+ *
+ * <p><b>测试范围</b>:纯契约级单测,Mockito + 反射结构探测,无 Spring / 无 DB / 无外部服务。</p>
+ */
+class TeacherDimensionScoreContractTest {
+
+    /** SC-T-31 五维编码(契约口径 body/mind/wisdom/action/wealth) */
+    private static final String[] DIMENSIONS = {"body", "mind", "wisdom", "action", "wealth"};
+
+    // ==================== SC-T-31 行为级绿测试的 mock ====================
+
+    @Mock
+    private FiveDimensionScoreMapper fiveDimensionScoreMapper;
+
+    @Mock
+    private FiveDimensionSelfCheckMapper selfCheckMapper;
+
+    @Mock
+    private FamilyMemberMapper familyMemberMapper;
+
+    @InjectMocks
+    private ManualDimensionScoreServiceImpl manualScoreService;
+
+    @BeforeEach
+    void setUp() {
+        MockitoAnnotations.openMocks(this);
+    }
+
+    // ------------------------------------------------------------------
+    // 正常流-1(SC-T-31):手动维度打分端点 submit/latest/history 应存在
+    // ------------------------------------------------------------------
+
+    @Test
+    @DisplayName("正常流-1(SC-T-31):ManualDimensionScoreController 应暴露 submit/latest/history 端点")
+    void manualScore_controllerExposesSubmitLatestHistory() {
+        Class<?> clazz = ManualDimensionScoreController.class;
+        String[] requiredPaths = {"/submit", "/latest", "/history"};
+        for (String path : requiredPaths) {
+            boolean found = false;
+            for (Method m : clazz.getDeclaredMethods()) {
+                org.springframework.web.bind.annotation.PostMapping pm =
+                        m.getAnnotation(org.springframework.web.bind.annotation.PostMapping.class);
+                if (pm != null && java.util.Arrays.asList(pm.value()).contains(path)) {
+                    found = true;
+                    break;
+                }
+            }
+            assertTrue(found,
+                    "[teacher.dimension.score] ManualDimensionScoreController 应暴露 POST "
+                            + path + " 端点(契约 SC-T-31 手动维度打分覆盖/调整)。");
+        }
+    }
+
+    // ------------------------------------------------------------------
+    // 正常流-2(SC-T-31):手动打分 submit 应落 five_dimension_scores(source_type=manual)
+    // ------------------------------------------------------------------
+
+    @Test
+    @DisplayName("正常流-2(SC-T-31):手动打分 submit 应写 five_dimension_scores(source_type=manual)")
+    void manualScore_submitPersistsManualSourceRecords() {
+        FamilyMember member = new FamilyMember();
+        member.setId(88001L);
+        member.setFamilyId(9001L);
+        member.setNickname("契约之子");
+
+        Map<String, Integer> scores = new LinkedHashMap<>();
+        scores.put("body", 80);
+        scores.put("mind", 60);
+        scores.put("wisdom", 70);
+        scores.put("action", 50);
+        scores.put("wealth", 90);
+
+        ManualScoreSubmitDTO dto = new ManualScoreSubmitDTO();
+        dto.setMemberId(88001L);
+        dto.setScores(scores);
+
+        when(familyMemberMapper.selectById(88001L)).thenReturn(member);
+
+        manualScoreService.submit(dto, 10001L);
+
+        ArgumentCaptor<FiveDimensionScore> captor = ArgumentCaptor.forClass(FiveDimensionScore.class);
+        verify(fiveDimensionScoreMapper, times(5)).insert(captor.capture());
+        Set<String> codes = new HashSet<>();
+        for (FiveDimensionScore record : captor.getAllValues()) {
+            codes.add(record.getDimensionCode());
+            assertTrue("manual".equals(record.getSourceType()),
+                    "[teacher.dimension.score] 手动打分应落 source_type=manual,实际="
+                            + record.getSourceType() + "(契约 SC-T-31)。");
+        }
+        for (String dim : DIMENSIONS) {
+            assertTrue(codes.contains(dim),
+                    "[teacher.dimension.score] 手动打分应覆盖五维编码 " + dim + "(契约 SC-T-31)。");
+        }
+        // 自检记录同步落表(复用 five_dimension_self_checks 记录手动填分)
+        verify(selfCheckMapper, times(1)).insert(any());
+    }
+
+    // ------------------------------------------------------------------
+    // 正常流-3(SC-T-30):测评录入应触发五维维度变化(桥接钩子存在)
+    // ------------------------------------------------------------------
+
+    @Test
+    @DisplayName("正常流-3(SC-T-30):DimensionScoreService 应提供 refreshFromDanAssessment 桥接")
+    void dimensionScoreService_exposesDanAssessmentRefreshHook() throws Exception {
+        Class<?> ds = Class.forName("com.etotem.cfc.service.DimensionScoreService");
+        boolean hasHook = false;
+        for (Method m : ds.getDeclaredMethods()) {
+            if ("refreshFromDanAssessment".equals(m.getName())) {
+                hasHook = true;
+                break;
+            }
+        }
+        assertTrue(hasHook,
+                "[teacher.dimension.score] DimensionScoreService 应提供 refreshFromDanAssessment"
+                        + "(契约 SC-T-30 测评录入触发五维维度变化)。");
+
+        // AssessmentService 应注入该桥接(recordResult 触发点)
+        Class<?> as = Class.forName("com.etotem.cfc.service.AssessmentService");
+        boolean injected = false;
+        for (Field f : as.getDeclaredFields()) {
+            if ("com.etotem.cfc.service.DimensionScoreService".equals(f.getType().getName())) {
+                injected = true;
+                break;
+            }
+        }
+        assertTrue(injected,
+                "[teacher.dimension.score] AssessmentService 应注入 DimensionScoreService"
+                        + "(recordResult 录入测评结果时触发五维维度变化,契约 SC-T-30)。");
+    }
+
+    // ------------------------------------------------------------------
+    // 边界流-1(SC-T-32,预期红):五维自评应桥接落五维变化(契约「自评 → 变化落 EnergyController」)
+    // ------------------------------------------------------------------
+
+    @Test
+    @DisplayName("边界流-1(SC-T-32,红):五维自评提交应桥接五维分数/能量变化(当前未落地)")
+    void selfCheck_submitShouldBridgeDimensionScoreChange() {
+        Class<?> clazz = FiveDimensionSelfCheckService.class;
+        String[] bridgeTypes = {
+                "com.etotem.cfc.service.DimensionScoreService", // 维度分桥接(refactor 同 SC-T-30)
+                "com.etotem.cfc.service.EnergyService",          // 能量落点(契约「变化落 EnergyController」)
+                "com.etotem.cfc.mapper.FiveDimensionScoreMapper" // 直接落 five_dimension_scores
+        };
+        String found = null;
+        for (Field f : clazz.getDeclaredFields()) {
+            String type = f.getType().getName();
+            for (String t : bridgeTypes) {
+                if (t.equals(type)) {
+                    found = t;
+                    break;
+                }
+            }
+            if (found != null) {
+                break;
+            }
+        }
+        assertNotNull(found,
+                "[teacher.dimension.score][SC-T-32] 五维自评提交后应触发五维变化落点:"
+                        + "FiveDimensionSelfCheckService 未注入任何维度分/能量桥接服务(DimensionScoreService / "
+                        + "EnergyService / FiveDimensionScoreMapper 均无)。契约要求「五维自评 → 变化落 "
+                        + "EnergyController」,当前自评只写 five_dimension_self_checks 表,"
+                        + "未桥接 five_dimension_scores / 五维能量。实现未落地,测试预期红。");
+    }
+
+    // ------------------------------------------------------------------
+    // 边界流-2(SC-T-32,预期红):five_dimension_scores.source_type 应有自评来源
+    // ------------------------------------------------------------------
+
+    @Test
+    @DisplayName("边界流-2(SC-T-32,红):five_dimension_scores.source_type 应含 selfcheck 来源(当前缺)")
+    void schema_sourceType_shouldSupportSelfCheck() {
+        String schema = readSchemaSql();
+        assertTrue(schema.contains("five_dimension_scores"),
+                "[teacher.dimension.score] schema.sql 缺少 five_dimension_scores 建表。");
+
+        // 定位 five_dimension_scores 建表段的 source_type 枚举注释
+        int idx = schema.indexOf("CREATE TABLE IF NOT EXISTS five_dimension_scores");
+        assertTrue(idx >= 0,
+                "[teacher.dimension.score] schema.sql 未找到 five_dimension_scores 建表语句。");
+        int end = schema.indexOf("ENGINE=InnoDB", idx);
+        String ddl = end > idx ? schema.substring(idx, end) : schema.substring(idx);
+        assertTrue(ddl.contains("selfcheck"),
+                "[teacher.dimension.score][SC-T-32] five_dimension_scores.source_type 枚举注释"
+                        + "(periodic/task/assessment/manual)缺少五维自评来源(selfcheck):"
+                        + "契约要求「五维自评 → 变化落 EnergyController」,自评分数应能写为五维分数变化,"
+                        + "当前建表定义无对应来源。实现未落地,测试预期红。");
+    }
+
+    // ------------------------------------------------------------------
+    // 权限流:teacher.dimension.score 为双角色业务域能力,无独立角色断言
+    // ------------------------------------------------------------------
+
+    @Test
+    @DisplayName("权限流:手动打分/自评端点走 JWT 登录态,规划师双角色无独立角色断言")
+    void permissionFlow_teacherDualRoleNoSeparateRoleAssertion() {
+        // 说明性用例:SC-T-30/31/32 端点经 JwtInterceptor 登录态保护(@RequestAttribute("userId")),
+        // 规划师(role=teacher)与家长共用同端点;契约未定义独立 teacher-only 角色分支(与
+        // FamilyJoinValidationRulesContractTest 权限流口径一致)。不产生断言失败。
+        assertTrue(true,
+                "权限流说明:teacher.dimension.score 端点面向登录用户(含 role=teacher 双角色),"
+                        + "无独立角色门禁断言。");
+    }
+
+    // ------------------------------------------------------------------
+    // 辅助
+    // ------------------------------------------------------------------
+
+    private String readSchemaSql() {
+        try (InputStream is = getClass().getClassLoader().getResourceAsStream("schema.sql")) {
+            if (is == null) {
+                return "";
+            }
+            try (Scanner scanner = new Scanner(is, StandardCharsets.UTF_8.name())) {
+                scanner.useDelimiter("\\A");
+                return scanner.hasNext() ? scanner.next() : "";
+            }
+        } catch (Exception e) {
+            return "";
+        }
+    }
+
+    @SuppressWarnings("unused")
+    private Method findMethod(Class<?> clazz, String name) {
+        for (Method m : clazz.getDeclaredMethods()) {
+            if (m.getName().equals(name)) {
+                return m;
+            }
+        }
+        return null;
+    }
+}

+ 21 - 21
docs/tdd/orchestration/sc-key-map.md

@@ -208,38 +208,38 @@ SC-G-02 / SC-G-06 / SC-P-03 / SC-P-10 / SC-P-11 共 5 个 SC 已于 2026-11-06 O
 | SC-P-94 | 家长 | 业务域(家长):成就与成长 | 我的成就「完成任务」→ 历史完成任务页面 | task.history.view | YELLOW | 未跑通 | 实现已在 origin/cfclub(ed8f1c347);三类流证据待补 |
 | SC-P-95 | 家长 | 业务域(家长):成就与成长 | 我的成就「成长记录」→ 成长历史记录页(含今日成长打卡记录) | growth.history.view | YELLOW | 未跑通 | 实现已在 origin/cfclub(da2727f53);三类流证据待补 |
 | SC-P-96 | 家长 | 业务域(家长):成就与成长 | 我的成就固定五列显示 | achievement.grid.fiveCol | YELLOW | 未跑通 | 实现已在 origin/cfclub(e205656ee);三类流证据待补 |
-| SC-T-01 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 规划师登录 + 切回家长 | — | — | 未跑通 | — |
-| SC-T-02 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 绑定测试家庭 | — | — | 未跑通 | — |
-| SC-T-03 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 代传报告 + 确认入库(上传报告)⭐ | — | — | 未跑通 | — |
-| SC-T-04 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 方案审核列表 + 批准 | — | — | 未跑通 | — |
-| SC-T-05 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 激活方案拆任务(幂等)⭐ 生成任务 | — | — | 未跑通 | — |
-| SC-T-06 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 家庭任务审核 | — | — | 未跑通 | — |
-| SC-T-07 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 录入测评结果触发方案 ⭐ | — | — | 未跑通 | — |
-| SC-T-08 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 成长计划 / 套餐创建 | — | — | 未跑通 | — |
-| SC-T-09 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 测评预约确认(状态守卫) | — | — | 未跑通 | — |
+| SC-T-01 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 规划师登录 + 切回家长 | teacher.login.switch | RED | 未跑通 | — |
+| SC-T-02 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 绑定测试家庭 | teacher.family.bind | RED | 未跑通 | — |
+| SC-T-03 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 代传报告 + 确认入库(上传报告)⭐ | teacher.report.proxy | RED | 未跑通 | — |
+| SC-T-04 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 方案审核列表 + 批准 | teacher.plan.review | RED | 未跑通 | — |
+| SC-T-05 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 激活方案拆任务(幂等)⭐ 生成任务 | teacher.plan.activate | RED | 未跑通 | — |
+| SC-T-06 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 家庭任务审核 | teacher.task.review | RED | 未跑通 | — |
+| SC-T-07 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 录入测评结果触发方案 ⭐ | teacher.assessment.record | RED | 未跑通 | — |
+| SC-T-08 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 成长计划 / 套餐创建 | teacher.plan.create | RED | 未跑通 | — |
+| SC-T-09 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 测评预约确认(状态守卫) | teacher.appointment.guard | RED | 未跑通 | — |
 | SC-T-10 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 测评预约完成/未到 | — | — | 未跑通 | — |
 | SC-T-11 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | DAN 报告上传与解析 | — | — | 未跑通 | — |
 | SC-T-12 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | DAN 八类报告查看 | — | — | 未跑通 | — |
-| SC-T-13 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 规划师视角数据隔离 | — | — | 未跑通 | — |
+| SC-T-13 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 规划师视角数据隔离 | teacher.data.isolation | RED | 未跑通 | — |
 | SC-T-14 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 方案编辑(规划师侧) | — | — | 未跑通 | — |
 | SC-T-15 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 套餐任务项管理 | — | — | 未跑通 | — |
-| SC-T-16 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 客户健康总览 | — | — | 未跑通 | — |
+| SC-T-16 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 客户健康总览 | teacher.family.overview | RED | 未跑通 | — |
 | SC-T-17 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 成长计划进度跟踪 | — | — | 未跑通 | — |
 | SC-T-18 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 批量任务下发 | — | — | 未跑通 | — |
-| SC-T-19 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 消息推送客户 | — | — | 未跑通 | — |
-| SC-T-20 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 退出/切换账号 | — | — | 未跑通 | — |
+| SC-T-19 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 消息推送客户 | teacher.message.push | RED | 未跑通 | — |
+| SC-T-20 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 退出/切换账号 | teacher.login.switch | RED | 未跑通 | — |
 | SC-T-21 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 报告对比分析 | — | — | 未跑通 | — |
 | SC-T-22 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 方案归档 | — | — | 未跑通 | — |
 | SC-T-23 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 报告代传(规划师代客户传)⭐ | — | — | 未跑通 | — |
-| SC-T-24 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 解绑/重分配客户家庭 | — | — | 未跑通 | — |
-| SC-T-25 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 舌诊/情绪测评录入(规划师) | — | — | 未跑通 | — |
-| SC-T-26 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 精准营养方案制定 | — | — | 未跑通 | — |
+| SC-T-24 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 解绑/重分配客户家庭 | teacher.family.bind | RED | 未跑通 | — |
+| SC-T-25 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 舌诊/情绪测评录入(规划师) | teacher.mind.record | RED | 未跑通 | — |
+| SC-T-26 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 精准营养方案制定 | teacher.nutrition.plan | RED | 未跑通 | — |
 | SC-T-27 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 家庭天盘解读(规划师) | — | — | 未跑通 | — |
 | SC-T-28 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | AI 方案内容安全复核 | — | — | 未跑通 | — |
-| SC-T-29 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 客户档案数据导出(合规) | — | — | 未跑通 | — |
-| SC-T-30 | 成长规划师 | 业务域(规划师):五维变化录入 | 测评录入触发五维维度变化 | — | — | 未跑通 | — |
-| SC-T-31 | 成长规划师 | 业务域(规划师):五维变化录入 | 手动维度打分覆盖/调整 | — | — | 未跑通 | — |
-| SC-T-32 | 成长规划师 | 业务域(规划师):五维变化录入 | 五维自评录入与变化 | — | — | 未跑通 | — |
+| SC-T-29 | 成长规划师 | 业务域 L:交易延伸 / 内容 / 多端 | 客户档案数据导出(合规) | teacher.client.export | RED | 未跑通 | — |
+| SC-T-30 | 成长规划师 | 业务域(规划师):五维变化录入 | 测评录入触发五维维度变化 | teacher.dimension.score | RED | 未跑通 | — |
+| SC-T-31 | 成长规划师 | 业务域(规划师):五维变化录入 | 手动维度打分覆盖/调整 | teacher.dimension.score | RED | 未跑通 | — |
+| SC-T-32 | 成长规划师 | 业务域(规划师):五维变化录入 | 五维自评录入与变化 | teacher.dimension.score | RED | 未跑通 | — |
 | SC-S-01 | 商品服务商 | 业务域 L:交易延伸 / 内容 / 多端 | 入驻申请 | — | — | 未跑通 | — |
 | SC-S-02 | 商品服务商 | 业务域 L:交易延伸 / 内容 / 多端 | 商品管理(上架/编辑) | — | — | 未跑通 | — |
 | SC-S-03 | 商品服务商 | 业务域 L:交易延伸 / 内容 / 多端 | 订单处理(发货) | supplier.order.fulfill | RED | 未跑通 | — |
@@ -268,7 +268,7 @@ SC-G-02 / SC-G-06 / SC-P-03 / SC-P-10 / SC-P-11 共 5 个 SC 已于 2026-11-06 O
 | SC-H-05 | 真人管家 | 业务域 C:资源对接与协同闭环 | 线下资源与活动对接 | — | — | 未跑通 | — |
 | SC-H-06 | 真人管家 | 业务域 C:资源对接与协同闭环 | 与 AI 管家协同闭环 | — | — | 未跑通 | — |
 | SC-H-07 | 真人管家 | 业务域 D:服务质量 | 服务质检与满意度 | — | — | 未跑通 | — |
-| SC-X-22 | 跨角色 | 奖励/CF 规则中心(全局 UI 规范) | CF 值获取预估(操作前提示 +N CF) | — | — | 未跑通 | — |
+| SC-X-22 | 跨角色 | 奖励/CF 规则中心(全局 UI 规范) | CF 值获取预估(操作前提示 +N CF) | reward.cfEstimate.expectedCf | DONE | 未跑通 | 键已 DONE(实现闭环),三类流证据待补 |
 
 ---