iT邦幫忙

2026 iThome 鐵人賽

DAY 16
0

Part 4|第 16/30 篇
今日要做的事: 先用一輪 compile mock 驗收 Strategy Stack,再鎖定 N=3~5 的 Pilot 協議。
今天要解決的目的: 確認 Strategy Stack 接起來仍守得住 LifeFlow,並把「C 是否比貼 Prompt 少步驟」鎖成之後可量的協議。

hard、soft、context 各自正確,不代表接起來也正確,更不代表真人願意每天用。
所以我先跑 mock,Stack 過關後才邀請人。


今日任務卡

項目 內容
產出 compile mock 驗收、Pilot 協議與事件格式;步驟比較表等真人數字
工具 自寫 Rule Engine;本篇用本地 snapshot,未寫 Firestore、未呼叫 Gemini
不使用 Health Connect、運動營養建議、真人資料代填
固定環境 induction_cooktop、deep_wok、tamagoyaki_pan、microwave;burners=1
指標 compile_ok、hard_ok、user_steps、elapsed_seconds、adopted

Day 11~14 的 compile、Hard、Soft、預算與時間在這裡串成 Gate 0;沒有呼叫 Gemini。


1. 問題:接線成功不等於使用成本下降

Day 12~14 解決的是個別規則:過敏不能投票、剩料只是排序、時間與預算是 hard、16:8 只做使用者自訂排程。

今天的新問題只有一個:

DishFlow 把 Strategy Stack 串起來後,是否比 B1/B2 手貼 Prompt 少做事?

A 最少步驟,但幾乎沒有 LifeFlow;B1 是十秒掃一眼後手打;B2 還要貼完整 pantry JSON。C 若要求每餐重選五個策略、重拍冰箱、再確認一次 profile,就算推薦較完整,產品仍然輸。

https://ithelp.ithome.com.tw/upload/images/20260930/20121052e3PAw8zeOH.png

因此 Pilot 不先問「喜不喜歡 AI」,而是量:

完成一次下一餐請求,需要幾個使用者動作?
同一份資料,B1/B2 貼 Prompt 要多久,C 又要多久?

今天走的路

固定一份 LifeFlow,用 mock 回兩個候選(一個合格、一個故意爐火重疊)。走 compile → mock → validate(過敏/設備/budget/time)→ soft 只重排合格案 → snapshot 可重播。Gate 0 有 PASS trace 才談 N=3~5 Pilot;事件只收步驟數與秒數,背景 compile 不算使用者步驟。

https://ithelp.ithome.com.tw/upload/images/20260930/20121052f9N0WbLWNU.png


2. 設計:Gate 0 過了,才開始 Pilot

2.1 第一關:一輪 compile mock

我先固定一份 LifeFlow,不讓 Gemini 參與,避免接線錯誤被自然語言掩蓋。

{
  "food_source": "home",
  "budget_twd": 120,
  "available_time_minutes": 20,
  "kitchen": {
    "equipment": ["induction_cooktop", "deep_wok",
      "tamagoyaki_pan", "microwave"],
    "burners": 1
  },
  "active_strategy_ids": ["allergy", "equipment", "budget_cap",
    "time_cap", "leftover_first", "meal_balance_recent"]
}

mock 只回兩個候選:P1 符合設備與預算;X2 故意在 0~10 分煮湯、3~8 分同時煎蛋。驗收順序固定:

LifeFlow snapshot
  → compile(rule_engine, soft_scores)
  → mock recommendation
  → validate
  → 合格案可顯示;違規案不得進前端

通過條件不是「看起來合理」,而是:

  • hard profile 沒被 Preset 關掉(allergy 等安全 ID 仍在 hard_constraint_ids)
  • burners=1 寫進 kitchen context,重疊爐火被擋
  • 故意違規的 mock 被攔下
  • soft 分數只重排,不救回違規案
  • snapshot 能重現同一份編譯結果

