Files
meida_backend/packages/report/dimensions.py
T
xsl e7076c9c01 feat(report): deterministic vision provider and 32-dimension diagnosis assembly
dimensions.py 逐行编码需求文档 §4(32 项四要素、缺失显式 skipped)+
VisionProvider 接口位与确定性实现 + 规则引擎联动 rule_hits(T-P3-01/06)。

实现:
- dimensions.py:DimensionSpec 恰 32 项(number 1~32 连续,33/34 永不复用),
  basic_coverage 口径机器可读:full 10 / label 3 / partial 8 / none 11,
  初级覆盖 21/32(BASIC_FULL_KEYS + BASIC_COVERED_KEYS)。
- vision.py:VisionProvider Protocol(派生特征形状写进 docstring,真实
  RTMPose/BiRefNet 实现后续按此替换)+ DeterministicVisionProvider(问卷
  BMI/自评驱动体型与肤色,sha256(object_key+canonical问卷) 驱动其余枚举,
  同输入同输出)+ build_vision_provider(未知名 CONFIG_ERROR)。
- vision_stage.py:derived_features 已存在 → skipped/reused(T-P3-09);
  否则分析 → 写中间态并 flush;photo_meta.quality_flags 透传进产物(T-P3-01)。
- diagnosis_stage.py:32 维恒全键,缺输入显式 skipped+原因(T-P3-06 红线);
  每维四要素 value/confidence/source/evidence/rule_hits + status;构造
  RuleContext → RuleService 载入 published 规则 → evaluate,body_shape/color
  类命中回落 #5/#19/#21,全量命中聚合进 #28/#30 的 rule_hits 与 evidence;
  色板 #19/#20/#21 从 kb_color 按季型读取,库空 → skipped「色板未录入」。

测试:单测 29 项(32 项完整性/编号连续/确定性/工厂拒绝未知名;四要素齐全
性——故意缺问卷+缺视觉断言 32 键全在且全部 skipped 带原因;规则命中进
rule_hits 用内置最小规则集不连库);集成 1 项(种 2 条 published 规则 →
VisionStage → DiagnosisStage:中间态落库与复用 skipped/reused、32 维全键、
rule_hits 出现、kb_color 空时色板 skipped、coverage basic_full=10/32)。
全绿:ruff check/format、mypy packages/report、lint-imports;单测 536 通过
(3 处既有 skip)、集成 46 通过。

偏差说明:
1. 任务文档写 RulesService,实际类名为 RuleService(packages/rules/service.py),按实际代码调用。
2. kb_color 联表键采用四季代码 spring/summer/autumn/winter(仓库无既定口径),已在 vision.py 注释声明。
3. 任务文档只给 basic_coverage 四枚举,需求表格中的「粗判/8色/3色」归入 partial、「Top1」归入 label。
2026-08-14 22:14:54 +08:00

