feat(backend): scan-product accuracy 66.2% -> 79.7% + frozen validation benchmark
Accuracy work on the 79-image product-scan validation set (user goal: 90%): - classify_ocr_server.py: 0/90/180/270-degree expiry-date search (stops at first hit, 0-degree fallback); classification decoupled onto the upright image (rotated frames regressed DINOv2 -6pts until this); cross-line date stitching; tiled full-res OCR pass (defeats the 4000px downscale that killed small inkjet dates); VL-pipeline expiry fallback with keyword-anchored anti-hallucination guard; VL text lines merged into text_lines + VL SKU retry. Visualization endpoints removed entirely (Visual/Spotting grids - unused by frontend, 3x per-scan GPU cost). - product-scan.ts: coverage-normalized OCR-evidence re-ranking of DINOv2 top-K (tuned offline: +8/-0 on top-1 misses), re-ranked class mapped to sku_master by SKU prefix; classifier timeout 90s->240s for fallback paths. - Frozen benchmark: product-test-images-fixed/ (79 renamed images) + freeze/seed/build-undetected/capture/experiment scripts; labels trimmed to the 79 validation entries (training rows kept in .bak-with-training); 5 TRAINED-ON SKUs replaced with fresh held-out photos. - manual-label-scan page: shows last batch-test AI prediction under every field by default (new /api/product-scan-results); serves the fixed folder; fixed total hydration failure via allowedDevOrigins 127.0.0.1. - Measured (all-79, zero failures): sku/name 87.3%, expiry 64.6%, overall 79.7%. Tiles/VL-evidence/VL-SKU deployed but not yet batch-measured. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gr6HH7JrdsXX8AARejQboM
This commit is contained in:
1 parent
19f1facf9b
commit
e76ccb60a6
156 files changed
+17150
-1405
No files matched your search
@@ -0,0 +1,130 @@
|
||||
// One-off script: fill ground-truth labels for backend/sources/product-test-images/
|
||||
// (the product-scan accuracy harness's Validation Set, previously 0 labeled images).
|
||||
// Run once from backend/: node scripts/seed-validation-labels.mjs
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
const IMAGES_DIR = path.join("sources", "product-test-images");
|
||||
const LABELS_PATH = path.join("sources", "product_manual_labels.json");
|
||||
|
||||
// [no_sku, nama_item, expiry_date ("" = not legible in photo, needs re-shoot)]
|
||||
const DATA = [
|
||||
["11110059", "CEKER BERKUKU FROZEN PACK 1 KG(*)", "13/06/2027"],
|
||||
["11140051", "AMPELA FROZEN PACK 1 KG(*)", "26/11/2026"],
|
||||
["11620056", "SBL (FILLET PAHA) 1 KG(*)", "27/02/2027"],
|
||||
["11650053", "PAHA ATAS 1 KG(*)", "26/02/2027"],
|
||||
["11660050", "PAHA BAWAH (1 KG)(*)", "22/06/2027"],
|
||||
["11710051", "DADA UTUH (1 KG)(*)", ""],
|
||||
["11818300", "CP-BEBEK GORENG 400GR/PAC", ""],
|
||||
["1195008A", "RTC CHICKEN KALASAN 400 GR (PAC)", ""],
|
||||
["11959937", "SATE AYAM FRESHMART 360 GR (PAC)", "24/11/2026"],
|
||||
["12010111", "FIESTA CRISPY BUBBLE 400 GR/PAC", "07/05/2027"],
|
||||
["12010115", "FIESTA NUGGET ZOO 400 GR/PAC", "01/12/2026"],
|
||||
["12010117", "FIESTA NUGGET HAPPY STAR 400 GR/PAC", "27/08/2026"],
|
||||
["12010119", "FIESTA NUGGET CHEESE 123 400 GR/PAC", "09/04/2027"],
|
||||
["12010121", "FIESTA NUGGET PIZZABC 400 GR/PAC", "12/11/2026"],
|
||||
["12010127", "FIESTA SPICY NUGGET 400 GR/PAC", "09/12/2027"],
|
||||
["12010509", "CHAMP CRUNCHY NUGGET 450 GR/PAC", "15/04/2027"],
|
||||
["12010515", "CHAMP KOIN KOMBINASI 450 GR/PAC", "15/04/2027"],
|
||||
["12010519", "CHAMP NUGGET STICK 900 GR/PAC", "08/03/2027"],
|
||||
["12012202", "ASIMO NUGGET KOMBINASI 1 KG/PAC", "13/05/2027"],
|
||||
["12012501", "AKUMO CHICKEN NAGET 250 GR", "21/05/2027"],
|
||||
["12012503", "AKUMO CHICKEN NUGGET 1000 GR", "17/06/2027"],
|
||||
["12012505", "AKUMO KOIN 400 GR/PAC", "16/11/2026"],
|
||||
["12020102", "FIESTA SPICY WING 400 GR/PAC", "22/05/2027"],
|
||||
["12030102", "FIESTA STIKIE 200 GR/PAC", "26/02/2027"],
|
||||
["12030403", "GOLDEN FIESTA STIKIE W/ SWEET CHILLI SAUCE 500GR", "07/04/2027"],
|
||||
["12032502", "AKUMO CHICKEN STIK 500 GR", "09/03/2027"],
|
||||
["12040101", "FIESTA SCHNITZEL 400 GR/PAC", "09/10/2026"],
|
||||
["12040102", "FIESTA CRISPY BUBBLE KATSU 400 GR/PAC", "12/04/2027"],
|
||||
["12060103", "FIESTA KARAGE 200 GR/PAC", ""],
|
||||
["12060402", "GOLDEN FIESTA KARAGE CHILI SAUCE 500GR", "15/05/2027"],
|
||||
["12080101", "FIESTA SPICY CHICK 400 GR/PAC", "26/04/2027"],
|
||||
["12130102", "FIESTA CRISPY BURGER 360 GR (NEW)", "27/04/2027"],
|
||||
["12150201", "FIESTA DS CRISPY CRUNCH 300 GR/PAC", "03/06/2027"],
|
||||
["12150501", "CHAMP CRUNCHY HOTZZ 300 GR/PAC", ""],
|
||||
["12190103", "FIESTA DELISTRIPE 400 GR/PAC", "07/04/2027"],
|
||||
["12240103", "FIESTA YAKINIKU R/BITES 400 GR/PAC", "07/05/2027"],
|
||||
["13010111", "FIESTA SOSIS BRATWURST 300 GR", "25/03/2027"],
|
||||
["13010116", "FIESTA SSG ORIGINAL 300 GR", "23/06/2027"],
|
||||
["13010118", "FIESTA RTG SSG 65 GR/PAC", ""],
|
||||
["13010120", "FIESTA RTG C/CHEESY MELTS 65 GR/PAC", ""],
|
||||
["13010122", "FIESTA RTG SAUSAGE WITH HOT LAVA 60G", ""],
|
||||
["13010123", "FIESTA RTG SAUSAGE WITH CHEESE LAVA 60G", "25/10/2026"],
|
||||
["13010125", "FIESTA RTG SAUSAGE WITH MENTAI LAVA 60GR", "05/11/2026"],
|
||||
["13010518", "CHAMP SSG JUMBO BAKAR 500 GR/PAC", ""],
|
||||
["13010524", "CHAMP SSG JUMBO BAKAR 500 GR/PAC (NEW)", ""],
|
||||
["13012206", "ASIMO SOSIS AYAM KOMBINASI 500 GR", "15/03/2027"],
|
||||
["13030501", "CHAMP CHICK MEATBALL 200 GR", ""],
|
||||
["13070506", "CHAMP FRANKFURTER SSG 375GR", "13/03/2027"],
|
||||
["13100512", "CHAMP CHICK SSG S/SANTAP ORIG 546GR (CAN)", ""],
|
||||
["15010101", "FIESTA SHOESTRING 500 GR", "04/06/2027"],
|
||||
["15010102", "FIESTA SHOESTRING 1000 GR", "05/03/2027"],
|
||||
["15010107", "FIESTA FRENCH F SHOESTRING INSTITUSI 2KG", "17/06/2027"],
|
||||
["15020101", "FIESTA STRAIGHT CUT 500 GR", "19/06/2027"],
|
||||
["15020102", "FIESTA STRAIGHT CUT 1000 GR", "18/05/2027"],
|
||||
["15030101", "FIESTA CRINKLE CUT 500 GR", ""],
|
||||
["15030102", "FIESTA CRINKLE CUT 1000 GR", "07/04/2027"],
|
||||
["16060113", "FIESTA CHICK SIOMAY 180GR (NEW)", "24/02/2027"],
|
||||
["16060114", "FIESTA GYOZA 180 GR (NEW)", "18/05/2027"],
|
||||
["17200109", "FIESTA RTS C/TERIYAKI 300GR/PAC", ""],
|
||||
["17210106", "FIESTA RTS B/YAKINIKU 300GR/PAC", "19/05/2027"],
|
||||
["17210107", "FIESTA RTS B/RENDANG 300GR/PAC", "23/06/2027"],
|
||||
["17210108", "FIESTA RTS B/BLACKPEPPER 300GR/PAC", "09/05/2027"],
|
||||
["17210109", "FIESTA RTS B/BULGOGI 300GR/PAC", "18/05/2027"],
|
||||
["20040101", "FIESTA RAMEN BEKU 570 GR/PAC", "23/06/2026"],
|
||||
["20120102", "FIESTA T/B AYAM GORENG 80 GR", ""],
|
||||
["20120105", "FIESTA T/B SERBAGUNA Â 80 GR", "09/03/2027"],
|
||||
["20120115", "FIESTA RACIK AYAM GORENG 20 GR/PAC", ""],
|
||||
["20120116", "FIESTA RACIK NASI GORENG 20 GR/PAC", "13/10/2026"],
|
||||
["21000123", "FIESTA RICE W/GEPREK CHICKEN 320GR/PAC", "30/04/2027"],
|
||||
["21000126", "NEW FIESTA CHICK RENDANG W RICE 320GR (PAC)", "16/04/2027"],
|
||||
["21000130", "NEW FIESTA RICE W/C CHEESE BULDAK 320GR (PAC)", "09/04/2027"],
|
||||
["21000137", "FIESTA HAINAMESE CHICKEN RICE 320GR (PAC)", "12/02/2027"],
|
||||
["21010101", "FIESTA TRUFFLE GYUDON 320 GR/PAC", "18/03/2027"],
|
||||
["21200107", "NEW FIESTA SPAGHETTI CARBONARA 300GR (PAC)", "20/05/2027"],
|
||||
];
|
||||
|
||||
const files = fs
|
||||
.readdirSync(IMAGES_DIR)
|
||||
.filter((f) => f !== "README.md" && f !== "filelist.txt")
|
||||
.sort();
|
||||
|
||||
if (files.length !== DATA.length) {
|
||||
throw new Error(`File count ${files.length} != DATA count ${DATA.length}`);
|
||||
}
|
||||
|
||||
const existing = JSON.parse(fs.readFileSync(LABELS_PATH, "utf8"));
|
||||
const now = new Date().toISOString();
|
||||
|
||||
let added = 0;
|
||||
let skipped = 0;
|
||||
let unreadable = 0;
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const filename = files[i];
|
||||
const [no_sku, nama_item, expiry_date] = DATA[i];
|
||||
|
||||
if (existing.some((l) => l.filename === filename)) {
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!expiry_date) unreadable++;
|
||||
|
||||
existing.push({
|
||||
filename,
|
||||
no_sku,
|
||||
nama_item,
|
||||
expiry_date,
|
||||
top1_confidence: null,
|
||||
notes: expiry_date
|
||||
? ""
|
||||
: "expiry date not legible in photo (cropped/blurry/out of frame) - needs re-shoot",
|
||||
saved_at: now,
|
||||
});
|
||||
added++;
|
||||
}
|
||||
|
||||
fs.writeFileSync(LABELS_PATH, JSON.stringify(existing, null, 2), "utf8");
|
||||
console.log(`Added ${added} labels (${unreadable} flagged with no expiry_date), skipped ${skipped} already-labeled.`);
|
||||
Reference in new issue
Block a user