# Handoff：明日 Demo — 上下片 + Tracking 精準度

> 日期：2026-07-27（文件）／目標 Demo：次日  
> 工作根目錄：`/home/brad/proj/sonice-ai-sentinel-edge/cursor-glass-factory/`  
> 影片：`/mnt/nas/archives/glass-factory/drive-0617/`（上片×4 + 下片×4，**8/8 可播**）  
> GT：`data/20260630-recoreds.csv`（人機圖；等同 Drive「繞圈圈」xlsx，不必再下）

---

## 1. Demo 目標（對齊）

明日要能**清楚展示／盡量準**：

1. **匿名 tracking**：畫面內的人可連續跟蹤（track id），不是認姓名  
2. **上片 cycle**：玻璃上滾輪檯 → 推進／進爐（巨觀事件）  
3. **下片 cycle**（若時間夠）：出爐／滾輪有片 → 收片離檯  
4. **工／等區佔用**（輔助）：誰在檯邊工作區、誰在等待區

**不追求明日必達**：撕條碼、蓋 MARK、踩踏板、掃碼、員工姓名 ID。

成功長相（客戶／內部）：viewer 可 scrub；上下片事件與人軌跡「看起來對、數字不太丟臉」；能講清楚限制與下一步。

---

## 2. 現況快照（已完成）

### 2.1 已跑通：Phase A1（有 GT）

| 項目 | 內容 |
|------|------|
| 片 | `強化上片-20260617-0800-1200.mp4` |
| 窗 | 牆鐘 **09:30–10:30**（`video_t` 5400–9000，1 fps × 3600 幀） |
| 引擎 | D-FINE person（`:18085`）+ ByteTrack（`:18086`）+ zones + 滾輪 glass edge/motion 狀態機 |
| 產出 | `runs/phase_a1_pilot/`（tracks、cycles、occupancy、viewer） |
| 稽核 UI | **`viewer.html`**（canvas **post-render**，不改原始 JPG） |

**開啟 viewer（本機已常在 8765 時可直接開）：**

```bash
cd /home/brad/proj/sonice-ai-sentinel-edge/cursor-glass-factory
python3 -m http.server 8765
# http://127.0.0.1:8765/runs/phase_a1_pilot/viewer.html
```

### 2.2-NEW 調校後結果（2026-07-27 重跑，**這是要 demo 的版本**）

> 產出：[`metrics/phase_a1_v2.json`](metrics/phase_a1_v2.json) · 前後對照 [`metrics/phase_a1_compare.json`](metrics/phase_a1_compare.json)  
> Viewer：`http://127.0.0.1:8765/runs/phase_a1_pilot/viewer.html`  
> 重跑：`./scripts/run_a1_v2.sh`（偵測已快取，全鏈 <1 分鐘；`SKIP_FEATURES=1` 只要 15 秒）

| 指標 | 調校前 | **調校後** | 門檻 | 過關 |
|------|--------|-----------|------|------|
| 上片 cycle Recall | 0.53 | **0.84** (16/19) | ≥0.75 | ✅ |
| 上片 cycle Precision | 0.67 | **0.94** (16/17) | ≥0.70 | ✅ |
| median \|Δt\| | 76 s | **37 s** | ≤60 s | ✅ |
| Work 誤差 (person-sec) | 68.7% | **11.8%** | ≤35% | ✅ |
| Wait 誤差 (person-sec) | 238% | **21.8%** | ≤35% | ✅ |
| Track ID 數 / 小時 | 707 | **86** | — | — |
| **`phase_a1_gate`** | false | **true** | | ✅ |

前後兩欄都用**同一份 station GT** 評分，是同基準比較。

四個根因修掉了什麼：

1. **GT 本身錯了。** 曾建程與賴彥碩是同一台檯的兩份紀錄（曾的動作欄寫明「與賴強化」），
   raw 33 次其實是 **19 次**；另有 2 筆收片員阮文明的列被 `classify()` 依「上片」字串誤收。
   → `scripts/a1_fix_gt.py`，產出 `data/gt_a1_0930_1030.station.json`。