123 lines
7.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""诊断维度字典(需求文档《美学报告-需求文档》§4 的机器可读形态)。
32 个维度是报告产出的**事实核心**:diagnosis_stage 逐维装配四要素(T-P3-06),
任何输入组合下都必须输出全部 32 个维度——缺失只能显式 skipped,不允许缺键。
逐行口径(与需求文档 §4 表格一一对应,2026-08-14 版):
- ``source`` 按表「数据来源」列归类到六类:questionnaire(问卷)/ vision
(视觉分析)/ color(色彩算法)/ kb(知识库映射)/ rule(规则)/ llm。
「视觉 + 问卷」「规则 + LLM」这类复合来源取**主生产方**(视觉、规则),
复合事实写进对应维度的 name_cn 与注释。
- ``basic_coverage`` 按表「初级」列归类:✅→full(完整输出)、标签/Top1→label
(仅标签)、部分/粗判/8 色/3 色→partial(粗判或子集)、❌→none(不输出)。
逐行统计(验收「初级 21/32」口径的来源,见需求文档 §4 表格下方统计):
- full(10 项):#1 gender、#2 height_weight、#3 birthday、#5 body_shape、
#8 face_shape、#12 skin_tone、#17 season_code、#26 psyche_style、
#27 fusion_style_name、#28 core_goals;
- label(3 项):#9 face_proportions、#10 facial_details、#25 gene_style;
- partial(8 项):#6 body_features、#13 face_hue、#14 face_lightness、
#19 best_colors、#21 avoid_colors、#22 volume、#23 curve_straight、
#30 preferences_taboos;
- none(11 项):#4 occupation、#7 body_ratio、#11 face_depth、#15 face_chroma、
#16 face_contrast、#18 season_detailed、#20 neutral_colors、#24 motion、
#29 occasion_weights、#31 hair_status、#32 hair_color。
合计 10 + 3 + 8 + 11 = 32;初级覆盖(非 none)= 10 + 3 + 8 = **21/32**。
编号 33(五行分布)/34(幸运色)已于 2026-08-13 随 TD-31 永久移除,**不复用**:
避免历史数据与文档交叉引用错位。
"""
from __future__ import annotations
from dataclasses import dataclass
#: basic_coverage 的合法取值(封闭枚举,防止拼写漂移出机器可读口径)。
BASIC_COVERAGE_VALUES: frozenset[str] = frozenset({"full", "label", "partial", "none"})
#: source 的合法取值(按需求文档「数据来源」列归类的六类)。
DIMENSION_SOURCES: frozenset[str] = frozenset(
{"questionnaire", "vision", "color", "kb", "rule", "llm"}
)
@dataclass(frozen=True, slots=True)
class DimensionSpec:
"""单个诊断维度的静态定义(字典行)。
只描述「这个维度是什么、数据从哪来、初级报告覆盖到什么程度」;
具体取值与置信度由 diagnosis_stage 装配成 DimensionResult。
"""
number: int # 1~32(33/34 已永久废弃,不复用)
key: str # 稳定英文键,与 kb/记忆系统词汇对齐(body_shape/face_shape/season_code…)
name_cn: str
source: str # questionnaire | vision | color | kb | rule | llm
basic_coverage: str # "full" | "label" | "partial" | "none"
#: 32 个维度,按需求文档 §4 表格逐行编码;number 恰好 1~32 连续。
DIMENSIONS: tuple[DimensionSpec, ...] = (
# -- 问卷直采(基础信息) --------------------------------------------------
DimensionSpec(1, "gender", "性别", "questionnaire", "full"),
DimensionSpec(2, "height_weight", "身高/体重", "questionnaire", "full"),
DimensionSpec(3, "birthday", "生日", "questionnaire", "full"),
DimensionSpec(4, "occupation", "职业", "questionnaire", "none"),
# -- 视觉分析(体型与比例) ------------------------------------------------
DimensionSpec(5, "body_shape", "体型", "vision", "full"),
DimensionSpec(6, "body_features", "身体局部特征", "vision", "partial"),
DimensionSpec(7, "body_ratio", "上下身比例", "vision", "none"),
# -- 视觉分析(面部) ------------------------------------------------------
DimensionSpec(8, "face_shape", "脸型", "vision", "full"),
DimensionSpec(9, "face_proportions", "三庭五眼", "vision", "label"),
DimensionSpec(10, "facial_details", "五官细节", "vision", "label"),
DimensionSpec(11, "face_depth", "面部立体度", "vision", "none"),
# -- 色彩(肤档与季型,视觉 Lab 采样 + 色彩算法) --------------------------
DimensionSpec(12, "skin_tone", "肤色档位", "vision", "full"),
DimensionSpec(13, "face_hue", "面部色相", "color", "partial"),
DimensionSpec(14, "face_lightness", "面部亮度", "color", "partial"),
DimensionSpec(15, "face_chroma", "面部色度", "color", "none"),
DimensionSpec(16, "face_contrast", "面部对比度", "color", "none"),
DimensionSpec(17, "season_code", "四季季型", "color", "full"),
DimensionSpec(18, "season_detailed", "十二季细分", "color", "none"),
# -- 知识库映射(色板) ----------------------------------------------------
DimensionSpec(19, "best_colors", "最佳色板", "kb", "partial"),
DimensionSpec(20, "neutral_colors", "中性色板", "kb", "none"),
DimensionSpec(21, "avoid_colors", "避雷色", "kb", "partial"),
# -- 视觉分析(风格量感) --------------------------------------------------
DimensionSpec(22, "volume", "量感", "vision", "partial"),
DimensionSpec(23, "curve_straight", "直曲(轮廓)", "vision", "partial"),
DimensionSpec(24, "motion", "动静", "vision", "none"), # 表列「视觉 + 问卷」,主生产方为视觉
DimensionSpec(25, "gene_style", "基因风格", "vision", "label"), # 初级 Top1
# -- 问卷偏好(心理侧) ----------------------------------------------------
DimensionSpec(26, "psyche_style", "心理风格", "questionnaire", "full"),
DimensionSpec(27, "fusion_style_name", "融合风格命名", "rule", "full"), # 表列「规则 + LLM」
DimensionSpec(28, "core_goals", "核心诉求", "questionnaire", "full"), # 问卷 + 推导
DimensionSpec(29, "occasion_weights", "场景权重", "questionnaire", "none"),
DimensionSpec(30, "preferences_taboos", "偏好与禁忌", "questionnaire", "partial"),
# -- 视觉分析(毛发现状) --------------------------------------------------
DimensionSpec(31, "hair_status", "发长与卷度现状", "vision", "none"),
DimensionSpec(32, "hair_color", "发色现状", "vision", "none"),
)
#: 初级报告**完整输出**(basic_coverage == "full")的维度键集合,共 10 项。
#: 这是验收「初级 21/32」口径里 21 = 10 full + 3 label + 8 partial 的机器可读形态。
BASIC_FULL_KEYS: frozenset[str] = frozenset(
spec.key for spec in DIMENSIONS if spec.basic_coverage == "full"
)
#: 初级报告**有产出**(full/label/partial,即 basic_coverage != "none")的维度键集合。
#: len == 21,对应需求文档 §4 统计表的「初级报告覆盖合计 21/32」。
BASIC_COVERED_KEYS: frozenset[str] = frozenset(
spec.key for spec in DIMENSIONS if spec.basic_coverage != "none"
)
#: number → spec(诊断装配按编号稳定排序输出用)。
DIMENSION_BY_NUMBER: dict[int, DimensionSpec] = {spec.number: spec for spec in DIMENSIONS}
#: key → spec(视觉/诊断阶段按维度键反查定义用)。
DIMENSION_BY_KEY: dict[str, DimensionSpec] = {spec.key: spec for spec in DIMENSIONS}