本文件用來「凍結」目前專案的對外行為契約,作為後續架構重構(Clean Architecture / 模組拆分)時的回歸基準。
- Baseline name:
contract-freeze-v1 - Freeze date:
2026-05-02 - Branch:
ultimate_refactor - Scope: API routes、HTTP method、status code、關鍵回應模型、LINE 對話流程語意
- 任何影響上述 scope 的修改,都需要同步更新本文件與對應測試。
- 若是重構(檔案搬移、分層調整)但行為不變,測試必須保持全綠。
- 若是產品需求導致契約變更,需升版為
contract-freeze-v2+並記錄變更摘要。
每次重構前後,至少要執行以下命令(在 repo root):
pytest backend/tests/test_openapi_contract.py建議額外執行(需可連到測試資料庫):
pytest backend/tests/test_contract_smoke.py- OpenAPI 路徑未意外刪除(
test_openapi_contract.py綠燈) - Swagger UI (
/) 與 OpenAPI (/openapi.json) 可正常存取 - 若有契約變更,已更新本文件與對應測試
- 已說明此次是「行為不變重構」或「契約升版變更」
- GET
/health- 200:
"OK"(PlainTextResponse)
- 200:
- POST
/callback- Header:
X-Line-Signature必填 - Body: JSON 須含 top-level
destination(對應store.slug) - 200:
"OK"(PlainTextResponse) - 400: Missing signature / Invalid signature / missing destination
- 404: No store for
destination - 503: Store inactive or LINE credentials not configured
- Header:
-
以下端點需 Bearer token,且僅能操作登入者所屬
store的資料。 -
GET
/orders- Response model:
Optional[List[OrderOut]]
- Response model:
-
POST
/order/{room_id}- 從
OrderDraft建立正式Order - Response model:
list[str]
- 從
-
PATCH
/order/{room_id}- 更新指定 room 的 order(目前 route 無 request body)
- Response model:
bool
-
DELETE
/order/{order_id}- 取消/刪除 order(實作上是狀態變更)
- Response model:
bool
-
GET
/orderdraft/{room_id}- Response model:
Optional[OrderDraftOut]
- Response model:
-
PATCH
/orderdraft/{room_id}- Body:
OrderDraftUpdate - Response model:
Optional[OrderDraftOut]
- Body:
- PATCH
/organize_data/{room_id}- 觸發一次「整理訂單草稿」(OpenAI delta extraction)
- Response model:
OrganizeOrderDraftOut(draft+changed_fields+source_message_ids) - 無新訊息:200 回傳現有 draft,
changed_fields=[],不呼叫 OpenAI - LLM / JSON 失敗:502
{"detail": "LLM returned empty or invalid JSON."} - OpenAI 暫時性錯誤:503
{"detail": "LLM service unavailable"}
- POST
/orders/{order_id}/suggest-from-chat- 依
processed=false對話 + 目前訂單呼叫 OpenAI,不寫入orders - Response:
OrderSuggestFromChatOut(suggested+changed_fields+source_message_ids)
- 依
- PATCH
/orders/{order_id}可帶mark_processed_message_ids;成功後將該批訊息標為processed=true
Base prefix: /chat_rooms(需 Bearer token;列表與 room 操作限所屬 store)
-
GET
/chat_rooms- Response model:
List[ChatRoomOut]
- Response model:
-
GET
/chat_rooms/{room_id}/messages?after={datetime?}- Response model:
List[ChatMessageOut]
- Response model:
-
POST
/chat_rooms/{room_id}/messages- Body:
ChatMessageCreate(擇一:text、image_url,或同時提供sticker_package_id+sticker_id) - Response model:
ChatMessageOut(其中message為ChatMessagePayload,可含貼圖欄位與image_url)
- Body:
-
POST
/chat_rooms/{room_id}/messages/upload_imagemultipart/form-data,欄位名file(JPEG/PNG/GIF/WebP,最大約 5MB)- Response model:
StaffChatImageUploadOut:{ "image_url": "<SUPABASE_URL>/storage/v1/object/public/chat-images/store_<id>/staff_chat/…>" } - 404:chat room 不存在
- 400:格式不符或空檔
- 413:檔案過大
-
POST
/chat_rooms/{room_id}/switch_mode- Body:
ChatRoomStage - Response model:
{"message": "success"}
- Body:
- GET
/stats- Response model:
StatsOut
- Response model:
-
GET
/payment_methods- Response model:
list[PaymentMethodBase]
- Response model:
-
PATCH
/payment_methods/{payment_method_id}- Response model:
PaymentMethodBase
- Response model:
-
GET
/payment_methods/{payment_method_id}- Response model:
PaymentMethodBase - 404:
{"detail": "Payment method not found"}
- Response model:
- GET
/orders/{order_id}.docx- 成功:回傳
StreamingResponse(docx) - 找不到:回傳
{"error": "Order not found"}
- 成功:回傳
- GET
/generate-fake-data?count={int=10}- 200:
"OK"(PlainTextResponse)
- 200:
Stage enum: ChatRoomStage(實際 enum 定義在 backend/app/enums/chat.py)
- 每次收到 LINE text message:
- 確保存在
User、ChatRoom、OrderDraft - 寫入一筆
ChatMessage(direction=INCOMING, processed=False) - 若距離最後一則訊息超過 7 天:重置
stage=WELCOME、bot_step=-1
- 確保存在
bot_step == -1:發出 confirm template「是否啟動智慧訂購流程」並記錄一則 bot outgoing 訊息,然後bot_step=0- 第二次回覆:
- 若文字為
啟動智慧訂購流程:切到BOT_ACTIVE,bot_step=1 - 否則:切到
WAITING_OWNER並 reply「已轉交客服人員」
- 若文字為
- 依
bot_step執行:1(預算) → 2(顏色) / 3(花材) → 4(收尾) → 結束後轉WAITING_OWNER - 若流程 handler 缺失:轉
WAITING_OWNER、bot_step=-1
- 若訂單確認後又收到訊息:轉
WAITING_OWNER(人工回覆)
- 讀取該 room 所有
processed=False的ChatMessage組成combined_text(時間正序、Asia/Taipei) - 用
app/prompts/order_prompt.txt+order_extraction_rules.txt產生 prompt,呼叫 OpenAImodel="gpt-4.1",temperature=0,response_format=json_object - 期望模型輸出「delta JSON」(僅有變更欄位),後端 server-side merge 後更新
OrderDraft customer_phone:草稿整理時若 LLM delta 含電話,經customer_organize_sync寫入Customer.phone(與order_draft分開);正式訂單 suggest 亦允許 LLM 更新電話(寫入orders.customer_phone,待 staff 儲存);customer_name永遠 locked- 若 draft 缺必要欄位:會對顧客 LINE push 缺漏提醒,並記錄一則 outgoing bot 訊息(且
processed=True以免再被 GPT 讀到)