feat: two demo shelves, enrolled, plus the price-card filter they forced out
Two real store photographs, chosen for density and for products that look different
from each other, each with its own enrolled store:
demo-images/demo-image-1-mediya-dairy.jpg CC0 MEDIYA dairy case
21 products kept (13 price cards dropped), 16 recognised, all 14 products named
demo-images/demo-image-2-milk-case.jpg CC BY 2.0 supermarket milk case
56 detections, 49 recognised, all 13 products named
Both were enrolled the way a store would be: the products were read off a contact
sheet of the detections, their crops became the store's gallery (stock-mediya,
stock-milk150), and a head was trained on them. `build_store_from_shelf.py` is that
flow.
The filter came out of the first image. Every Japanese supermarket photo has the same
artifact: the detector boxes the shelf-edge price cards as products, because a card is
roughly the shape of a pack - 13 of 34 detections on image 1 were cards. Their text
separates them cleanly: a card's characters are 30-44% digits and it prints the
tax-excluded price (本体), while a product's are 3-19% digits. Cards are now dropped
before classification, and the count is reported as `price_cards_dropped` rather than
hidden.
demo-images/README.md records how to run both, what the demo shows, and the honest
caveat: both shelves are enrolled, so the recognition rate is in-distribution - what
the app does on a shelf it has been onboarded to, not on one it has never seen.
175 tests pass.
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
|
@ -24,6 +24,7 @@ import hmac
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
|
@ -479,6 +480,48 @@ def create_app(
|
|||
result = _attach_still_tags(result, image)
|
||||
return result
|
||||
|
||||
def _is_price_card(crop: Any) -> bool:
|
||||
"""Is this detection a shelf price card rather than a product?
|
||||
|
||||
Measured on a supermarket photo whose shelf edge carries big price cards:
|
||||
the detector boxes them as products, because a card is roughly the shape of a
|
||||
pack. Their text gives them away - a price card's characters are 30-44 %
|
||||
digits and it prints the tax-excluded price (本体), while a product's are 3-19 %
|
||||
digits. Reporting a price card as a product would put a box labelled with a
|
||||
product name on a price tag, which is worse than not detecting it.
|
||||
"""
|
||||
ocr_engine = getattr(engine, "ocr_engine", None)
|
||||
if ocr_engine is None or not getattr(ocr_engine, "available", lambda: False)():
|
||||
return False
|
||||
try:
|
||||
import cv2
|
||||
|
||||
big = cv2.resize(crop, None, fx=2.0, fy=2.0, interpolation=cv2.INTER_CUBIC)
|
||||
lines = ocr_engine.read(big).lines
|
||||
except Exception:
|
||||
return False
|
||||
text = " ".join(line.text for line in lines)
|
||||
compact = re.sub(r"\s", "", text)
|
||||
if not compact:
|
||||
return False
|
||||
digits = sum(1 for char in compact if char.isdigit() or char in "¥¥./") / len(compact)
|
||||
return ("本体" in text and digits >= 0.20) or digits >= 0.30
|
||||
|
||||
def _drop_price_cards(result: Dict[str, Any], image: Any) -> int:
|
||||
"""Remove detections that are price cards, and say how many were removed."""
|
||||
kept, dropped = [], 0
|
||||
for detection in result["detections"]:
|
||||
crop = engine.crop(image, detection["box"])
|
||||
if crop is not None and crop.size and _is_price_card(crop):
|
||||
dropped += 1
|
||||
continue
|
||||
kept.append(detection)
|
||||
if dropped:
|
||||
result["detections"] = kept
|
||||
result["summary"] = engine.summarize(kept, result.get("empty_slots") or [])
|
||||
result["price_cards_dropped"] = dropped
|
||||
return dropped
|
||||
|
||||
def _attach_still_tags(result: Dict[str, Any], image: Any) -> Dict[str, Any]:
|
||||
"""Run one synchronous tag pass over a still and merge its evidence."""
|
||||
ocr_engine = getattr(engine, "ocr_engine", None)
|
||||
|
|
@ -491,6 +534,9 @@ def create_app(
|
|||
from reconcile import attach_tags_to_boxes
|
||||
from tags import TagService, TagSettings
|
||||
|
||||
# Before anything else: a price card is not a product.
|
||||
_drop_price_cards(result, image)
|
||||
|
||||
boxes = [
|
||||
{"id": detection["id"], "box": detection["box"]} for detection in result["detections"]
|
||||
]
|
||||
|
|
|
|||
61
recognization/code/build_store_from_shelf.py
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
"""
|
||||
Enroll a shelf: the products read off its crops become the store's catalog.
|
||||
|
||||
This is the onboarding flow a real store would go through once - detect the facings,
|
||||
name the products, and keep their crops as the gallery that recognition matches
|
||||
against. The two demo shelves were built with it; the product lists live in this file
|
||||
because reading a contact sheet is a human step.
|
||||
|
||||
python code/build_store_from_shelf.py
|
||||
"""
|
||||
import json, os, shutil, sys
|
||||
sys.path.insert(0, "code")
|
||||
import cv2
|
||||
|
||||
ROOT = os.path.expanduser("~/dorayaki-data/shelf-images")
|
||||
manifest = json.load(open(os.path.join(ROOT, "manifest.json")))
|
||||
entry = next(e for e in manifest["images"] if "Azabudai-Hills" in e["file"])
|
||||
image = cv2.imread(os.path.join(ROOT, entry["file"]))
|
||||
height, width = image.shape[:2]
|
||||
scale = min(1.0, 1600.0 / max(height, width))
|
||||
image = cv2.resize(image, (int(width*scale), int(height*scale)), interpolation=cv2.INTER_AREA)
|
||||
boxes = json.load(open("/tmp/dorayaki-verify/mediya-boxes.json"))["boxes"]
|
||||
|
||||
# read off the numbered contact sheet; prices come from the shelf's own tags
|
||||
PRODUCTS = [
|
||||
("sku_m01", "小岩井 低脂肪牛乳", [9, 23, 31]),
|
||||
("sku_m02", "荻山酪農 カフェ・オ・レ", [10]),
|
||||
("sku_m03", "丹那 3.6牛乳", [11]),
|
||||
("sku_m04", "木次 ミルクコーヒー", [12]),
|
||||
("sku_m05", "木次 パスチャライズ牛乳", [13]),
|
||||
("sku_m06", "キスキ ブラウン・スイス牛乳", [14]),
|
||||
("sku_m07", "丹那 飲んでやさしいヨーグルト", [15]),
|
||||
("sku_m08", "農場3.7牛乳 特選", [24]),
|
||||
("sku_m09", "MINOR FIGURES OAT", [26]),
|
||||
("sku_m10", "北海道 低脂肪乳", [27]),
|
||||
("sku_m11", "タカナシ 低温殺菌牛乳", [28]),
|
||||
("sku_m12", "RUDE HEALTH Oat Organic", [29]),
|
||||
("sku_m13", "小岩井 無脂肪牛乳", [30]),
|
||||
("sku_m14", "きすき ノンホモ牛乳", [8]),
|
||||
]
|
||||
PRICES = {"sku_m01": 186, "sku_m02": 397, "sku_m03": 214, "sku_m04": 195, "sku_m05": 240,
|
||||
"sku_m06": 254, "sku_m07": 225, "sku_m08": 216, "sku_m09": 300, "sku_m10": 214,
|
||||
"sku_m11": 396, "sku_m12": 400, "sku_m13": 178, "sku_m14": 198}
|
||||
|
||||
store = "stock-mediya"
|
||||
shutil.rmtree(store, ignore_errors=True)
|
||||
os.makedirs(store, exist_ok=True)
|
||||
rows = ["SKU_ID,product_name,receipt_date,expiration_date,number_in,number_out,number_on,image_link,price"]
|
||||
for sku, name, indices in PRODUCTS:
|
||||
for position, index in enumerate(indices):
|
||||
box = boxes[index]
|
||||
x1, y1, x2, y2 = [int(v) for v in box]
|
||||
crop = image[max(0,y1):y2, max(0,x1):x2]
|
||||
if crop.size == 0:
|
||||
continue
|
||||
suffix = "" if position == 0 else f"_{position}"
|
||||
cv2.imwrite(os.path.join(store, f"{sku}{suffix}.jpeg"), crop)
|
||||
rows.append(f"{sku},{name},2026-09-25,2026-10-20,24,8,16,'',{PRICES[sku]}")
|
||||
open(os.path.join(store, "stock.csv"), "w").write("\n".join(rows) + "\n")
|
||||
print(f"{store}: {len(PRODUCTS)} products, "
|
||||
f"{sum(len(i) for _, _, i in PRODUCTS)} views, csv written")
|
||||
16
recognization/demo-images/ATTRIBUTION.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
[
|
||||
{
|
||||
"file": "demo-image-1-mediya-dairy.jpg",
|
||||
"title": "File:Azabudai-Hills---2025-04-05 015.jpg",
|
||||
"license": "CC0",
|
||||
"artist": "<a href=\"//commons.wikimedia.org/wiki/User:RuinDig\" title=\"User:RuinDig\">RuinDig/Yuki Uchida</a>",
|
||||
"source": "https://commons.wikimedia.org/wiki/File%3AAzabudai-Hills---2025-04-05_015.jpg"
|
||||
},
|
||||
{
|
||||
"file": "demo-image-2-milk-case.jpg",
|
||||
"title": "File:Milk (2655574568).jpg",
|
||||
"license": "CC BY 2.0",
|
||||
"artist": "<a rel=\"nofollow\" class=\"external text\" href=\"https://www.flickr.com/people/36014533@N00\">yumtan</a>",
|
||||
"source": "https://commons.wikimedia.org/wiki/File%3AMilk_%282655574568%29.jpg"
|
||||
}
|
||||
]
|
||||
63
recognization/demo-images/README.md
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# The two demo shelves
|
||||
|
||||
Two real store photographs, chosen because they are dense and their products are
|
||||
visually distinct, with a store catalog enrolled for each. Both come from Wikimedia
|
||||
Commons under licences that allow reuse; `demo-images/ATTRIBUTION.json` records the
|
||||
author, licence and source of each.
|
||||
|
||||
| | image 1 | image 2 |
|
||||
| --- | --- | --- |
|
||||
| file | `demo-images/demo-image-1-mediya-dairy.jpg` | `demo-images/demo-image-2-milk-case.jpg` |
|
||||
| scene | a MEDIYA dairy case | a supermarket milk case |
|
||||
| licence | **CC0** | **CC BY 2.0** |
|
||||
| detections | 21 products (13 price cards filtered out) | 56 products |
|
||||
| recognised | **16, covering all 14 products** | **49, covering all 13 products** |
|
||||
| store | `stock-mediya/` | `stock-milk150/` |
|
||||
|
||||
## How to run them
|
||||
|
||||
```bash
|
||||
cd recognization
|
||||
YOLO_OFFLINE=1 .venv/bin/python code/app.py \
|
||||
--port 8090 --identity gallery --live-conf 0.25 \
|
||||
--stock-dir stock-mediya # or stock-milk150
|
||||
```
|
||||
|
||||
Then POST the image, or open the web client and use **Photo** mode:
|
||||
|
||||
```bash
|
||||
curl -s -X POST --data-binary @demo-images/demo-image-1-mediya-dairy.jpg \
|
||||
-H 'content-type: image/jpeg' \
|
||||
'http://127.0.0.1:8090/v1/recognize?classify=true&conf=0.25&tags=true' | python3 -m json.tool | head -30
|
||||
```
|
||||
|
||||
## What the demo shows
|
||||
|
||||
* **Every product on the shelf is boxed and named** - 小岩井 低脂肪牛乳,
|
||||
荻山酪農 カフェ・オ・レ, 丹那 3.6牛乳, 木次 ミルクコーヒー, 木次 パスチャライズ牛乳,
|
||||
キスキ ブラウン・スイス牛乳, 丹那 飲んでやさしいヨーグルト, 農場3.7牛乳【特選】,
|
||||
MINOR FIGURES OAT, 北海道 低脂肪乳, タカナシ 低温殺菌牛乳, RUDE HEALTH Oat Organic,
|
||||
小岩井 無脂肪牛乳, きすき ノンホモ牛乳 on the first; 明治 おいしい牛乳, ゆうパック
|
||||
優良牛乳, 酪農家限定 低温殺菌牛乳, 北海道牛乳, MEGMILK, 毎日牛乳, タカナシ 低温殺菌牛乳,
|
||||
タカナシ 殺菌低脂肪牛乳, 毎日骨太, カルシウム入り低脂肪乳, 無脂肪牛乳, 低脂肪牛乳, ミルク
|
||||
on the second.
|
||||
* **Price cards are not boxed as products.** The detector finds them (a card is the
|
||||
shape of a pack); their text gives them away - a card's characters are 30-44 %
|
||||
digits and it prints the tax-excluded price (本体), a product's are 3-19 %. Measured
|
||||
on image 1: 13 cards dropped, 21 products kept.
|
||||
* **What it cannot name, it does not invent.** Boxes on the dark shelf strips and on
|
||||
cards come back as "check" (amber), not as a product name.
|
||||
|
||||
## The honest caveat, for the demo
|
||||
|
||||
Both shelves were **enrolled** - their products were read off the shelf and their
|
||||
crops became the store's catalog and its fine-tuned head, which is the product's own
|
||||
onboarding flow (a store is enrolled once, then scanned). So the recognition rate
|
||||
above is **in-distribution**: it is what the app does on a shelf it has been enrolled
|
||||
on, not on a shelf it has never seen. On shelves it has not been enrolled on, the
|
||||
measured behaviour is: detection transfers (46 of 54 unseen photos), identity does not
|
||||
(off-the-shelf embeddings reach ~25 % top-1 on fine-grained products; see
|
||||
`docs/EXTERNAL-EVAL.md`).
|
||||
|
||||
The first image is CC0, so it can be used in any recording without attribution; the
|
||||
second is CC BY 2.0 and needs the credit in `ATTRIBUTION.json` if it appears on screen.
|
||||
BIN
recognization/demo-images/demo-image-1-mediya-dairy.jpg
Normal file
|
After Width: | Height: | Size: 783 KiB |
BIN
recognization/demo-images/demo-image-2-milk-case.jpg
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
recognization/stock-mediya/identity_head.pt
Normal file
BIN
recognization/stock-mediya/sku_m01.jpeg
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
recognization/stock-mediya/sku_m01_1.jpeg
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
recognization/stock-mediya/sku_m01_2.jpeg
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
recognization/stock-mediya/sku_m02.jpeg
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
recognization/stock-mediya/sku_m03.jpeg
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
recognization/stock-mediya/sku_m04.jpeg
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
recognization/stock-mediya/sku_m05.jpeg
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
recognization/stock-mediya/sku_m06.jpeg
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
recognization/stock-mediya/sku_m07.jpeg
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
recognization/stock-mediya/sku_m08.jpeg
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
recognization/stock-mediya/sku_m09.jpeg
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
recognization/stock-mediya/sku_m10.jpeg
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
recognization/stock-mediya/sku_m11.jpeg
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
recognization/stock-mediya/sku_m12.jpeg
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
recognization/stock-mediya/sku_m13.jpeg
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
recognization/stock-mediya/sku_m14.jpeg
Normal file
|
After Width: | Height: | Size: 13 KiB |
15
recognization/stock-mediya/stock.csv
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
SKU_ID,product_name,receipt_date,expiration_date,number_in,number_out,number_on,image_link,price
|
||||
sku_m01,小岩井 低脂肪牛乳,2026-09-25,2026-10-20,24,8,16,'',186
|
||||
sku_m02,荻山酪農 カフェ・オ・レ,2026-09-25,2026-10-20,24,8,16,'',397
|
||||
sku_m03,丹那 3.6牛乳,2026-09-25,2026-10-20,24,8,16,'',214
|
||||
sku_m04,木次 ミルクコーヒー,2026-09-25,2026-10-20,24,8,16,'',195
|
||||
sku_m05,木次 パスチャライズ牛乳,2026-09-25,2026-10-20,24,8,16,'',240
|
||||
sku_m06,キスキ ブラウン・スイス牛乳,2026-09-25,2026-10-20,24,8,16,'',254
|
||||
sku_m07,丹那 飲んでやさしいヨーグルト,2026-09-25,2026-10-20,24,8,16,'',225
|
||||
sku_m08,農場3.7牛乳 特選,2026-09-25,2026-10-20,24,8,16,'',216
|
||||
sku_m09,MINOR FIGURES OAT,2026-09-25,2026-10-20,24,8,16,'',300
|
||||
sku_m10,北海道 低脂肪乳,2026-09-25,2026-10-20,24,8,16,'',214
|
||||
sku_m11,タカナシ 低温殺菌牛乳,2026-09-25,2026-10-20,24,8,16,'',396
|
||||
sku_m12,RUDE HEALTH Oat Organic,2026-09-25,2026-10-20,24,8,16,'',400
|
||||
sku_m13,小岩井 無脂肪牛乳,2026-09-25,2026-10-20,24,8,16,'',178
|
||||
sku_m14,きすき ノンホモ牛乳,2026-09-25,2026-10-20,24,8,16,'',198
|
||||
|
BIN
recognization/stock-milk150/identity_head.pt
Normal file
BIN
recognization/stock-milk150/sku_k01.jpeg
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
recognization/stock-milk150/sku_k01_1.jpeg
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
recognization/stock-milk150/sku_k01_2.jpeg
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
recognization/stock-milk150/sku_k01_3.jpeg
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
recognization/stock-milk150/sku_k01_4.jpeg
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
recognization/stock-milk150/sku_k01_5.jpeg
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
recognization/stock-milk150/sku_k01_6.jpeg
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
recognization/stock-milk150/sku_k01_7.jpeg
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
recognization/stock-milk150/sku_k01_8.jpeg
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
recognization/stock-milk150/sku_k02.jpeg
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
recognization/stock-milk150/sku_k02_1.jpeg
Normal file
|
After Width: | Height: | Size: 4 KiB |
BIN
recognization/stock-milk150/sku_k02_2.jpeg
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
recognization/stock-milk150/sku_k02_3.jpeg
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
recognization/stock-milk150/sku_k02_4.jpeg
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
recognization/stock-milk150/sku_k02_5.jpeg
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
recognization/stock-milk150/sku_k02_6.jpeg
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
recognization/stock-milk150/sku_k03.jpeg
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
recognization/stock-milk150/sku_k03_1.jpeg
Normal file
|
After Width: | Height: | Size: 6 KiB |
BIN
recognization/stock-milk150/sku_k03_2.jpeg
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
recognization/stock-milk150/sku_k04.jpeg
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
recognization/stock-milk150/sku_k04_1.jpeg
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
recognization/stock-milk150/sku_k04_2.jpeg
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
recognization/stock-milk150/sku_k04_3.jpeg
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
recognization/stock-milk150/sku_k04_4.jpeg
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
recognization/stock-milk150/sku_k04_5.jpeg
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
recognization/stock-milk150/sku_k04_6.jpeg
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
recognization/stock-milk150/sku_k04_7.jpeg
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
recognization/stock-milk150/sku_k04_8.jpeg
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
recognization/stock-milk150/sku_k05.jpeg
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
recognization/stock-milk150/sku_k05_1.jpeg
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
recognization/stock-milk150/sku_k05_2.jpeg
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
recognization/stock-milk150/sku_k06.jpeg
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
recognization/stock-milk150/sku_k07.jpeg
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
recognization/stock-milk150/sku_k07_1.jpeg
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
recognization/stock-milk150/sku_k08.jpeg
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
recognization/stock-milk150/sku_k08_1.jpeg
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
recognization/stock-milk150/sku_k09.jpeg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
recognization/stock-milk150/sku_k09_1.jpeg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
recognization/stock-milk150/sku_k09_2.jpeg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
recognization/stock-milk150/sku_k10.jpeg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
recognization/stock-milk150/sku_k10_1.jpeg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
recognization/stock-milk150/sku_k10_2.jpeg
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
recognization/stock-milk150/sku_k11.jpeg
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
recognization/stock-milk150/sku_k11_1.jpeg
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
recognization/stock-milk150/sku_k12.jpeg
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
recognization/stock-milk150/sku_k13.jpeg
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
14
recognization/stock-milk150/stock.csv
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
SKU_ID,product_name,receipt_date,expiration_date,number_in,number_out,number_on,image_link,price
|
||||
sku_k01,明治 おいしい牛乳,2026-09-25,2026-10-15,48,20,28,'',198
|
||||
sku_k02,ゆうパック 優良牛乳,2026-09-25,2026-10-15,48,20,28,'',130
|
||||
sku_k03,酪農家限定 低温殺菌牛乳,2026-09-25,2026-10-15,48,20,28,'',248
|
||||
sku_k04,北海道牛乳,2026-09-25,2026-10-15,48,20,28,'',178
|
||||
sku_k05,MEGMILK,2026-09-25,2026-10-15,48,20,28,'',158
|
||||
sku_k06,毎日牛乳,2026-09-25,2026-10-15,48,20,28,'',168
|
||||
sku_k07,タカナシ 低温殺菌牛乳,2026-09-25,2026-10-15,48,20,28,'',258
|
||||
sku_k08,タカナシ 殺菌低脂肪牛乳,2026-09-25,2026-10-15,48,20,28,'',198
|
||||
sku_k09,毎日骨太,2026-09-25,2026-10-15,48,20,28,'',148
|
||||
sku_k10,カルシウム入り低脂肪乳,2026-09-25,2026-10-15,48,20,28,'',145
|
||||
sku_k11,無脂肪牛乳,2026-09-25,2026-10-15,48,20,28,'',138
|
||||
sku_k12,低脂肪牛乳,2026-09-25,2026-10-15,48,20,28,'',120
|
||||
sku_k13,ミルク,2026-09-25,2026-10-15,48,20,28,'',260
|
||||
|