Skip to content

Latest commit

 

History

History
169 lines (126 loc) · 6.78 KB

File metadata and controls

169 lines (126 loc) · 6.78 KB

Contract Freeze (No Behavior Change)

本文件用來「凍結」目前專案的對外行為契約,作為後續架構重構(Clean Architecture / 模組拆分)時的回歸基準。

Freeze Baseline Metadata

  • Baseline name: contract-freeze-v1
  • Freeze date: 2026-05-02
  • Branch: ultimate_refactor
  • Scope: API routes、HTTP method、status code、關鍵回應模型、LINE 對話流程語意

Change Policy

  • 任何影響上述 scope 的修改,都需要同步更新本文件與對應測試。
  • 若是重構(檔案搬移、分層調整)但行為不變,測試必須保持全綠。
  • 若是產品需求導致契約變更,需升版為 contract-freeze-v2+ 並記錄變更摘要。

Verification Gate

每次重構前後,至少要執行以下命令(在 repo root):

pytest backend/tests/test_openapi_contract.py

建議額外執行(需可連到測試資料庫):

pytest backend/tests/test_contract_smoke.py

PR Checklist (Contract Related)

  • OpenAPI 路徑未意外刪除(test_openapi_contract.py 綠燈)
  • Swagger UI (/) 與 OpenAPI (/openapi.json) 可正常存取
  • 若有契約變更,已更新本文件與對應測試
  • 已說明此次是「行為不變重構」或「契約升版變更」

Backend API Endpoints (FastAPI)

Health

  • GET /health
    • 200: "OK" (PlainTextResponse)

LINE Webhook

  • 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

Orders / Drafts

  • 以下端點需 Bearer token,且僅能操作登入者所屬 store 的資料。

  • GET /orders

    • Response model: Optional[List[OrderOut]]
  • 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]
  • PATCH /orderdraft/{room_id}

    • Body: OrderDraftUpdate
    • Response model: Optional[OrderDraftOut]

LLM Organize (Draft)

  • PATCH /organize_data/{room_id}
    • 觸發一次「整理訂單草稿」(OpenAI delta extraction)
    • Response model: OrganizeOrderDraftOutdraft + 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"}

LLM Suggest (Formal Order Preview)

  • POST /orders/{order_id}/suggest-from-chat
    • processed=false 對話 + 目前訂單呼叫 OpenAI,不寫入 orders
    • Response: OrderSuggestFromChatOutsuggested + changed_fields + source_message_ids
  • PATCH /orders/{order_id} 可帶 mark_processed_message_ids;成功後將該批訊息標為 processed=true

Messages (Chat Rooms)

Base prefix: /chat_rooms(需 Bearer token;列表與 room 操作限所屬 store)

  • GET /chat_rooms

    • Response model: List[ChatRoomOut]
  • GET /chat_rooms/{room_id}/messages?after={datetime?}

    • Response model: List[ChatMessageOut]
  • POST /chat_rooms/{room_id}/messages

    • Body: ChatMessageCreate(擇一:textimage_url,或同時提供 sticker_package_id + sticker_id
    • Response model: ChatMessageOut(其中 messageChatMessagePayload,可含貼圖欄位與 image_url
  • POST /chat_rooms/{room_id}/messages/upload_image

    • multipart/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"}

Statistics

  • GET /stats
    • Response model: StatsOut

Payment Methods

  • GET /payment_methods

    • Response model: list[PaymentMethodBase]
  • PATCH /payment_methods/{payment_method_id}

    • Response model: PaymentMethodBase
  • GET /payment_methods/{payment_method_id}

    • Response model: PaymentMethodBase
    • 404: {"detail": "Payment method not found"}

Export

  • GET /orders/{order_id}.docx
    • 成功:回傳 StreamingResponse(docx)
    • 找不到:回傳 {"error": "Order not found"}

Dev / Seed

  • GET /generate-fake-data?count={int=10}
    • 200: "OK" (PlainTextResponse)

LINE Conversation Flow (Stages)

Stage enum: ChatRoomStage(實際 enum 定義在 backend/app/enums/chat.py

Global behavior

  • 每次收到 LINE text message:
    • 確保存在 UserChatRoomOrderDraft
    • 寫入一筆 ChatMessage(direction=INCOMING, processed=False)
    • 若距離最後一則訊息超過 7 天:重置 stage=WELCOMEbot_step=-1

WELCOME

  • bot_step == -1:發出 confirm template「是否啟動智慧訂購流程」並記錄一則 bot outgoing 訊息,然後 bot_step=0
  • 第二次回覆:
    • 若文字為 啟動智慧訂購流程:切到 BOT_ACTIVEbot_step=1
    • 否則:切到 WAITING_OWNER 並 reply「已轉交客服人員」

BOT_ACTIVE

  • bot_step 執行:1(預算) → 2(顏色) / 3(花材) → 4(收尾) → 結束後轉 WAITING_OWNER
  • 若流程 handler 缺失:轉 WAITING_OWNERbot_step=-1

ORDER_CONFIRM

  • 若訂單確認後又收到訊息:轉 WAITING_OWNER(人工回覆)

LLM Draft Organize Behavior

  • 讀取該 room 所有 processed=FalseChatMessage 組成 combined_text(時間正序、Asia/Taipei)
  • app/prompts/order_prompt.txt + order_extraction_rules.txt 產生 prompt,呼叫 OpenAI model="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 讀到)