Skip to content

mindforge-labs/LotusHack26_OrderLens

Repository files navigation

“AI đối chiếu khay đồ ăn với order từ POS” alt text

1. Kiến trúc MVP

Mục tiêu kiến trúc

  • cực ít moving parts
  • dễ demo local
  • không phụ thuộc backend riêng
  • giữ được Gemini API key
  • đủ sạch để pitch như một hệ thống có thể mở rộng

Kiến trúc đề xuất

[Next.js Frontend]
   ├─ POS Order Builder
   ├─ Tray Capture / Upload UI
   ├─ Verification Result UI
   ├─ Mock menu + mock order state
   └─ IndexedDB (lưu session demo, lịch sử verify)

                |
                | POST /api/verify-tray
                v

[Next.js Route Handler]
   ├─ nhận order + image
   ├─ build prompt
   ├─ gọi Gemini API bằng fetch/curl-style request
   └─ trả structured JSON về frontend

                |
                v

[Google Gemini API]
   └─ phân tích ảnh + đối chiếu expected order

Vì sao kiến trúc này hợp

  • Frontend vẫn là trung tâm
  • không cần dựng ASP.NET hay DB thật
  • route handler chỉ là lớp proxy siêu mỏng
  • API key không lộ ra browser
  • đủ “có kiến trúc” để giám khảo thấy bạn không code bừa

Thành phần cụ thể

Frontend

Các module:

  • menu catalog
  • order builder
  • camera/upload tray image
  • verification dashboard
  • history panel

State

Bạn có thể giữ đơn giản:

  • React state / Zustand đều được

  • IndexedDB chỉ dùng cho:

    • lưu order gần nhất
    • lưu ảnh demo metadata
    • lưu lịch sử kết quả verify

Route handler

Ví dụ:

  • app/api/verify-tray/route.ts

Nó làm 4 việc:

  1. nhận expectedOrder
  2. nhận imageBase64
  3. build prompt + schema instruction
  4. call Gemini và parse JSON trả về

2. Flow màn hình

Tôi khuyên chỉ làm 3 màn hình chính hoặc 1 trang chia 3 panel.

Option ngon nhất cho hackathon: 1 trang chia 3 cột/panel


Màn hình 1 — POS Order Builder

Mục tiêu: cashier nhập source of truth.

UI

  • danh sách sản phẩm cố định

  • mỗi item có:

    • tên
    • thumbnail/icon
    • nút - / +
  • panel bên phải hiện:

    • selected items
    • tổng số lượng item
    • nút Create Order

Output

{
  "orderId": "ORD-001",
  "items": [
    { "productId": "snack_red", "name": "Snack Red", "quantity": 2 },
    { "productId": "cola_can", "name": "Cola Can", "quantity": 1 }
  ]
}

Màn hình 2 — Tray Capture / Upload

Mục tiêu: lấy ảnh khay để verify.

UI

  • tab Upload Image
  • tab Use Webcam
  • preview ảnh đã chọn
  • nút Verify Tray

Gợi ý UX

  • hiển thị order hiện tại phía trên

  • nếu chưa có order thì disable verify

  • có note nhỏ:

    • đặt khay trong vùng rõ
    • chụp từ trên xuống hoặc hơi chéo
    • tránh rung mờ

Màn hình 3 — Verification Result

Mục tiêu: giám khảo nhìn 3 giây là hiểu.

UI chia 3 block

A. Expected Order

  • Snack Red ×2
  • Cola Can ×1

B. Detected Tray

  • Snack Red ×1
  • Cola Can ×1
  • Snack Blue ×1

C. Result

  • Mismatch Detected

  • Missing:

    • Snack Red ×1
  • Extra:

    • Snack Blue ×1

Badge nên có

  • Matched
  • Mismatch
  • Missing Items
  • Extra Items
  • Quantity Error

3. Structured output schema

Nên làm schema thật rõ và không quá thông minh. Đừng bắt model trả mấy field quá thơ ca.

Schema đề xuất

{
  "success": true,
  "verificationStatus": "matched",
  "summary": "Tray matches the expected order.",
  "detectedItems": [
    {
      "productId": "snack_red",
      "productName": "Snack Red",
      "quantity": 2
    },
    {
      "productId": "cola_can",
      "productName": "Cola Can",
      "quantity": 1
    }
  ],
  "missingItems": [],
  "extraItems": [],
  "quantityMismatches": [],
  "notes": [
    "Image is clear and all items are visible."
  ]
}

