# 後段スコアリング・パイプライン（恒久化版）

打ち手1（判断不能画像の分岐）を組み込んだ、本番想定の自己完結パイプライン。
**元ファイル（`検証/` `local_batch_api/` `APIテスト/`）は一切変更していない**（読み取りのみ）。
特徴量生成・グレード定義は `soup_scoring.py` 内に複製済み。

## 構成

```
pipeline/
  soup_scoring.py   … コアモジュール（特徴量生成・分岐・学習・推論API）
  train_model.py    … 学習スクリプト（assessable=1で線形回帰を学習し models/ に保存）
  predict.py        … 推論スクリプト（バッチCSV／単件JSON／デモ）
  models/           … 学習済みモデル（*.pkl）＋ metadata.json
```

## パイプラインの流れ

本番入力 = **濃度(brix) ＋ 動画(→最良フレーム→LLM評価) ＋ 時間(slot: 開店前/17時〜19時)**。

```
LLM画像評価JSON ＋ 濃度brix ＋ 時間slot
   ↓ prepare(): 特徴量化
   ↓ 判定可否ゲート（2段）
   ├─ readable=False（任意・上流RF-DETR） → status="要確認"
   ├─ assessable=0（LLM評価不能）          → status="要確認"（スコアせず）
   └─ 両方OK → 線形回帰でスコア回帰
                ├─ total(human_total): feature_set=visual_plus_brix_slot
                └─ soup (human_soup) : feature_set=visual_plus_brix_slot
                ↓ grade(): A≥80 / B60-79 / C21-59 / D<21
              status="scored" + total/soup スコア・グレード
```

判定可否ゲート（重要）は2段:
- **① 上流 `readable`**（任意入力）: RF-DETRスープ可読性。スープ表面が抽出/確認できない画像を弾く。
- **② `assessable`**: LLM評価4軸（visual_density / water_level / oil_emulsification / boiling_heat_state）が全て「判定不可」でないか（学習時と同じ導出）。

どちらか不可なら「要確認」。`readable` 列が無ければ②のみで判定（後方互換）。

## 使い方

### 1. 学習（モデル再生成）
```
cd 精度改善_20260619/pipeline
python train_model.py                 # 既定 training_dataset.csv で学習
python train_model.py --input <csv>   # 入力を差し替え
```

### 2. バッチ推論（training_dataset互換CSV）
```
python predict.py --input <input.csv> --output <result.csv>
# 出力列: sample_id, assessable, status(scored/要確認), reason,
#         total_score, total_grade, soup_score, soup_grade
```

### 3. 単件推論
```
python predict.py --demo              # サンプル2件（scored / 要確認）
python predict.py --record rec.json   # 1件JSONを評価
```

Pythonから直接:
```python
import soup_scoring as S
models = S.load_models("models")
result = S.score_record({
    "video_path": "123.MOV", "brix_raw": 12.0, "slot": "開店前", "store": "豚山X",
    "visual_density": "12", "water_level": "水位良好",
    "oil_emulsification": "乳化が進んでいる", "boiling_heat_state": "良い",
    "photo_quality": "良い", "image_condition_score": 8,
}, models)
# -> {"status":"scored","total_score":..,"total_grade":"A","soup_score":..,"soup_grade":"A"}
```

## 入力レコードの想定キー

| キー | 必須 | 内容 |
|---|---|---|
| visual_density / water_level / oil_emulsification / boiling_heat_state / photo_quality | ◯ | LLM画像評価AIの出力（文字列。評価不能は「判定不可」） |
| image_condition_score | ◯ | LLM出力の画質スコア |
| brix_raw | △ | 実測濃度。無ければ空でも可（線形回帰側で補完。将来は推定濃度を投入） |
| slot | ◯ | 時間帯（開店前 / 17時〜19時 等）。本番採用特徴量 |
| readable | △ | 上流RF-DETRスープ可読性(bool)。Falseで「要確認」へ。無ければLLM assessableのみで判定 |
| store / video_path / frame_path | △ | 補助情報（識別用） |

## 採用根拠と性能（分岐後 assessable=1, 5-fold CV）

| 指標 | total | soup |
|---|---|---|
| グレード一致率 | 89.0% | 87.9% |
| 誤差±15点以内 | 93.1% | 92.2% |
| MAE | 8.56 | 8.25 |

baseline（判断不能行も無理にスコア）比でグレード一致率 total 75.7%→89.0% / soup 69.0%→87.9%。
詳細は `../05_branch_comparison/` と `../README.md` を参照。

## 将来拡張メモ
- 実測濃度が無くなる場合: `brix_raw` に推定濃度AIの出力を入れ、`density_source` を特徴量化する余地あり。
- 要注意判定（低評価検出）は別建てが必要（`../03_low_score/` 参照、現状 anomaly_recall≈0）。
- LLMプロンプトの visual_density 境界補強（打ち手2）はLLM再出力＝再課金が伴うため別タイミング。