2. **玻璃訊號量錯了。** 舊的灰階 Canny 在 ROI 內 CV 只有 0.024（全時段 54.7–62.9），
   因為 ROI 框住了爐罩與白色機台。實測比較（7 張有片 / 3 張空檯）：

   | 特徵 | Cohen's d |
   |------|-----------|
   | 灰階 absdiff（舊） | 0.33 |
   | 亮度上升 | 2.10 |
   | **飽和度下降（新）** | **4.72**（範圍完全不重疊）|

   玻璃是半透明的：壓在紅滾輪上 = **一塊退飽和的淡色區**。灰階 absdiff 空檯也是 0.66，
   它量的是滾輪在轉，不是玻璃。→ `scripts/a1_features.py`
3. **ByteTrack 的旋鈕被除以 30。** `max_time_lost = int(frame_rate/30 * lost_track_buffer)`，
   `--frame-rate 1 --lost-track-buffer 60` 實際只有 **2 幀**容忍。
   → 改用 1 fps 專用的兩段式追蹤（保守關聯 → 外觀縫合）：`scripts/a1_retrack.py`
4. **工/等的單位錯了。** 舊碼每幀只吐一個 scene 標籤（上限 3600 s），GT 是 person-seconds（5158 s），
   這個 gate 數學上不可能過。改為全程 person-seconds + 動作判定；
   另加「**短暫停頓不算等待**」（<20 s 的靜止仍算工作），否則 AI wait 會膨脹約 4 倍。

**誠實的殘留限制**（demo 要講）：1 個 FP（09:35:01）、3 個 FN；86 個 id 對約 2.5 人仍代表整小時有 ID 斷點；
1 fps 是追蹤平滑度的物理上限；**下片尚未跑**。

<details>
<summary>調校前的舊數據（保留備查）</summary>

### 2.2 目前偵測結果與 Performance（Baseline，調參前）

> **跑次：** 2026-07-24 · Phase A1 · 上片 `0800-1200` 窗 **09:30–10:30**（`video_t` 5400–9000）  
> **原始檔：** [`metrics/phase_a1.json`](metrics/phase_a1.json) · [`metrics/phase_a1.md`](metrics/phase_a1.md) · [`runs/phase_a1_pilot/summary.json`](runs/phase_a1_pilot/summary.json)  
> **狀態：** `phase_a1_gate = **false**`（未達進入 A2／全班次的門檻）  
> 下片：**尚未跑**（無對應 metrics）。

#### 偵測產出量（AI raw）

| 項目 | 數值 |
|------|------|
| 分析幀數 | 3600（1 fps） |
| AI 上片 cycles | **15** |
| Scene work 總秒 | **1613 s**（~26.9 min） |
| Scene wait 總秒 | **1819 s**（~30.3 min） |
| Unique track ids | **707**／小時（ID 切換偏多） |
| Cycle 自動閾值 | `edge_thr≈58.6` · `motion_thr≈10.2` |

#### 對 GT Performance（與 CSV 人機圖同窗比對）

**上片 Cycle**（匹配窗 ±90s；以 AI `t_start` 對 CSV 上片發生時刻）

| 指標 | 數值 | 門檻 | 過關 |
|------|------|------|------|
| GT 次數 | 33 | — | — |
| AI 次數 | 15 | — | — |
| Hits / FP / FN | **11 / 4 / 22** | — | — |
| Recall | **0.333** | ≥ 0.75 | 否 |
| Precision | **0.733** | ≥ 0.70 | 是 |
| median \|Δt\| | **57 s** | ≤ 60 s | 是（勉強） |

**等待區佔用（Wait）**

| 指標 | 數值 | 門檻 | 過關 |
|------|------|------|------|
| GT wait 秒 | 1491 s | — | — |
| AI wait 秒 | 1819 s | — | — |
| MAPE | **0.220（22.0%）** | ≤ 35% | 是 |
| 區間 hits / FP / FN | 7 / 102 / 14 | （參考；切段偏碎） | — |

**工作區佔用（Work）** — 注意語意：AI＝腳在 `roller_table_work`；GT＝CSV work_like（含長複合上片列）

| 指標 | 數值 | 門檻 | 過關 |
|------|------|------|------|
| GT work 秒 | 7493 s | — | — |
| AI work 秒 | 1613 s | — | — |
| MAPE | **0.785（78.5%）** | （非正式主門檻） | — |
| Coverage recall（區間 IoU≥0.3） | **0.216** | ≥ 0.80 | 否 |
| 區間 hits / FP / FN | 16 / 90 / 58 | — | — |

**Tracking（診斷，未納入 gate）**

| 指標 | 數值 | 說明 |
|------|------|------|
| Unique track ids / 1h | **707** | 實際同時約數人 → ID switch 嚴重 |
| 正式 MOTA/IDF1 小標 | 未做 | 計畫有 2×10min 人工小標，A1 baseline 未跑 |

#### Gate 總結

| Gate 條件 | 結果 |
|-----------|------|
| Cycle pass | 否（Recall） |
| Wait pass | 是 |
| Work pass | 否（coverage） |
| **`phase_a1_gate`** | **false** |

解讀一句話：**等的總時長大致可用；上片 cycle 報的相對準（P=0.73）但漏很多（R=0.33）；work 數字不能當人工「整段上片作業」；tracking 有框但 id 不穩。**

調參重跑後請覆寫本節，並以新的 `metrics/phase_a1.json` 為準。

</details>

### 2.2-DEMO 必秀時間點（可直接貼網址，viewer 支援 `#t=`）

| # | 網址 | 秀什麼 |
|---|------|--------|
| 1 | `viewer.html#t=12` | cycle #1，**Δt = 0 秒**，AI 起點與人工表完全對上 |
| 2 | `viewer.html#t=555` | cycle #3 進行中，ROI 亮藍＝檯上有玻璃，覆蓋率 16.1% / 門檻 14.2%，推進 3.4/7 段 |
| 2a | `viewer.html#t=555&mask=1` | **玻璃偵測遮罩** — 藍色像素就是判定為玻璃的部分，面積 = 面板上的 16.1% |
| 2b | `viewer.html#t=555&sat=1&boxes=0&zones=0` | **S 飽和度通道** — 紅滾輪亮（高飽和）、玻璃暗（低飽和），一眼看懂原理 |
| 3 | `viewer.html#t=1300` | `p_015` 連續跟蹤 **787 秒（13 分鐘）** 不換 id |
| 4 | `viewer.html#t=1159` | 同框 6 人全部有 id，5 人工作 + 1 人非本站（自動排除收片員） |
| 5 | `viewer.html#t=2935` | 全小時最長的一段「等待」29 秒 — 說明 wait 在這條線本來就很少 |
| 6 | `viewer.html#t=301` | **唯一的誤報** cycle #2 — 主動講，別等客戶問 |

### 2.3 計畫位置

- 完整策略：[`PLAN.md`](PLAN.md)（A1 pilot → A2 全長 GT → B 其餘 7 片同參）  
- 本 handoff 覆寫「明日優先」：先把 **上片 cycle + tracking** 拉到可 demo，下片緊接；**不要**在 gate 失敗時假裝全 8 片已驗證。

---

## 3. 明日優先順序（建議 timeline）

### P0 — Demo 前必做（上片精準度）

1. **重畫 / 放大 `roller_table_work` + `roller_glass_roi`**  
   - 檔：`configs/shengmuglass-tempering-load.zones.json`  
   - 用 viewer 對照 09:30–10:30：人腳常落在 zone 外 → work 低估；glass ROI 太緊/太鬆 → cycle 漏或誤觸  

2. **放寬 cycle 狀態機（拉高 Recall）**  
   - 調 `event_rules`：`cycle_min_occupy_sec`、`cycle_min_advance_sec`、`cycle_min_gap_sec`  
   - 或降低 `edge_thr` / `motion_thr` 百分位（目前在 `run_phase_a1.py` 用 P70/P75 自動估）  
   - 目標：同一窗 GT 33 次，Recall 先衝 **≥0.6**（demo 敘事），再朝 0.75  

3. **穩定 tracking（降 ID switch）**  
   - ByteTrack：加大 `lost_track_buffer`、略降 `minimum_matching_threshold`（服務在 `examples/roven-bytetrack-supervision-tracker-service.py`，port 18086）  
   - 必要時 detect conf 略降（現 0.50）減少漏檢造成斷軌  
   - Demo 話術：展示「同框多人連續數十秒 id 不變」片段，不要強調 1h unique=707  

4. **重跑 A1 同窗 → 更新 metrics + viewer 資料**  

```bash
# 服務（若掛了）
# detector :18085 / tracker :18086 — 見下方「啟動服務」

cd /home/brad/proj/sonice-ai-sentinel-edge/cursor-glass-factory
./scripts/run_a1_pipeline.sh   # 會重抽幀若 <3500；已有 3600 幀則直接分析
# 日誌：logs/phase_a1_pipeline.log
```

5. **Demo 腳本（建議 8–10 分鐘）**  
   - 開 viewer → 指 zones → 跟一個 track 20–30s  
   - 點一條 AI cycle + 對應 GT 點（時間軸最下一列）  
   - 誠實講：微動作不做；work 秒數 ≠ CSV 複合列時長；下片另機位／另 zone  

### P1 — 若 P0 午後還有時間：下片最小可行

- 下片片建議先：**`強化下片-20260617-0800-1200.mp4`**（與上片同日早上，方便對 CSV 收片欄）  
- **新建** `configs/shengmuglass-tempering-unload.zones.json`（不可共用上片左側紅滾輪幾何；下片機位不同）  
- Cycle 定義改為：**滾輪出現玻璃 → 搬離／A 車**（或對爐「出爐」時刻若 Excel 有）  
- 複用同一套 detect+track+viewer；只換 ROI／狀態機閾值  
- Demo：至少 10–15 分鐘高密度窗 + viewer，不強求全長 GT gate  

### P2 — 明確不要排進明日主線

- 全 8 片批次推論當「已驗證準確率」  
- 姓名／臉／條碼 OCR  
- 重做 Shengmu 雲端 demo-cache UI（本地 viewer 已夠稽核）  

---

## 4. 偵測定義（對客戶怎麼講）

| 事件 | 訊號 | 不是什麼 |
|------|------|----------|
| Tracking | bbox + ByteTrack id；腳底點進 zone | 員工姓名 |
| 上片 cycle | 滾輪 ROI：邊緣↑（有片）→ 運動↑（推進）→ 清空 | 撕碼／蓋章／踩踏板 |
| 等待 | 人在 `wait_rest` 達一定秒數 | 「等出爐」語意保證 |
| 工作佔用 | 人在 `roller_table_work` | CSV「上片+撕碼+…」整段工時 |

CSV 對齊規則（已鎖）：整表能對的都對；**上／下片優先**；微動作本階段不計分。見 `PLAN.md`。

---

## 5. 關鍵路徑與指令

### 5.1 資料

```
NAS 影片:  /mnt/nas/archives/glass-factory/drive-0617/
A1 幀:     cursor-glass-factory/media/proxies/a1_frames/f_XXXXX.jpg  (3600)
GT JSON:   cursor-glass-factory/data/gt_a1_0930_1030.json
Zones:     cursor-glass-factory/configs/shengmuglass-tempering-load.zones.json
Run:       cursor-glass-factory/runs/phase_a1_pilot/
Metrics:   cursor-glass-factory/metrics/phase_a1.json
Viewer:    .../runs/phase_a1_pilot/viewer.html
```

### 5.2 啟動 detect / track（掛了才需要）

```bash
REPO=/home/brad/proj/sonice-ai-sentinel-edge
PY=$REPO/.cache/roven-real-model-eval/venv/bin/python

# Detector (person)
setsid $PY $REPO/examples/roven-dfine-onnx-detector-service.py \
  --model $REPO/.cache/roven-real-model-eval/models/dfine_s_obj2coco_onnx/model_fp16.onnx \
  --cpu-fallback-model $REPO/.cache/roven-real-model-eval/models/dfine_s_obj2coco_onnx/model_quantized.onnx \
  --execution-provider cpu --host 127.0.0.1 --port 18085 \
  --imgsz 640 --conf 0.50 --classes person \
  --work-dir $REPO/runtime/live-services/dfine-glass-a1 \
  </dev/null >$REPO/cursor-glass-factory/logs/dfine-detector.log 2>&1 &

# Tracker
setsid $PY $REPO/examples/roven-bytetrack-supervision-tracker-service.py \
  --host 127.0.0.1 --port 18086 --frame-rate 1 \
  --lost-track-buffer 60 \
  --work-dir $REPO/runtime/roven-tracker-service/bytetrack-glass-a1 \
  </dev/null >$REPO/cursor-glass-factory/logs/bytetrack.log 2>&1 &

curl -s http://127.0.0.1:18085/healthz | head -c 80; echo
curl -s http://127.0.0.1:18086/healthz | head -c 80; echo
```

