"""AI 舌色判定の確率を,既存の診断結果と同じ形(5 色の割合・上位 2 色)にする.
画面(メイン画面の「診断結果」欄・結果画面)はルールベース診断と共通にし,推論だけを
差し替えるため(Issue #13).判定モデル(`color_model.json`)のクラスを既存の 5 色のキー
(`core.svm.COLOR_KEYS`)に割り当てる.
本モジュールは onnxruntime を import しない(結果画面から型として参照するため軽く保つ).
"""
from __future__ import annotations
from collections.abc import Mapping
from dataclasses import dataclass
from tiasjudge.core.svm import COLOR_KEYS
#: 判定モデルのクラス(`color_model.json` の `classes` の `slug`)→ 既存の 5 色のキー.
#: 表示名は 淡白→淡白・淡紅→淡紅・紅→紅・暗赤紅→暗赤・紫→紫 に対応する
CLASS_SLUG_TO_COLOR_KEY: dict[str, str] = {
"pale": "pale",
"light_red": "pink",
"red": "crimson",
"dark_red": "dark_red",
"purple": "purple",
}
#: 確率(0〜1)を百分率にする倍率
PERCENT_SCALE = 100
@dataclass(frozen=True)
class MlDiagnosis:
"""AI 舌色判定の結果を既存の診断結果と同じ形にしたもの.
`core.svm.DiagnosisResult` と同じ名前の属性(`scores`・`percentages`・`top_labels`・
`failed`)を持ち,結果画面・メイン画面の表示にそのまま渡せる.L*a*b* は持たない
(変換行列を通さないため).
Attributes:
scores: 5 色の確率(0.0〜1.0.和は 1).キーは `COLOR_KEYS`.
percentages: 表示用の百分率(整数.色ごとに四捨五入するため和が 100 にならないことが
ある.ルールベース診断の表示と同じ扱い).
top_labels: 確率の降順の上位 2 色のキー(同点は `COLOR_KEYS` の順).
failed: 常に False(判定できなかった場合は本結果を作らない).
"""
scores: dict[str, float]
percentages: dict[str, int]
top_labels: tuple[str, str]
failed: bool = False
def build_ml_diagnosis(probabilities_by_slug: Mapping[str, float]) -> MlDiagnosis:
"""判定モデルのクラスごとの確率から,既存の形の結果を作る.
Args:
probabilities_by_slug: クラスの `slug` → 確率(5 クラスすべて).
Returns:
5 色のキーに割り当てた結果.
Raises:
ValueError: 未知のクラス,またはクラスの過不足がある場合.
"""
unknown = set(probabilities_by_slug) - set(CLASS_SLUG_TO_COLOR_KEY)
if unknown:
raise ValueError(f"判定モデルに未知のクラスがある: {sorted(unknown)}")
scores = {
CLASS_SLUG_TO_COLOR_KEY[slug]: float(value) for slug, value in probabilities_by_slug.items()
}
if set(scores) != set(COLOR_KEYS):
raise ValueError(f"判定モデルのクラスが 5 色と対応しない: {sorted(scores)}")
# 表示順を既存に揃える(dict の並びは `COLOR_KEYS`)
scores = {key: scores[key] for key in COLOR_KEYS}
percentages = {key: round(value * PERCENT_SCALE) for key, value in scores.items()}
# 安定ソートなので,同点は `COLOR_KEYS` の順になる
ordered = sorted(COLOR_KEYS, key=lambda key: scores[key], reverse=True)
return MlDiagnosis(scores=scores, percentages=percentages, top_labels=(ordered[0], ordered[1]))