Khi sai số lượng

{
  "success": true,
  "verificationStatus": "mismatch",
  "summary": "Tray does not match the expected order.",
  "detectedItems": [
    {
      "productId": "snack_red",
      "productName": "Snack Red",
      "quantity": 1
    },
    {
      "productId": "cola_can",
      "productName": "Cola Can",
      "quantity": 1
    }
  ],
  "missingItems": [
    {
      "productId": "snack_red",
      "productName": "Snack Red",
      "expectedQuantity": 2,
      "detectedQuantity": 1,
      "missingQuantity": 1
    }
  ],
  "extraItems": [],
  "quantityMismatches": [
    {
      "productId": "snack_red",
      "productName": "Snack Red",
      "expectedQuantity": 2,
      "detectedQuantity": 1
    }
  ],
  "notes": [
    "One Snack Red appears to be missing."
  ]
}

Khi có món lạ / dư

{
  "success": true,
  "verificationStatus": "mismatch",
  "summary": "Tray contains extra items not present in the order.",
  "detectedItems": [
    {
      "productId": "snack_red",
      "productName": "Snack Red",
      "quantity": 2
    },
    {
      "productId": "snack_blue",
      "productName": "Snack Blue",
      "quantity": 1
    }
  ],
  "missingItems": [],
  "extraItems": [
    {
      "productId": "snack_blue",
      "productName": "Snack Blue",
      "detectedQuantity": 1
    }
  ],
  "quantityMismatches": [],
  "notes": [
    "Detected an extra item that is not part of the expected order."
  ]
}

Enum status

Chỉ cần 2 giá trị:

  • matched
  • mismatch

Đừng tách quá nhiều enum phụ, frontend render sẽ mệt.


4. Thiết kế menu metadata gửi sang Gemini

Đây là phần rất quan trọng. Bạn hỏi nên nhét tên, id, tag mô tả thế nào — câu trả lời là: nên có cả 3.

Cấu trúc menu item đề xuất

[
  {
    "productId": "snack_red",
    "displayName": "Snack Red",
    "shortLabel": "Red Snack Pack",
    "category": "snack_pack",
    "visualTags": [
      "red package",
      "small snack bag",
      "bright red wrapper"
    ]
  },
  {
    "productId": "snack_blue",
    "displayName": "Snack Blue",
    "shortLabel": "Blue Snack Pack",
    "category": "snack_pack",
    "visualTags": [
      "blue package",
      "small snack bag",
      "bright blue wrapper"
    ]
  },
  {
    "productId": "cola_can",
    "displayName": "Cola Can",
    "shortLabel": "Cola Can",
    "category": "drink_can",
    "visualTags": [
      "soft drink can",
      "aluminum can",
      "red soda can"
    ]
  },
  {
    "productId": "water_bottle",
    "displayName": "Water Bottle",
    "shortLabel": "Water Bottle",
    "category": "drink_bottle",
    "visualTags": [
      "clear plastic bottle",
      "water bottle",
      "transparent bottle"
    ]
  }
]

Vì sao cần từng field

  • productId: để frontend compare ổn định
  • displayName: để hiển thị UI
  • category: giúp model hiểu nhóm vật thể
  • visualTags: đây là “gợi ý thị giác” rất hữu ích khi packaging khác màu

Rule quan trọng

Tag mô tả phải:

  • ngắn
  • cụ thể
  • có tính thị giác
  • không viết marketing

Sai:

  • “delicious snack”
  • “premium refreshing cola”

Đúng:

  • “red package”
  • “plastic bottle”
  • “silver can top”
  • “blue snack bag”

5. Prompt Gemini

Bạn đang làm verification, nên prompt phải ép model theo đúng vai trò đó.

System instruction đề xuất

You are an AI vision verification assistant for a fast-food POS system.

Your task is to analyze a tray image and compare the visible items against the expected POS order.

Important rules:
1. Only identify items from the provided allowed menu list.
2. Do not invent new products outside the allowed menu.
3. Count visible items conservatively and carefully.
4. If an item is uncertain, prefer the closest allowed product based on visual tags, but do not hallucinate.
5. Your job is not only to detect items, but also to compare them with the expected order.
6. Return valid JSON only.
7. Do not include markdown fences.
8. Do not include explanations outside the JSON.