### 5.3 重跑 A1 + 指標

```bash
cd /home/brad/proj/sonice-ai-sentinel-edge/cursor-glass-factory
./scripts/run_a1_pipeline.sh
# 或逐步：
# python scripts/run_phase_a1.py --frames-dir media/proxies/a1_frames --zones configs/... --out-dir runs/phase_a1_pilot --video-t0 5400
# python scripts/compute_metrics.py --run-dir runs/phase_a1_pilot --gt-json data/gt_a1_0930_1030.json --out metrics/phase_a1.json
```

約 3600×~0.4s ≈ **25–40 分鐘**（CPU D-FINE）。

### 5.4 Host viewer

```bash
cd /home/brad/proj/sonice-ai-sentinel-edge/cursor-glass-factory
python3 -m http.server 8765
# http://127.0.0.1:8765/runs/phase_a1_pilot/viewer.html
```

---

## 6. 已知坑（避免重踩）

1. **不要用**舊 `examples/shengmuglass-tempering-unload.zones.json` 套上片（幾何是下片 demo，方向反了）。  
2. **Work 秒數 vs CSV**：人工複合列時長 ≫「腳在檯邊」；評 work 要用 coverage／區間，不要只看 MAPE 嚇自己。  
3. **透明玻璃**：cycle 不能只靠亮度；要靠 edge + motion；仍會漏 → 寧可調參提高 recall。  
4. **Proxy mp4**：曾出現 moov 未寫完；分析以 **`a1_frames/*.jpg`** 為準。  
5. **產出一律留在 `cursor-glass-factory/`**，勿寫進舊 `runs/shengmuglass-demo-cache*`。  
6. 缺的下片夜班若曾限流：目前 NAS **8 支皆齊**；人機圖 xlsx 不必再下。

---

## 7. Demo 風險與話術

| 風險 | 緩解 |
|------|------|
| Cycle 仍漏 | 先秀「AI 有報且對上 GT」的段落；說明漏檢在調 ROI／閾值 |
| ID 亂跳 | 選遮擋少、1–2 人片段；強調匿名存在感而非 TPM 到人 |
| 下片沒好 | Demo 主軸上片；下片說「同管線、換 zone，下一版」 |
| 客戶要撕碼 | 明確：廣角 CCTV 物理極限；要近距鏡或 PLC／掃描器 |

---

## 8. 接手檢查清單（明早第一件事）

- [ ] `curl` 18085 / 18086 healthz = PASS  
- [ ] `ls media/proxies/a1_frames | wc -l` → 3600  
- [ ] 開 viewer，scrub 確認 canvas 疊加正常  
- [ ] 讀 `metrics/phase_a1.json` 是否已是「調參後」新數字  
- [ ] 定 2–3 個「必秀」時間點（寫在便利貼：wall clock）  
- [ ] 若要秀下片：確認 unload zones 草稿 + 至少一段 15 min 結果  

---

## 9. 聯絡語境（給下一位 agent／同事）

- 對話與決策已鎖在 `PLAN.md`：GT 先驗、忽略微動作、A1→A2→B。  
- 最新可 demo 產物是 **`viewer.html` + tracks.jsonl`**，不是舊 `audit.html`（那份綁 pre-baked overlay mp4）。  
- 明日 KPI 焦點：**上片 cycle Recall↑、track ID 連續性↑、下片有一條能講的故事**；全班次精準數字排 demo 後。

---

*本檔路徑：`cursor-glass-factory/HANDOFF-demo-next-day.md`*