Gate 0 mock 驗收(本地 node test.js)

項目 結果
compile 合併安全 ID + budget/time hard PASS
P1 可顯示(hard/budget/time) PASS
X2 爐火重疊 → 不顯示 PASS
soft 不救 Z9 違規案 PASS
預算 121 > 120 → Stack 擋下 PASS
snapshot 重播 compile PASS
Pilot 事件 schema PASS
背景步驟不計入 user_steps PASS

2.2 第二關:N=3~5 Pilot

只有 Gate 0 通過後才發邀請。Pilot 是探索性試用,不代表台灣使用者,也不拿來宣稱健康成效。

參與者每天只做最小流程:

庫存有變才更新
吃完記一筆 today_meals
預算/時間/通路不同時才覆寫
需要下一餐時按推薦
採用或略過,各按一次

外食只寫 today_meals,不扣 pantry。自煮則要等實際煮完,後面的 Outflow 才能扣 pantry 並產生 tip_card。


3. 實作:Stack 與「比較省事」事件

3.1 runGate0Mock():串起 Day 11~14

// stack.js(節錄)
export function runGate0Mock(lifeFlow, dailySession, mockResponse, catalog) {
  const compiled = attachParamHints(
    compileStrategies(null, catalog, null, dailySession),
    catalog
  );
  const limits = extractLimits(lifeFlow, compiled);
  const context = buildKitchenContext(lifeFlow);

  const eligible = [];
  for (const option of mockResponse.options ?? []) {
    const verdict = validateOptionStack(option, context, limits);
    if (verdict.displayable) eligible.push(option);
  }

  const ranked = rankOptions(eligible, mockResponse.pantryById ?? {}, context);
  return { compiled, limits, perOption, ranked, snapshot: createRequestSnapshot(...) };
}

單案驗證依序是 Day 12 validateAfterGeneration → Day 14 validateBudget → validateTime;三者都過才 displayable: true。

export function validateOptionStack(option, context, limits) {
  const hard = validateAfterGeneration(option, context);
  if (!hard.hard_ok) return { displayable: false, hard_ok: false, hard };

  const payable = estimatePayable(option.meal_cost ?? {});
  const budget = validateBudget(
    payable.estimated_payable_twd,
    limits.max_budget_twd,
    payable.cost_complete
  );
  if (!budget.ok || budget.needs_confirmation) {
    return { displayable: false, hard_ok: true, budget_ok: false, budget, payable };
  }

  const timeEst = estimateTotalMinutes(option.time_input ?? {}, context.burners);
  const time = validateTime(
    timeEst.estimated_total_minutes,
    limits.max_time_minutes,
    timeEst.estimate_complete
  );
  if (!time.ok) return { displayable: false, time_ok: false, time, timeEst };

  return { displayable: true, hard_ok: true, budget_ok: true, time_ok: true };
}

3.2 Pilot 事件:同一組起終點

產品端與 B 對照都記同一組事件。下面的 2 步、18 秒只是格式範例,不是量到的數字:

{
  "participant_id": "anonymous_id",
  "method": "B1|B2|C",
  "task": "request_next_meal",
  "user_steps": 2,
  "elapsed_seconds": 18,
  "abandoned": false,
  "adopted": true
}

user_steps 只算使用者可感知動作:打字、貼上、改欄位、按確認。背景的 compile_*、validate_* 不算步驟,但延遲仍另記 elapsed_seconds。

// pilot.js
export function countUserSteps(stepIds) {
  return stepIds.filter((id) => !isBackgroundStep(id)).length;
}

B1/B2 與 C 使用同一個任務起點與終點:

起點:看到「今晚吃什麼」任務
終點:畫面出現可採用方案,或使用者放棄

Pilot 說明只承諾研究操作成本:

我們想知道:連續記下庫存與餐點後,
下一餐是否更容易決定、是否少做重複貼資料的動作。
這不是診斷或運動營養計畫;你可以隨時停止。