User prompt template đề xuất

Allowed menu list:
{{menuJson}}

Expected POS order:
{{orderJson}}

Task:
Analyze the tray image and detect the visible items using only the allowed menu list.
Then compare the detected items with the expected POS order.

Return JSON with this exact structure:
{
  "success": true,
  "verificationStatus": "matched" | "mismatch",
  "summary": "string",
  "detectedItems": [
    {
      "productId": "string",
      "productName": "string",
      "quantity": number
    }
  ],
  "missingItems": [
    {
      "productId": "string",
      "productName": "string",
      "expectedQuantity": number,
      "detectedQuantity": number,
      "missingQuantity": number
    }
  ],
  "extraItems": [
    {
      "productId": "string",
      "productName": "string",
      "detectedQuantity": number
    }
  ],
  "quantityMismatches": [
    {
      "productId": "string",
      "productName": "string",
      "expectedQuantity": number,
      "detectedQuantity": number
    }
  ],
  "notes": ["string"]
}

Decision rules:
- If all products and quantities match the expected order exactly, set verificationStatus to "matched".
- Otherwise set verificationStatus to "mismatch".
- An item in detectedItems must belong to the allowed menu list.
- missingItems should contain items where detected quantity is lower than expected.
- extraItems should contain detected items not expected in the order.
- quantityMismatches should contain items whose detected quantity differs from expected quantity.
- Keep notes short and practical.

Kết hợp ảnh trong request

Bạn sẽ gửi:

  • prompt text
  • image input

Một lưu ý hay

Có thể thêm:

The tray contains only a small number of items. Focus on precise counting.

Câu này hợp scope của bạn, giúp model bớt over-detect.


6. Route handler flow

Pseudo-flow:

Frontend gửi:
- expectedOrder
- menuCatalog
- imageBase64

Route handler:
- validate payload
- build prompt
- call Gemini
- parse JSON
- nếu parse fail thì fallback response
- trả JSON cho frontend

Fallback rất nên có

Nếu model trả JSON lỗi:

{
  "success": false,
  "verificationStatus": "mismatch",
  "summary": "Unable to verify tray reliably.",
  "detectedItems": [],
  "missingItems": [],
  "extraItems": [],
  "quantityMismatches": [],
  "notes": ["Model response could not be parsed safely."]
}

Hackathon demo mà không có fallback là tự gài mìn.


7. Strategy demo hackathon ít vỡ nhất

Đây là phần quan trọng nhất. Không phải model mạnh là thắng. Demo ít vỡ mới thắng.

Nguyên tắc vàng

Dàn dựng để AI có cơ hội đúng.

Nghe hơi sân khấu, nhưng hackathon là nơi “controlled reality”.

A. Chốt môi trường chụp

  • nền bàn cố định
  • ánh sáng ổn định
  • khoảng cách camera gần cố định
  • vật thể không chồng che nhau nhiều
  • mỗi item nhìn rõ phần nhận diện chính

B. Chỉ dùng 4–6 sản phẩm cực khác nhau về hình dáng/màu

Nên ưu tiên:

  • snack đỏ
  • snack xanh
  • lon nước ngọt
  • chai nước
  • gói snack vàng nếu có
  • lon khác màu nếu đủ khác biệt

Tránh:

  • 3 món nhìn na ná cùng shape cùng tone
  • packaging quá giống nhau

C. Chuẩn bị sẵn bộ ảnh “đẹp”

Bạn có 2 mode demo:

  1. safe mode: upload ảnh đã chụp đẹp sẵn
  2. live mode: webcam nếu tình hình ổn

Khi trình diễn chính thức, tôi khuyên:

  • demo 1 case webcam
  • còn lại dùng ảnh pre-shot chuẩn Đừng all-in vào live camera như phim hành động.

D. Chuẩn bị 3 case demo kinh điển

Case 1 — Good case

Order:

  • Snack Red ×2
  • Cola Can ×1

Tray:

  • đúng y chang

Kết quả:

  • Matched

Case 2 — Missing item

Order:

  • Snack Red ×2
  • Cola Can ×1

Tray:

  • Snack Red ×1
  • Cola Can ×1

Kết quả:

  • thiếu 1 Snack Red

Case 3 — Extra/wrong item

