|
|
@@ -0,0 +1,128 @@
|
|
|
+# 维度评分口径约定(P2-T2)
|
|
|
+
|
|
|
+> 状态:评审稿 · 归属:统一维度体系(2026-11-03-unified-dimension-system-design.md §9 P2-T2)
|
|
|
+> 目的:**文档化**雷达 / 沙盘能量 / 指标归一化 三处共享的 percentile / tier / 归一化口径,消除歧义。
|
|
|
+> 原则:本文件**不改变任何运行时行为**(纯文档 + 可选共享常量,常量未接入业务逻辑)。所有换算逻辑以代码实际实现为准,逐条核实并注明出处。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 1. 共享口径(三处统一)
|
|
|
+
|
|
|
+**量纲**:所有维度得分统一落在 **0–100** 闭区间(`0`=最弱/最差,`100`=最强/最优)。
|
|
|
+
|
|
|
+**统一离散化(percentile 带 / tier)**:以人群百分位断点 **P5 / P25 / P75 / P95** 划分五档(tier)。该断点是三处共享的**唯一** tier 分段依据:
|
|
|
+
|
|
|
+| 数值区间(相对常模/人群) | band(档) | status(语义) |
|
|
|
+|---|---|---|
|
|
|
+| `v < P5` | `veryLow` | 偏低(异常) |
|
|
|
+| `P5 ≤ v < P25` | `low` | 偏低(异常) |
|
|
|
+| `P25 ≤ v ≤ P75` | `mid` | 正常 |
|
|
|
+| `P75 < v ≤ P95` | `high` | 偏高(异常) |
|
|
|
+| `v > P95` | `veryHigh` | 偏高(异常) |
|
|
|
+
|
|
|
+**归一化公式**:指标原始值 → 百分位,采用常模断点 **线性插值**:
|
|
|
+
|
|
|
+```
|
|
|
+percentile(v) =
|
|
|
+ P≤5 , v ≤ P5
|
|
|
+ P≥95 , v ≥ P95
|
|
|
+ P[ p_i + (v - p_i)/(p_{i+1} - p_i) × (p_{i+1} - p_i) ] , p_i ≤ v ≤ p_{i+1}
|
|
|
+```
|
|
|
+
|
|
|
+其中断点序列 `(p_0..p_4) = (P5, P25, P50, P75, P95)`,对应百分位 `(5, 25, 50, 75, 95)`。
|
|
|
+> 注:上式第二行在 `p_{i+1} - p_i > 0` 时即 `cents[i] + (v - p_i)/span × (cents[i+1] - cents[i])`;`span ≤ 0` 时退化为该断点对应百分位。
|
|
|
+
|
|
|
+**常量来源**:`com.etotem.cfc.common.constant.DimensionCodeConstants`(P2-T2 新增)
|
|
|
+`SCORE_MIN=0`、`SCORE_MAX=100`、`BAND_P5=5`、`BAND_P25=25`、`BAND_P75=75`、`BAND_P95=95`。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 2. 子系统 A —— 雷达(RadarChart)
|
|
|
+
|
|
|
+**代码**:`cfc-frontend/components/RadarChart.vue`(uni-app / Vue2);后端取数 `DimensionScoreServiceImpl.calcPopulationScore`。
|
|
|
+
|
|
|
+**实际换算**(从代码核实,非推测):
|
|
|
+- 分值输入 `scores[]`(0–100,与 `dimensions[]` 按索引对齐)。
|
|
|
+- 无效值归一:`_safeScore(v)` —— 非 number / `NaN` / `null` 一律按 `0`(防 Canvas `NaN` 静默失败)。
|
|
|
+- 顶点半径:`r = outerR × clamp(score/100, 0, 1)`(即 0–100 线性映射到 0–100% 半径)。
|
|
|
+- 分值文本:`Math.round(score) + '%'`。
|
|
|
+- 网格层级:`[0.25, 0.5, 0.75, 1.0]`(对应 25/50/75/100 分参考线)。
|
|
|
+- 参考系列(同一 0–100 量纲):
|
|
|
+ - **人群位置** `populationScores[]` = 常模 **P50**(`DimensionScoreServiceImpl.calcPopulationScore`:`cognitive_norms.percentile_50` / `health_norm_reference.percentile_50`;无成员/无常模返回 `null`,画布按 0 处理)。
|
|
|
+ - **家庭平均** `avgScores[]` / `familyScores[]` = 家庭平均分。
|
|
|
+- **tier 文本**:RadarChart **当前不输出** tier 文本;其 0–100 分与 §1 档位按同一断点可互相映射(展示层如需标签,应复用 P5/P25/P75/P95 断点,勿另设阈值)。
|
|
|
+
|
|
|
+**结论**:雷达把 0–100 分直接画为半径占比;tier 语义通过数值隐含(数值 = 百分位等价量纲)。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 3. 子系统 B —— 沙盘能量(Energy)
|
|
|
+
|
|
|
+**代码**:`cfc-backend/src/main/java/com/etotem/cfc/service/EnergyService.java`。
|
|
|
+
|
|
|
+**实际换算**(从代码核实,非推测):
|
|
|
+- 语义:计算每个家庭成员(主账号+成员)的 **身 / 心 / 智 / 行 / 富** 五维能量值,**0–100%**(类注释 line 27)。
|
|
|
+- 各子维公式 → `clamp(..., 0, 100)` 收口。代表公式:
|
|
|
+ - 富·利他贡献:`clamp(20 + (直接邀请人数-1) × 15, 0, 100)`(1 人=20 分,每多 1 人 +15,封顶 100)。
|
|
|
+ - 富·财务积累:`clamp(cf_balance.total_earned / 50, 0, 100)`(5000 CF = 100 分)。
|
|
|
+ - 行动·习惯/打卡:`clamp(连续打卡天数 × 100 / 参考天数, 0, 100)` 及加权合成。
|
|
|
+- **五行调节(在 0–100 基础上乘系数,仍 clamp [0,100])**:
|
|
|
+ - 增益:`boost = (上游-50)/100 × 10`(仅当 `上游>60 && 下游<80` 触发)。
|
|
|
+ - 衰减/透支/克制:`penalty = 0.5 + 0.5 × (自身/100)`(身<40 → 财富 × penalty;行>75 且身<40 → 身 × penalty 等)。
|
|
|
+- 最终能量比 = 该维度能量值(0–100%)。
|
|
|
+
|
|
|
+**结论**:沙盘以 0–100 量化五维能量;tier 通过数值隐含(同 §1 断点)。五行调节是 0–100 量纲上的乘性修正,不改变量纲。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 4. 子系统 C —— 指标归一化(Indicator)
|
|
|
+
|
|
|
+**代码**:
|
|
|
+- 单一写者:`NormalizeIndicatorPipeline.processReport` / `snapshotFoodIndices`(原始值 + `numeric_value` + `ref_range_snapshot` 幂等写入 `indicator_values`)。
|
|
|
+- 常模比对:`attribution/NormCompareEngine.applyBandFromNorm` / `applyBandFromPopulation` / `roughPercentile`。
|
|
|
+- 模型:`attribution/model/IndicatorReading`(band / status / percentile 常量)。
|
|
|
+
|
|
|
+**实际换算**(从代码核实,非推测):
|
|
|
+- 常模命中(`p5/p95` 完整断点存在):
|
|
|
+ - `v < p5` → `veryLow` / 偏低
|
|
|
+ - `v < p25` → `low` / 偏低
|
|
|
+ - `v ≤ p75` → `mid` / 正常
|
|
|
+ - `v ≤ p95` → `high` / 偏高
|
|
|
+ - `v > p95` → `veryHigh` / 偏高
|
|
|
+ - percentile 文本:`roughPercentile` 线性插值(`P≤5` / `P≥95` / 插值 `P<n>`)。
|
|
|
+- 菌群 `populationLevel`(如 `"35%"`)优先作为人群百分位:`≤5 / ≤25 / ≤75 / ≤95` 同上分带,`percentile = "P" + round(pop)`。
|
|
|
+- **常模缺失 → `status="待补"`,`band=unknown`,不猜值**(§8 共享知识·常模口径)。
|
|
|
+
|
|
|
+**结论**:指标显式使用 §1 的 P5/P25/P75/P95 分带与线性插值归一化,是最直接落地 tier 的子系统。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 5. 三处一致性校验
|
|
|
+
|
|
|
+| 维度 | 量纲 | 是否显式 tier | tier 断点 | 归一化方式 |
|
|
|
+|---|---|---|---|---|
|
|
|
+| 雷达 | 0–100 | 否(数值隐含) | P5/P25/P75/P95 | `clamp(score/100,0,1)` → 半径;参考=P50 |
|
|
|
+| 沙盘能量 | 0–100% | 否(数值隐含) | P5/P25/P75/P95 | 子维公式 clamp(0,100) + 五行乘性修正 |
|
|
|
+| 指标 | 0–100 百分位 | 是(band/status) | P5/P25/P75/P95 | 常模断点线性插值 → 百分位带 |
|
|
|
+
|
|
|
+**共享同一口径**:三者同用 0–100 量纲,且 tier 均以 P5/P25/P75/P95 断点为准(指标显式实现,雷达/沙盘数值隐含、可按同断点映射)。**消除歧义点**:任何子系统不得另设 tier 阈值(如 优秀/良好 自定 80/60 分界),如需标签须复用 §1 断点。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 6. 不变式(红线)
|
|
|
+
|
|
|
+- 本文件**不改变**任何取分 / 归一化 / 五行计算逻辑;仅文档化 + 新增未接入的共享常量。
|
|
|
+- 常模缺失(指标)→ `待补`,不猜值;缺失成员/常模(雷达人群位置)→ `null`(画布按 0)。
|
|
|
+- 所有分数 clamp 到 `[0, 100]`;`NaN`/`null` 归一为 0(RadarChart `_safeScore`)。
|
|
|
+
|
|
|
+## 7. 代码出处速查
|
|
|
+
|
|
|
+| 事实 | 出处 |
|
|
|
+|---|---|
|
|
|
+| 0–100 → 半径、null→0、`%` 文本、网格层级 | `cfc-frontend/components/RadarChart.vue`(`_safeScore`、`_drawScoreArea`、`_drawScoreText`、`_drawGrid`) |
|
|
|
+| 人群位置 = 常模 P50 | `cfc-backend/.../service/impl/DimensionScoreServiceImpl.java#calcPopulationScore` |
|
|
|
+| 五维能量 0–100%、clamp、五行 boost/penalty | `cfc-backend/.../service/EnergyService.java` |
|
|
|
+| 指标常模分带 + 线性插值 + populationLevel 回退 + 待补 | `cfc-backend/.../service/attribution/NormCompareEngine.java`(`applyBandFromNorm`/`applyBandFromPopulation`/`roughPercentile`) |
|
|
|
+| band/status 常量(veryLow/low/mid/high/veryHigh、待补/偏低/正常/偏高) | `cfc-backend/.../service/attribution/model/IndicatorReading.java` |
|
|
|
+| 指标单一写者(原始值+参考范围快照) | `cfc-backend/.../service/NormalizeIndicatorPipeline.java` |
|
|
|
+| 共享常量(SCORE_MIN/MAX、BAND_P5/25/75/95) | `cfc-backend/.../common/constant/DimensionCodeConstants.java`(P2-T2) |
|