人還沒找,步驟、秒數和說明書截圖都先空著。等真的有人用過,再把量到的數字補上。


4. 驗證

執行test code:

node test.js

輸出太長,終端機一次截不完,下面依同一輪跑分六段截。

4.1 compile:安全 ID 與上限進 hard

https://ithelp.ithome.com.tw/upload/images/20260930/20121052HObh92w11L.png

hard_constraint_ids 含安全四項與 budget_cap、time_cap;limits 寫死 120 元、20 分鐘、burners=1。當日清單沒勾 forbidden_foods、diet_pattern,compile 仍把它們併進來。meal_balance_recent 有進 soft 清單,本篇排名還沒用到它。

4.2 Gate 0:P1 可顯示,X2 被擋

https://ithelp.ithome.com.tw/upload/images/20260930/20121052rLq2C9gwdi.png

mock 兩個候選:豆腐蛋炒青菜通過;故意爐火重疊的 X2 hard_ok: false,不進前端。

4.3 soft 救不回違規案

https://ithelp.ithome.com.tw/upload/images/20260930/20121052Ok6auaCbNb.png

Z9 就算分數再高,原因碼仍是 BURNER_CONCURRENCY_EXCEEDED,只出現在 removed。

4.4 budget Hard 疊在 Stack 上

https://ithelp.ithome.com.tw/upload/images/20260930/20121052rJffAaRG5S.png

過敏與設備都過了,121 > 120 仍在 budget 層被擋,不會進榜。

4.5 snapshot 可重播

https://ithelp.ithome.com.tw/upload/images/20260930/20121052z4iaUZMtyi.jpg

同一份 snapshot 再 compile,hard 清單不變。

4.6 Pilot 事件與收工

https://ithelp.ithome.com.tw/upload/images/20260930/201210520I0FRuyULZ.png

user_steps/elapsed_seconds 未填會拒收;範例 C、2 步、18 秒格式合法。最後一行:全部通過,Gate 0 mock 未呼叫 Gemini。招募前 actual_N 維持空白。


5. 今日結論

  1. 先驗 Stack,再碰真人。 compile mock 已有八項 PASS trace,才宣稱 Pilot 協議就緒。
  2. Pilot 測操作成本。 N=3~5 只做探索,不假裝有代表性或健康成效。
  3. C 必須少做事。 是否優於貼 Prompt,要用相同起終點的步驟與時間回答;若 C 較麻煩,就是產品 bug。

Day 11 到今天,畫面都很像:一條規則、一輪 node test.js、幾張 PASS。我是故意先停在本機。過敏、爐口、預算、時間這些門禁,先當本地 unit test 跑穩,再送上 Gemini 或雲端。規則還沒定,每次失敗都在燒配額,服務費用不該花在這種地方。

下一篇: 同一套門禁開始接到 Google。DishFlow 用 Function Calling 讀 LifeFlow,工具仍然不能取代 hard 門禁。


附錄 A:compile mock 驗收單

[x] hard profile 永遠保留;equipment 名稱一致;burners=1 可驗
[x] soft 只排序;違規 mock 不顯示
[x] request snapshot 可重播

附錄 B:Pilot 最小資料

只收匿名 participant id、事件時間、步驟數、是否採用、是否完成、卡住原因。不要收病歷,也不擴張到運動營養。

actual_N = (招募完成後填 3~5)
pilot_started_at = (真實啟動時間)
adoption_rate = (Day 26 依事件計算)
median_user_steps = (收齊資料後計算)

上一篇
[Day 14] 100 元與 20 分鐘不是參考值
下一篇
[Day 16] 模型會自己拿資料,就不用再貼整份人生了嗎?用 Function Calling 讓 Gemini 自己讀冰箱
系列文
DishFlow AI Agent:用 Google AI 打造 Eat-Cost Balance 的下一餐決策系統 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言