Order:

  • Snack Red ×2
  • Cola Can ×1

Tray:

  • Snack Red ×2
  • Water Bottle ×1

Kết quả:

  • extra Water Bottle
  • thiếu Cola Can

Ba case này đủ để kể business story.

E. Đừng demo quá nhiều case

Tối đa:

  • 1 intro case
  • 2 mismatch case

Nhiều hơn là giám khảo bắt đầu mệt và demo có thêm cơ hội phản chủ.

F. Chuẩn bị nút “Load Demo Order”

Rất nên có:

  • Load Demo A
  • Load Demo B
  • Load Demo C

Khi lên demo, bạn không phải cộng tay từng món. Tiết kiệm thời gian và giảm thao tác lỗi.

G. Chuẩn bị ảnh tương ứng với từng demo order

Ví dụ:

  • Demo A → ảnh đúng
  • Demo B → ảnh thiếu món
  • Demo C → ảnh sai món

Nút:

  • Use Sample Image

Cực kỳ thực dụng. Cực kỳ hackathon.

H. Pitch theo pain point

Bạn nên nói:

  • cashier nhập order thủ công dễ sai
  • giờ cao điểm dễ nghẽn hàng
  • AI không thay cashier
  • AI là lớp kiểm tra cuối trước khi giao khay
  • giảm sai đơn và giảm khiếu nại

Câu này nghe doanh nghiệp hơn rất nhiều.


8. Scope UI nên có

Trang chính

Block trái

POS Menu

  • add/remove item
  • current order

Block giữa

Tray Image

  • upload
  • webcam capture
  • preview

Block phải

AI Verification

  • status badge
  • expected vs detected
  • missing / extra / mismatch

Nút chính

  • Create Order
  • Capture / Upload
  • Verify Tray
  • Load Demo Case

9. Công thức compare ở frontend

Đừng phụ thuộc 100% vào AI để kết luận matched/mismatch. Tốt hơn là:

  • AI trả detectedItems
  • frontend/backend nhẹ tự compare lại với expected order

Tức là Gemini làm tốt nhất phần:

  • nhận diện item
  • đếm số lượng

Còn phần:

  • matched hay mismatch
  • thiếu gì
  • dư gì

thì app của bạn tự tính lại từ expectedOrderdetectedItems.

Vì sao nên làm vậy

  • deterministic hơn
  • dễ debug hơn
  • nếu AI note hơi ngáo, logic vẫn đúng
  • hệ thống trông “engineering” hơn, ít phụ thuộc model

Khuyến nghị thực chiến

Gemini vẫn có thể trả full schema, nhưng app nên:

  • parse detectedItems
  • chạy local compare function
  • dùng compare result local làm source of truth cuối cùng

Cái này là nước đi khôn.


10. Khuyến nghị cuối cùng cho MVP bản đẹp

Tôi chốt stack như này

  • Next.js App Router
  • Route Handler cho /api/verify-tray
  • IndexedDB cho demo history
  • menu mock JSON local
  • sample images local/public
  • Gemini vision call
  • local compare engine

Triết lý triển khai

  • AI chỉ làm phần khó nhất: nhìn ảnh
  • app tự làm phần logic: so sánh order
  • demo được thiết kế có kiểm soát
  • UI phải cực rõ expected vs detected vs result

11. Bản chốt ngắn gọn

Kiến trúc MVP

  • Next.js frontend
  • Next.js route handler proxy Gemini
  • IndexedDB lưu lịch sử demo
  • menu mock local
  • AI vision để detect item
  • local logic để compare order

Flow màn hình

  • chọn món + số lượng
  • upload/chụp ảnh khay
  • verify
  • hiện expected / detected / mismatch result

Structured output

  • detectedItems
  • missingItems
  • extraItems
  • quantityMismatches
  • verificationStatus
  • summary
  • notes

Prompt Gemini

  • chỉ detect từ fixed menu
  • dùng productId + displayName + visualTags
  • trả JSON only
  • tập trung đếm số lượng chính xác

Strategy demo

  • ảnh đẹp, nền cố định, item ít
  • 4–6 sản phẩm khác nhau rõ rệt
  • 3 demo case kinh điển
  • có sample order + sample image
  • webcam chỉ dùng như bonus, không all-in

About

AI-Powered Food Tray Recognition for POS Integration

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors