diff --git a/backend/docs/feature-list.md b/backend/docs/feature-list.md index cb39048..9994502 100644 --- a/backend/docs/feature-list.md +++ b/backend/docs/feature-list.md @@ -71,7 +71,8 @@ workflow and have no task numbers; see `git log` for real dates/history. - **6.1** Built standalone annotation page `manual-label-scan/page.tsx` for ground truth editing. Includes image browser, editable fields (`no_sku`, `nama_item`, `expiry_date`, `notes`), and a "Scan with AI" fill-blanks feature — shipped 2026-07-08. - **6.2** API + storage groundwork for scan annotation. Extended `api/manual-label-scan` with `GET` list mode and `DELETE`. Persisted uploaded scan photos as base64 images into `sources/product-test-images/`. Made the `scan-pfm` quick-save honest by allowing manual correction before save — shipped 2026-07-08. -- **6.3** Built `backend/pfm-web-app/scripts/accuracy-check-scan.mts` mirroring the DO-harness architecture, measuring overall match rate plus per-field breakdown (`no_sku`, `expiry_date`) against the new stable labels — shipped 2026-07-08. +- **6.3** Built `backend/scripts/accuracy-check-scan.mts` mirroring the DO-harness architecture, measuring overall match rate plus per-field breakdown (`no_sku`, `expiry_date`) against the new stable labels — shipped 2026-07-08. +- **6.4** Ported the DO-harness's auto-diff-vs-previous-run reporting into `accuracy-check-scan.mts`: every run now prints a Δ column per field per split (Training/Validation) vs the last `product_accuracy_history.jsonl` entry, and calls out field- and image-level regressions/improvements explicitly. Added classifier method (`dinov2_similarity`/`yolo_classifier`) distribution and average confidence as informational (non-scoring) context. Created the previously-missing `sources/product-test-images/README.md` documenting the validation-photo drop workflow — shipped 2026-07-13, user-directed `n` request to make algorithm tuning self-verifying. ### Master Data Management - **8.1 & 8.3 CRUD APIs and Web UI**: Created `/api/v1/master/stores` and `/api/v1/master/skus` endpoints alongside a Next.js Admin page (`/admin/master-data`) to visually manage the core reference data used by the OCR matching engine — shipped 2026-07-08. diff --git a/backend/docs/scan-product.md b/backend/docs/scan-product.md index 25b052f..ca157b9 100644 --- a/backend/docs/scan-product.md +++ b/backend/docs/scan-product.md @@ -136,6 +136,32 @@ get mangled. Verify in `docker logs`: "DINOv2 index loaded with N reference images", "Using classifier weights: ". Full worked example: `plans/next-enhancements.md` task 2.1. +## Accuracy regression harness + +`backend/scripts/accuracy-check-scan.mts` — mirrors the DO-flow's +`pfm-web-app/scripts/accuracy-check.mts`. Hits the live `/api/scan-pfm` for +every labeled image in `sources/product_manual_labels.json`, checks 3 fields +(`no_sku`, `nama_item`, `expiry_date`) against ground truth, and splits into: +- **Training Set** — gallery photos under `foto-kemasan-v2/` (the classifier's + own reference images; scores here measure memorization, not generalization). +- **Validation Set** — flat filenames dropped into + `sources/product-test-images/` (a real held-out set; see that folder's + `README.md` for the drop-photo → label → re-run workflow via + `/manual-label-scan`). + +Every run appends to `sources/product_accuracy_history.jsonl` and **auto-diffs +against the previous run**: the printed summary shows a Δ column per field per +split, flags field/image-level regressions and improvements, and reports +classifier method (`dinov2_similarity`/`yolo_classifier`) distribution + +average confidence as informational context (not scored pass/fail, since +DINOv2's "confidence" is a raw cosine similarity, not a calibrated +probability — see Stage 1 above). This is what makes it safe to tune +`classify_ocr_server.py` and immediately see whether a change helped or hurt. + +```bash +node scripts/accuracy-check-scan.mts # from backend/ +``` + ## Operational notes - **Env vars**: `CLASSIFIER_SERVER_URL`, `PIPELINE_URL` (gateway, set in compose); @@ -156,20 +182,18 @@ images", "Using classifier weights: ". Full worked example: ## Known gaps & future recommendations Tracked ones (see `plans/next-enhancements.md`): -- **§6.1–6.3 ground truth**: today's "Save Ground Truth" stores the model's own - predictions (only the SKU is editable) and uploads get phantom - `uploaded-.jpg` keys with no image persisted; §6 plans the editable - annotation page, persisted uploads, and a scan accuracy harness. - **Dataset thinness**: 2–16 photos/class caps both classifiers; every new real - photo (especially non-studio, in-warehouse shots) matters. The §6.3 harness - should report gallery vs. uploaded-photo accuracy separately — gallery photos - are training data, so scores on them measure memorization. + photo (especially non-studio, in-warehouse shots) matters. The harness above + already reports gallery (training) vs. held-out (validation) accuracy + separately — but as of this writing `sources/product-test-images/` is empty, + so the Validation Set is still 0 images and every published number so far is + a training/memorization score. Dropping real photos there is the next step, + not yet done. Additional recommendations (not yet tasks — promote via `e`/`n` when wanted): -1. **Use `extracted_sku` in match ranking.** An exact 8-digit SKU hit read off - the label is far stronger evidence than fuzzy name similarity, yet ranking - currently ignores it. Suggested: exact `no_sku` match pins rank 1; blend name - similarity for the rest. +1. ~~Use `extracted_sku` in match ranking.~~ **Done** — `product-scan.ts`'s + `classifyAndMatchProduct` already pins rank 1 to an exact `no_sku` match + (score forced to 1.0) before falling back to name similarity. 2. **Fuse DINOv2 and YOLO instead of primary/fallback** (e.g. agreement boosts confidence; disagreement flags for review) — cheap, both already load. 3. **"Not a known product" handling**: DINOv2 always returns *some* class; add a diff --git a/backend/scripts/accuracy-check-scan.mts b/backend/scripts/accuracy-check-scan.mts index 789f0e5..3d2d472 100644 --- a/backend/scripts/accuracy-check-scan.mts +++ b/backend/scripts/accuracy-check-scan.mts @@ -1,3 +1,20 @@ +// Product-scan (scan-pfm) accuracy regression tool. +// +// Hits the live /api/scan-pfm endpoint for every labeled image in +// backend/sources/product_manual_labels.json, checks 3 fields (no_sku, +// nama_item, expiry_date) against ground truth, splits results into a +// Training Set (gallery photos under foto-kemasan-v2/ that trained the +// classifier itself) vs a Validation Set (flat filenames dropped in +// backend/sources/product-test-images/), and appends a summary to +// backend/sources/product_accuracy_history.jsonl. Every run auto-diffs +// against the last history entry and flags field/image regressions or +// improvements, so a tuning change to classify_ocr_server.py shows its +// effect immediately instead of requiring manual before/after comparison. +// +// Usage: +// node scripts/accuracy-check-scan.mts +// node scripts/accuracy-check-scan.mts --base-url http://localhost:3000 + import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; @@ -8,10 +25,15 @@ const __dirname = path.dirname(__filename); const APP_ROOT = path.join(__dirname, "..", "pfm-web-app"); const SOURCES_DIR = path.join(__dirname, "..", "sources"); -const LABELS_PATH = path.join(SOURCES_DIR, "product_manual_labels.json"); -const HISTORY_PATH = path.join(SOURCES_DIR, "product_accuracy_history.jsonl"); +// Overridable for local smoke-testing against a scratch dataset without +// touching the real ground-truth/history files. +const LABELS_PATH = process.env.ACCURACY_LABELS_PATH || path.join(SOURCES_DIR, "product_manual_labels.json"); +const HISTORY_PATH = process.env.ACCURACY_HISTORY_PATH || path.join(SOURCES_DIR, "product_accuracy_history.jsonl"); const FETCH_TIMEOUT_MS = 120_000; +const FIELDS = ["no_sku", "nama_item", "expiry_date"] as const; +type Field = typeof FIELDS[number]; +type Split = "training" | "validation"; interface GroundTruth { filename: string; @@ -24,6 +46,7 @@ interface ScanResponse { classification?: { top1_name: string; top1_confidence: number; + method?: string; }; ocr?: { extracted_expired_date: string; @@ -36,19 +59,30 @@ interface ScanResponse { } interface Check { - field: "no_sku" | "nama_item" | "expiry_date"; + field: Field; match: boolean; } +interface ResultItem { + gt: GroundTruth; + checks: Check[]; + method?: string; + confidence?: number; +} + +interface ClassificationStats { + methodCounts: Record; + avgConfidence: number; +} + interface HistoryEntry { timestamp: string; commit: string; imageCount: { training: number; validation: number }; failedImages: string[]; - fields: { - training: Record; - validation: Record; - }; + fields: Record>; + classification: Record; + perImage: Record; } function parseArgs(argv: string[]) { @@ -107,6 +141,187 @@ function getGitCommit(): string { function pct(c: number, t: number) { return t === 0 ? 0 : (c / t) * 100; } function fmtPct(n: number) { return `${n.toFixed(1)}%`; } +function fmtDelta(curr: number, prev: number | undefined): string { + if (prev === undefined) return ""; + const d = curr - prev; + if (Math.abs(d) < 0.05) return "±0.0"; + const sign = d > 0 ? "+" : ""; + return `${sign}${d.toFixed(1)}`; +} + +function loadLastHistoryEntry(): HistoryEntry | null { + if (!fs.existsSync(HISTORY_PATH)) return null; + const lines = fs.readFileSync(HISTORY_PATH, "utf8").trim().split("\n").filter(Boolean); + if (lines.length === 0) return null; + try { + return JSON.parse(lines[lines.length - 1]); + } catch { + return null; + } +} + +function aggregateFields(list: ResultItem[]): Record { + const agg = { + no_sku: { correct: 0, total: 0 }, + nama_item: { correct: 0, total: 0 }, + expiry_date: { correct: 0, total: 0 } + } as Record; + for (const item of list) { + for (const check of item.checks) { + agg[check.field].total++; + if (check.match) agg[check.field].correct++; + } + } + return agg; +} + +function overallFromFields(fields: Record | undefined) { + if (!fields) return undefined; + let correct = 0, total = 0; + for (const f of FIELDS) { + const s = fields[f]; + if (s) { correct += s.correct; total += s.total; } + } + return { correct, total }; +} + +function aggregateClassification(list: ResultItem[]): ClassificationStats { + const methodCounts: Record = {}; + let confSum = 0; + let confCount = 0; + for (const item of list) { + if (item.method) methodCounts[item.method] = (methodCounts[item.method] || 0) + 1; + if (typeof item.confidence === "number") { + confSum += item.confidence; + confCount++; + } + } + return { methodCounts, avgConfidence: confCount ? confSum / confCount : 0 }; +} + +function printClassificationLine(label: string, stats: ClassificationStats, prevStats: ClassificationStats | undefined) { + const methodStr = Object.entries(stats.methodCounts).map(([m, c]) => `${m}=${c}`).join(", ") || "n/a"; + const confStr = stats.avgConfidence ? stats.avgConfidence.toFixed(3) : "n/a"; + let suffix = ""; + if (prevStats && prevStats.avgConfidence) { + const delta = fmtDelta(stats.avgConfidence, prevStats.avgConfidence); + suffix = delta ? ` (Δ ${delta} vs prev)` : ""; + } + console.log(` ${label.padEnd(11)}: ${methodStr.padEnd(28)} avg confidence ${confStr}${suffix}`); +} + +function printSummary( + trainAgg: Record, + valAgg: Record, + trainClassStats: ClassificationStats, + valClassStats: ClassificationStats, + perImagePct: Record, + prev: HistoryEntry | null, + imageCount: { training: number; validation: number }, + failedImages: string[] +) { + console.log("\n=== Product Scan Accuracy Summary ==="); + console.log(`Training Images: ${imageCount.training} | Validation Images: ${imageCount.validation} | Failed: ${failedImages.length}\n`); + + const fieldCol = 14, numCol = 9, deltaCol = 8; + const header = + "Field".padEnd(fieldCol) + + "Training".padStart(numCol) + "Δ".padStart(deltaCol) + " " + + "Validation".padStart(numCol) + "Δ".padStart(deltaCol); + console.log(header); + console.log("-".repeat(header.length)); + + const overallTrain = { correct: 0, total: 0 }; + const overallVal = { correct: 0, total: 0 }; + + for (const field of FIELDS) { + const t = trainAgg[field]; + const v = valAgg[field]; + overallTrain.correct += t.correct; overallTrain.total += t.total; + overallVal.correct += v.correct; overallVal.total += v.total; + + const tPct = pct(t.correct, t.total); + const vPct = pct(v.correct, v.total); + const tPrevStat = prev?.fields?.training?.[field]; + const vPrevStat = prev?.fields?.validation?.[field]; + const tPrevPct = tPrevStat?.total ? pct(tPrevStat.correct, tPrevStat.total) : undefined; + const vPrevPct = vPrevStat?.total ? pct(vPrevStat.correct, vPrevStat.total) : undefined; + + const tStr = t.total ? fmtPct(tPct) : "n/a"; + const vStr = v.total ? fmtPct(vPct) : "n/a"; + + console.log( + field.padEnd(fieldCol) + + tStr.padStart(numCol) + fmtDelta(tPct, tPrevPct).padStart(deltaCol) + " " + + vStr.padStart(numCol) + fmtDelta(vPct, vPrevPct).padStart(deltaCol) + ); + } + console.log("-".repeat(header.length)); + + const tOverallPct = pct(overallTrain.correct, overallTrain.total); + const vOverallPct = pct(overallVal.correct, overallVal.total); + const prevTrainOverall = overallFromFields(prev?.fields?.training); + const prevValOverall = overallFromFields(prev?.fields?.validation); + const tOverallPrevPct = prevTrainOverall?.total ? pct(prevTrainOverall.correct, prevTrainOverall.total) : undefined; + const vOverallPrevPct = prevValOverall?.total ? pct(prevValOverall.correct, prevValOverall.total) : undefined; + + console.log( + "OVERALL".padEnd(fieldCol) + + fmtPct(tOverallPct).padStart(numCol) + fmtDelta(tOverallPct, tOverallPrevPct).padStart(deltaCol) + " " + + fmtPct(vOverallPct).padStart(numCol) + fmtDelta(vOverallPct, vOverallPrevPct).padStart(deltaCol) + ); + + if (failedImages.length) { + console.log(`\nFailed to parse: ${failedImages.join(", ")}`); + } + + // Informational only - DINOv2 "confidence" is a raw cosine similarity, not a + // calibrated probability (see docs/scan-product.md), so a delta here doesn't + // by itself mean better/worse. Only the fields above drive regression flags. + console.log("\nClassification (informational, not scored as pass/fail):"); + printClassificationLine("Training", trainClassStats, prev?.classification?.training); + printClassificationLine("Validation", valClassStats, prev?.classification?.validation); + + if (prev) { + const fieldRegressions: string[] = []; + const fieldImprovements: string[] = []; + for (const split of ["training", "validation"] as Split[]) { + const agg = split === "training" ? trainAgg : valAgg; + for (const field of FIELDS) { + const stat = agg[field]; + if (!stat.total) continue; + const currPct = pct(stat.correct, stat.total); + const prevStat = prev.fields?.[split]?.[field]; + if (!prevStat?.total) continue; + const prevPct = pct(prevStat.correct, prevStat.total); + const d = currPct - prevPct; + const label = `${field} (${split})`; + if (d <= -0.05) fieldRegressions.push(`${label} ${fmtDelta(currPct, prevPct)}`); + else if (d >= 0.05) fieldImprovements.push(`${label} ${fmtDelta(currPct, prevPct)}`); + } + } + if (fieldRegressions.length) console.log(`\nField regressions: ${fieldRegressions.join(", ")}`); + if (fieldImprovements.length) console.log(`Field improvements: ${fieldImprovements.join(", ")}`); + + const imageRegressions: string[] = []; + const imageImprovements: string[] = []; + for (const [filename, currPct] of Object.entries(perImagePct)) { + const prevPct = prev.perImage?.[filename]; + if (prevPct === undefined) continue; + const d = currPct - prevPct; + if (d <= -0.5) imageRegressions.push(`${filename} ${fmtDelta(currPct, prevPct)}`); + else if (d >= 0.5) imageImprovements.push(`${filename} ${fmtDelta(currPct, prevPct)}`); + } + if (imageRegressions.length) console.log(`\nImage regressions: ${imageRegressions.join(", ")}`); + if (imageImprovements.length) console.log(`Image improvements: ${imageImprovements.join(", ")}`); + + console.log(`\n(vs run at ${prev.timestamp}${prev.commit !== "unknown" ? `, commit ${prev.commit}` : ""})`); + } else { + console.log("\n(no previous run in product_accuracy_history.jsonl — this is the baseline)"); + } + console.log(""); +} + async function main() { const args = parseArgs(process.argv.slice(2)); await checkServerReachable(args.baseUrl); @@ -116,12 +331,14 @@ async function main() { process.exit(1); } const labels: GroundTruth[] = JSON.parse(fs.readFileSync(LABELS_PATH, "utf8")); + const prev = loadLastHistoryEntry(); const results = { - training: [] as { gt: GroundTruth; checks: Check[] }[], - validation: [] as { gt: GroundTruth; checks: Check[] }[], + training: [] as ResultItem[], + validation: [] as ResultItem[], failed: [] as string[] }; + const perImagePct: Record = {}; for (const gt of labels) { if (gt.filename.startsWith("uploaded-")) continue; // Skip phantom @@ -135,7 +352,7 @@ async function main() { try { const b64 = "data:image/jpeg;base64," + fs.readFileSync(imgPath, "base64"); const parsed = await fetchScan(args.baseUrl, b64); - + const bestMatchSku = parsed.possibleMatches?.find(m => m.isBestMatch)?.no_sku || ""; const predictedItemName = parsed.classification?.top1_name || ""; const predictedExpiry = parsed.ocr?.extracted_expired_date || ""; @@ -146,13 +363,21 @@ async function main() { { field: "expiry_date", match: isMatch(gt.expiry_date, predictedExpiry) } ]; + const item: ResultItem = { + gt, + checks, + method: parsed.classification?.method, + confidence: parsed.classification?.top1_confidence + }; + if (gt.filename.includes("/")) { - results.training.push({ gt, checks }); + results.training.push(item); } else { - results.validation.push({ gt, checks }); + results.validation.push(item); } - + const score = checks.filter(c => c.match).length; + perImagePct[gt.filename] = (score / checks.length) * 100; console.log(`done (${score}/3)`); } catch (err) { console.log(`FAILED (${(err as Error).message})`); @@ -160,42 +385,22 @@ async function main() { } } - const aggregate = (list: { checks: Check[] }[]) => { - const agg: Record = { - no_sku: { correct: 0, total: 0 }, - nama_item: { correct: 0, total: 0 }, - expiry_date: { correct: 0, total: 0 } - }; - for (const item of list) { - for (const check of item.checks) { - agg[check.field].total++; - if (check.match) agg[check.field].correct++; - } - } - return agg; - }; + const trainAgg = aggregateFields(results.training); + const valAgg = aggregateFields(results.validation); + const trainClassStats = aggregateClassification(results.training); + const valClassStats = aggregateClassification(results.validation); + const imageCount = { training: results.training.length, validation: results.validation.length }; - const trainAgg = aggregate(results.training); - const valAgg = aggregate(results.validation); - - console.log("\n=== Product Scan Accuracy Summary ==="); - console.log(`Training Images: ${results.training.length} | Validation Images: ${results.validation.length} | Failed: ${results.failed.length}\n`); - - console.log("Field | Training Set | Validation Set"); - console.log("---------------|--------------|---------------"); - ["no_sku", "nama_item", "expiry_date"].forEach(f => { - const t = trainAgg[f].total ? fmtPct(pct(trainAgg[f].correct, trainAgg[f].total)) : "n/a"; - const v = valAgg[f].total ? fmtPct(pct(valAgg[f].correct, valAgg[f].total)) : "n/a"; - console.log(`${f.padEnd(14)} | ${t.padEnd(12)} | ${v.padEnd(14)}`); - }); - console.log(""); + printSummary(trainAgg, valAgg, trainClassStats, valClassStats, perImagePct, prev, imageCount, results.failed); const entry: HistoryEntry = { timestamp: new Date().toISOString(), commit: getGitCommit(), - imageCount: { training: results.training.length, validation: results.validation.length }, + imageCount, failedImages: results.failed, - fields: { training: trainAgg, validation: valAgg } + fields: { training: trainAgg, validation: valAgg }, + classification: { training: trainClassStats, validation: valClassStats }, + perImage: perImagePct }; fs.appendFileSync(HISTORY_PATH, JSON.stringify(entry) + "\n"); } diff --git a/backend/sources/product-test-images/11110059 CEKER BERKUKU FROZEN PACK 1 KG__IMG_2530 2.jpeg b/backend/sources/product-test-images/11110059 CEKER BERKUKU FROZEN PACK 1 KG__IMG_2530 2.jpeg new file mode 100644 index 0000000..3d41226 Binary files /dev/null and b/backend/sources/product-test-images/11110059 CEKER BERKUKU FROZEN PACK 1 KG__IMG_2530 2.jpeg differ diff --git a/backend/sources/product-test-images/11140051 AMPELA FROZEN PACK 1 KG__IMG_6861.jpeg b/backend/sources/product-test-images/11140051 AMPELA FROZEN PACK 1 KG__IMG_6861.jpeg new file mode 100644 index 0000000..106a81b Binary files /dev/null and b/backend/sources/product-test-images/11140051 AMPELA FROZEN PACK 1 KG__IMG_6861.jpeg differ diff --git a/backend/sources/product-test-images/11620056 SBL (FILLET PAHA) 1 KG__IMG_6890.jpeg b/backend/sources/product-test-images/11620056 SBL (FILLET PAHA) 1 KG__IMG_6890.jpeg new file mode 100644 index 0000000..03c5317 Binary files /dev/null and b/backend/sources/product-test-images/11620056 SBL (FILLET PAHA) 1 KG__IMG_6890.jpeg differ diff --git a/backend/sources/product-test-images/11650053 PAHA ATAS 1KG__WhatsApp Image 2026-07-01 at 15.07.36 (1).jpeg b/backend/sources/product-test-images/11650053 PAHA ATAS 1KG__WhatsApp Image 2026-07-01 at 15.07.36 (1).jpeg new file mode 100644 index 0000000..874d930 Binary files /dev/null and b/backend/sources/product-test-images/11650053 PAHA ATAS 1KG__WhatsApp Image 2026-07-01 at 15.07.36 (1).jpeg differ diff --git a/backend/sources/product-test-images/11660050 PAHA BAWAH (1 KG)__WhatsApp Image 2026-07-01 at 4.12.23 PM.jpeg b/backend/sources/product-test-images/11660050 PAHA BAWAH (1 KG)__WhatsApp Image 2026-07-01 at 4.12.23 PM.jpeg new file mode 100644 index 0000000..52c2484 Binary files /dev/null and b/backend/sources/product-test-images/11660050 PAHA BAWAH (1 KG)__WhatsApp Image 2026-07-01 at 4.12.23 PM.jpeg differ diff --git a/backend/sources/product-test-images/11710051 DADA UTUH (1 KG)__IMG_6905.jpeg b/backend/sources/product-test-images/11710051 DADA UTUH (1 KG)__IMG_6905.jpeg new file mode 100644 index 0000000..18e6fd7 Binary files /dev/null and b/backend/sources/product-test-images/11710051 DADA UTUH (1 KG)__IMG_6905.jpeg differ diff --git a/backend/sources/product-test-images/11818300 CP-Bebek goreng__IMG_6920.jpeg b/backend/sources/product-test-images/11818300 CP-Bebek goreng__IMG_6920.jpeg new file mode 100644 index 0000000..d407b1c Binary files /dev/null and b/backend/sources/product-test-images/11818300 CP-Bebek goreng__IMG_6920.jpeg differ diff --git a/backend/sources/product-test-images/1195008A RTC CHICKEN KALASAN 400 GR (PAC)__WhatsApp Image 2026-07-08 at 13.37.11 (1).jpeg b/backend/sources/product-test-images/1195008A RTC CHICKEN KALASAN 400 GR (PAC)__WhatsApp Image 2026-07-08 at 13.37.11 (1).jpeg new file mode 100644 index 0000000..d38c6e5 Binary files /dev/null and b/backend/sources/product-test-images/1195008A RTC CHICKEN KALASAN 400 GR (PAC)__WhatsApp Image 2026-07-08 at 13.37.11 (1).jpeg differ diff --git a/backend/sources/product-test-images/11959937 SATE AYAM FRESHMART 360 GR (PAC)__IMG_6874.jpeg b/backend/sources/product-test-images/11959937 SATE AYAM FRESHMART 360 GR (PAC)__IMG_6874.jpeg new file mode 100644 index 0000000..62ac519 Binary files /dev/null and b/backend/sources/product-test-images/11959937 SATE AYAM FRESHMART 360 GR (PAC)__IMG_6874.jpeg differ diff --git a/backend/sources/product-test-images/12010111 FIESTA CRISPY BUBBLE 400GR__WhatsApp Image 2026-07-01 at 15.16.31 (1).jpeg b/backend/sources/product-test-images/12010111 FIESTA CRISPY BUBBLE 400GR__WhatsApp Image 2026-07-01 at 15.16.31 (1).jpeg new file mode 100644 index 0000000..8608ce4 Binary files /dev/null and b/backend/sources/product-test-images/12010111 FIESTA CRISPY BUBBLE 400GR__WhatsApp Image 2026-07-01 at 15.16.31 (1).jpeg differ diff --git a/backend/sources/product-test-images/12010115 FIESTA NUGGET ZOO 400 GR__WhatsApp Image 2026-07-02 at 09.41.22 (1).jpeg b/backend/sources/product-test-images/12010115 FIESTA NUGGET ZOO 400 GR__WhatsApp Image 2026-07-02 at 09.41.22 (1).jpeg new file mode 100644 index 0000000..01b235a Binary files /dev/null and b/backend/sources/product-test-images/12010115 FIESTA NUGGET ZOO 400 GR__WhatsApp Image 2026-07-02 at 09.41.22 (1).jpeg differ diff --git a/backend/sources/product-test-images/12010117 FIESTA NUGGET HAPPY STAR 400 GR-PAC__WhatsApp Image 2026-07-01 at 4.13.20 PM.jpeg b/backend/sources/product-test-images/12010117 FIESTA NUGGET HAPPY STAR 400 GR-PAC__WhatsApp Image 2026-07-01 at 4.13.20 PM.jpeg new file mode 100644 index 0000000..799a3b4 Binary files /dev/null and b/backend/sources/product-test-images/12010117 FIESTA NUGGET HAPPY STAR 400 GR-PAC__WhatsApp Image 2026-07-01 at 4.13.20 PM.jpeg differ diff --git a/backend/sources/product-test-images/12010119 FIESTA NUGGET CHEESE 123 400 GR PER PAC__IMG_2550 2.jpeg b/backend/sources/product-test-images/12010119 FIESTA NUGGET CHEESE 123 400 GR PER PAC__IMG_2550 2.jpeg new file mode 100644 index 0000000..990e828 Binary files /dev/null and b/backend/sources/product-test-images/12010119 FIESTA NUGGET CHEESE 123 400 GR PER PAC__IMG_2550 2.jpeg differ diff --git a/backend/sources/product-test-images/12010121 FIESTA NUGGET PIZZABC 400GR__WhatsApp Image 2026-07-01 at 15.23.52.jpeg b/backend/sources/product-test-images/12010121 FIESTA NUGGET PIZZABC 400GR__WhatsApp Image 2026-07-01 at 15.23.52.jpeg new file mode 100644 index 0000000..ce9b3c8 Binary files /dev/null and b/backend/sources/product-test-images/12010121 FIESTA NUGGET PIZZABC 400GR__WhatsApp Image 2026-07-01 at 15.23.52.jpeg differ diff --git a/backend/sources/product-test-images/12010127 FIESTA SPICY NUGGET 400 GR-PAC__WhatsApp Image 2026-07-01 at 4.11.37 PM (1).jpeg b/backend/sources/product-test-images/12010127 FIESTA SPICY NUGGET 400 GR-PAC__WhatsApp Image 2026-07-01 at 4.11.37 PM (1).jpeg new file mode 100644 index 0000000..e28274e Binary files /dev/null and b/backend/sources/product-test-images/12010127 FIESTA SPICY NUGGET 400 GR-PAC__WhatsApp Image 2026-07-01 at 4.11.37 PM (1).jpeg differ diff --git a/backend/sources/product-test-images/12010509 CHAMP CRUNCHY NUGGET 450 GR PER PAC__WhatsApp Image 2026-05-28 at 12.03.31 (1).jpeg b/backend/sources/product-test-images/12010509 CHAMP CRUNCHY NUGGET 450 GR PER PAC__WhatsApp Image 2026-05-28 at 12.03.31 (1).jpeg new file mode 100644 index 0000000..997fab2 Binary files /dev/null and b/backend/sources/product-test-images/12010509 CHAMP CRUNCHY NUGGET 450 GR PER PAC__WhatsApp Image 2026-05-28 at 12.03.31 (1).jpeg differ diff --git a/backend/sources/product-test-images/12010515 CHAMP CHICKEN NUGGET KOIN__IMG_2507 2.jpeg b/backend/sources/product-test-images/12010515 CHAMP CHICKEN NUGGET KOIN__IMG_2507 2.jpeg new file mode 100644 index 0000000..986bb76 Binary files /dev/null and b/backend/sources/product-test-images/12010515 CHAMP CHICKEN NUGGET KOIN__IMG_2507 2.jpeg differ diff --git a/backend/sources/product-test-images/12010519 CHAMP NUGGET STICK 900 GR__WhatsApp Image 2026-07-01 at 14.14.06.jpeg b/backend/sources/product-test-images/12010519 CHAMP NUGGET STICK 900 GR__WhatsApp Image 2026-07-01 at 14.14.06.jpeg new file mode 100644 index 0000000..dd71347 Binary files /dev/null and b/backend/sources/product-test-images/12010519 CHAMP NUGGET STICK 900 GR__WhatsApp Image 2026-07-01 at 14.14.06.jpeg differ diff --git a/backend/sources/product-test-images/12012202 ASIMO NUGGET KOMBINASI 1 KG PER PAC__WhatsApp Image 2026-05-28 at 11.52.42 (1).jpeg b/backend/sources/product-test-images/12012202 ASIMO NUGGET KOMBINASI 1 KG PER PAC__WhatsApp Image 2026-05-28 at 11.52.42 (1).jpeg new file mode 100644 index 0000000..9e4f128 Binary files /dev/null and b/backend/sources/product-test-images/12012202 ASIMO NUGGET KOMBINASI 1 KG PER PAC__WhatsApp Image 2026-05-28 at 11.52.42 (1).jpeg differ diff --git a/backend/sources/product-test-images/12012501 AKUMO CHICKEN NAGET 250 GR__WhatsApp Image 2026-07-08 at 13.28.44 (1).jpeg b/backend/sources/product-test-images/12012501 AKUMO CHICKEN NAGET 250 GR__WhatsApp Image 2026-07-08 at 13.28.44 (1).jpeg new file mode 100644 index 0000000..68b44f0 Binary files /dev/null and b/backend/sources/product-test-images/12012501 AKUMO CHICKEN NAGET 250 GR__WhatsApp Image 2026-07-08 at 13.28.44 (1).jpeg differ diff --git a/backend/sources/product-test-images/12012503 AKUMO CHICKEN NUGGET 1000 GR__WhatsApp Image 2026-06-29 at 08.56.04 (1).jpeg b/backend/sources/product-test-images/12012503 AKUMO CHICKEN NUGGET 1000 GR__WhatsApp Image 2026-06-29 at 08.56.04 (1).jpeg new file mode 100644 index 0000000..72c0b4f Binary files /dev/null and b/backend/sources/product-test-images/12012503 AKUMO CHICKEN NUGGET 1000 GR__WhatsApp Image 2026-06-29 at 08.56.04 (1).jpeg differ diff --git a/backend/sources/product-test-images/12012505 AKUMO KOIN 400 GR-PAC__WhatsApp Image 2026-07-08 at 13.19.45 (1).jpeg b/backend/sources/product-test-images/12012505 AKUMO KOIN 400 GR-PAC__WhatsApp Image 2026-07-08 at 13.19.45 (1).jpeg new file mode 100644 index 0000000..de3977c Binary files /dev/null and b/backend/sources/product-test-images/12012505 AKUMO KOIN 400 GR-PAC__WhatsApp Image 2026-07-08 at 13.19.45 (1).jpeg differ diff --git a/backend/sources/product-test-images/12020102 FIESTA SPICY WING 400 GR-PAC__WhatsApp Image 2026-06-29 at 09.14.40.jpeg b/backend/sources/product-test-images/12020102 FIESTA SPICY WING 400 GR-PAC__WhatsApp Image 2026-06-29 at 09.14.40.jpeg new file mode 100644 index 0000000..6637476 Binary files /dev/null and b/backend/sources/product-test-images/12020102 FIESTA SPICY WING 400 GR-PAC__WhatsApp Image 2026-06-29 at 09.14.40.jpeg differ diff --git a/backend/sources/product-test-images/12030102 FIESTA STIKIE 200 GR-PAC__WhatsApp Image 2026-06-29 at 09.10.01 (1).jpeg b/backend/sources/product-test-images/12030102 FIESTA STIKIE 200 GR-PAC__WhatsApp Image 2026-06-29 at 09.10.01 (1).jpeg new file mode 100644 index 0000000..f575d07 Binary files /dev/null and b/backend/sources/product-test-images/12030102 FIESTA STIKIE 200 GR-PAC__WhatsApp Image 2026-06-29 at 09.10.01 (1).jpeg differ diff --git a/backend/sources/product-test-images/12030403 GOLDEN FIESTA STIKIE W- SWEET CHILLI SAUCE 500GR__IMG_2608.jpeg b/backend/sources/product-test-images/12030403 GOLDEN FIESTA STIKIE W- SWEET CHILLI SAUCE 500GR__IMG_2608.jpeg new file mode 100644 index 0000000..4d616b1 Binary files /dev/null and b/backend/sources/product-test-images/12030403 GOLDEN FIESTA STIKIE W- SWEET CHILLI SAUCE 500GR__IMG_2608.jpeg differ diff --git a/backend/sources/product-test-images/12032502 AKUMO CHICKEN STIK 500 GR__WhatsApp Image 2026-07-08 at 13.23.37 (1).jpeg b/backend/sources/product-test-images/12032502 AKUMO CHICKEN STIK 500 GR__WhatsApp Image 2026-07-08 at 13.23.37 (1).jpeg new file mode 100644 index 0000000..4d7ab89 Binary files /dev/null and b/backend/sources/product-test-images/12032502 AKUMO CHICKEN STIK 500 GR__WhatsApp Image 2026-07-08 at 13.23.37 (1).jpeg differ diff --git a/backend/sources/product-test-images/12040101 FIESTA SCHNITZEL 400 GR-PAC__WhatsApp Image 2026-07-08 at 10.47.52.jpeg b/backend/sources/product-test-images/12040101 FIESTA SCHNITZEL 400 GR-PAC__WhatsApp Image 2026-07-08 at 10.47.52.jpeg new file mode 100644 index 0000000..54898a3 Binary files /dev/null and b/backend/sources/product-test-images/12040101 FIESTA SCHNITZEL 400 GR-PAC__WhatsApp Image 2026-07-08 at 10.47.52.jpeg differ diff --git a/backend/sources/product-test-images/12040102 FIESTA CRISPY BUBBLE KATSU 400 GR-PAC__WhatsApp Image 2026-07-08 at 13.40.53.jpeg b/backend/sources/product-test-images/12040102 FIESTA CRISPY BUBBLE KATSU 400 GR-PAC__WhatsApp Image 2026-07-08 at 13.40.53.jpeg new file mode 100644 index 0000000..6a16a0a Binary files /dev/null and b/backend/sources/product-test-images/12040102 FIESTA CRISPY BUBBLE KATSU 400 GR-PAC__WhatsApp Image 2026-07-08 at 13.40.53.jpeg differ diff --git a/backend/sources/product-test-images/12060103 FIESTA KARAGE 200 GR-PAC__WhatsApp Image 2026-06-29 at 08.35.24.jpeg b/backend/sources/product-test-images/12060103 FIESTA KARAGE 200 GR-PAC__WhatsApp Image 2026-06-29 at 08.35.24.jpeg new file mode 100644 index 0000000..d9fcbad Binary files /dev/null and b/backend/sources/product-test-images/12060103 FIESTA KARAGE 200 GR-PAC__WhatsApp Image 2026-06-29 at 08.35.24.jpeg differ diff --git a/backend/sources/product-test-images/12060402 GOLDEN FIESTA KARAGE CHILI SAUCE 500GR__WhatsApp Image 2026-05-28 at 11.51.20 (1).jpeg b/backend/sources/product-test-images/12060402 GOLDEN FIESTA KARAGE CHILI SAUCE 500GR__WhatsApp Image 2026-05-28 at 11.51.20 (1).jpeg new file mode 100644 index 0000000..29c500e Binary files /dev/null and b/backend/sources/product-test-images/12060402 GOLDEN FIESTA KARAGE CHILI SAUCE 500GR__WhatsApp Image 2026-05-28 at 11.51.20 (1).jpeg differ diff --git a/backend/sources/product-test-images/12080101 FIESTA SPICY CHICK 400 GR-PAC__WhatsApp Image 2026-06-29 at 8.19.05 AM.jpeg b/backend/sources/product-test-images/12080101 FIESTA SPICY CHICK 400 GR-PAC__WhatsApp Image 2026-06-29 at 8.19.05 AM.jpeg new file mode 100644 index 0000000..4b662eb Binary files /dev/null and b/backend/sources/product-test-images/12080101 FIESTA SPICY CHICK 400 GR-PAC__WhatsApp Image 2026-06-29 at 8.19.05 AM.jpeg differ diff --git a/backend/sources/product-test-images/12130102 FIESTA CRISPY BURGER 360gr__WhatsApp Image 2026-07-01 at 4.08.26 PM.jpeg b/backend/sources/product-test-images/12130102 FIESTA CRISPY BURGER 360gr__WhatsApp Image 2026-07-01 at 4.08.26 PM.jpeg new file mode 100644 index 0000000..eec5150 Binary files /dev/null and b/backend/sources/product-test-images/12130102 FIESTA CRISPY BURGER 360gr__WhatsApp Image 2026-07-01 at 4.08.26 PM.jpeg differ diff --git a/backend/sources/product-test-images/12150201 FIESTA DS CRISPY CRUNCH 300 GR__IMG_2541 2.jpeg b/backend/sources/product-test-images/12150201 FIESTA DS CRISPY CRUNCH 300 GR__IMG_2541 2.jpeg new file mode 100644 index 0000000..cc38b56 Binary files /dev/null and b/backend/sources/product-test-images/12150201 FIESTA DS CRISPY CRUNCH 300 GR__IMG_2541 2.jpeg differ diff --git a/backend/sources/product-test-images/12150501 CHAMP CRUNCHY HOTZZ 300 GR-PAC__WhatsApp Image 2026-06-29 at 08.48.10.jpeg b/backend/sources/product-test-images/12150501 CHAMP CRUNCHY HOTZZ 300 GR-PAC__WhatsApp Image 2026-06-29 at 08.48.10.jpeg new file mode 100644 index 0000000..f4433a0 Binary files /dev/null and b/backend/sources/product-test-images/12150501 CHAMP CRUNCHY HOTZZ 300 GR-PAC__WhatsApp Image 2026-06-29 at 08.48.10.jpeg differ diff --git a/backend/sources/product-test-images/12190103 FIESTA DELISTRIPE 400 GR-PAC__IMG_20260707_101832.jpeg b/backend/sources/product-test-images/12190103 FIESTA DELISTRIPE 400 GR-PAC__IMG_20260707_101832.jpeg new file mode 100644 index 0000000..888659b Binary files /dev/null and b/backend/sources/product-test-images/12190103 FIESTA DELISTRIPE 400 GR-PAC__IMG_20260707_101832.jpeg differ diff --git a/backend/sources/product-test-images/12240103 FIESTA YAKINIKU RICE BITES 400GR__WhatsApp Image 2026-07-01 at 15.09.17.jpeg b/backend/sources/product-test-images/12240103 FIESTA YAKINIKU RICE BITES 400GR__WhatsApp Image 2026-07-01 at 15.09.17.jpeg new file mode 100644 index 0000000..7bd8741 Binary files /dev/null and b/backend/sources/product-test-images/12240103 FIESTA YAKINIKU RICE BITES 400GR__WhatsApp Image 2026-07-01 at 15.09.17.jpeg differ diff --git a/backend/sources/product-test-images/13010111 FIESTA SOSIS BRATWURST 300 GR__WhatsApp Image 2026-07-01 at 4.09.26 PM.jpeg b/backend/sources/product-test-images/13010111 FIESTA SOSIS BRATWURST 300 GR__WhatsApp Image 2026-07-01 at 4.09.26 PM.jpeg new file mode 100644 index 0000000..8a0a09c Binary files /dev/null and b/backend/sources/product-test-images/13010111 FIESTA SOSIS BRATWURST 300 GR__WhatsApp Image 2026-07-01 at 4.09.26 PM.jpeg differ diff --git a/backend/sources/product-test-images/13010116 FIESTA SSG ORIGINAL 300 GR__WhatsApp Image 2026-07-01 at 4.10.26 PM.jpeg b/backend/sources/product-test-images/13010116 FIESTA SSG ORIGINAL 300 GR__WhatsApp Image 2026-07-01 at 4.10.26 PM.jpeg new file mode 100644 index 0000000..26ba7cc Binary files /dev/null and b/backend/sources/product-test-images/13010116 FIESTA SSG ORIGINAL 300 GR__WhatsApp Image 2026-07-01 at 4.10.26 PM.jpeg differ diff --git a/backend/sources/product-test-images/13010118 FIESTA RTG SSG 65 GR-PAC__WhatsApp Image 2026-07-08 at 12.56.30 (1).jpeg b/backend/sources/product-test-images/13010118 FIESTA RTG SSG 65 GR-PAC__WhatsApp Image 2026-07-08 at 12.56.30 (1).jpeg new file mode 100644 index 0000000..9249bf2 Binary files /dev/null and b/backend/sources/product-test-images/13010118 FIESTA RTG SSG 65 GR-PAC__WhatsApp Image 2026-07-08 at 12.56.30 (1).jpeg differ diff --git a/backend/sources/product-test-images/13010120 FIESTA RTG C-CHEESY MELTS 65 GR-PAC__IMG_2739.jpeg b/backend/sources/product-test-images/13010120 FIESTA RTG C-CHEESY MELTS 65 GR-PAC__IMG_2739.jpeg new file mode 100644 index 0000000..6f9ece0 Binary files /dev/null and b/backend/sources/product-test-images/13010120 FIESTA RTG C-CHEESY MELTS 65 GR-PAC__IMG_2739.jpeg differ diff --git a/backend/sources/product-test-images/13010122 FIESTA RTG SAUSAGE WITH HOT LAVA 60G__IMG_2730.jpeg b/backend/sources/product-test-images/13010122 FIESTA RTG SAUSAGE WITH HOT LAVA 60G__IMG_2730.jpeg new file mode 100644 index 0000000..b1f8a3a Binary files /dev/null and b/backend/sources/product-test-images/13010122 FIESTA RTG SAUSAGE WITH HOT LAVA 60G__IMG_2730.jpeg differ diff --git a/backend/sources/product-test-images/13010123 FIESTA RTG SAUSAGE WITH CHEESE LAVA 60G__IMG_20260707_102609.jpeg b/backend/sources/product-test-images/13010123 FIESTA RTG SAUSAGE WITH CHEESE LAVA 60G__IMG_20260707_102609.jpeg new file mode 100644 index 0000000..eb749e9 Binary files /dev/null and b/backend/sources/product-test-images/13010123 FIESTA RTG SAUSAGE WITH CHEESE LAVA 60G__IMG_20260707_102609.jpeg differ diff --git a/backend/sources/product-test-images/13010125 FIESTA RTG SAUSAGE WITH MENTAI LAVA 60GR__WhatsApp Image 2026-07-08 at 11.03.11.jpeg b/backend/sources/product-test-images/13010125 FIESTA RTG SAUSAGE WITH MENTAI LAVA 60GR__WhatsApp Image 2026-07-08 at 11.03.11.jpeg new file mode 100644 index 0000000..1a77f3e Binary files /dev/null and b/backend/sources/product-test-images/13010125 FIESTA RTG SAUSAGE WITH MENTAI LAVA 60GR__WhatsApp Image 2026-07-08 at 11.03.11.jpeg differ diff --git a/backend/sources/product-test-images/13010518 CHAMP SSG JUMBO BAKAR 500 GR-PAC__IMG_2747.jpeg b/backend/sources/product-test-images/13010518 CHAMP SSG JUMBO BAKAR 500 GR-PAC__IMG_2747.jpeg new file mode 100644 index 0000000..64c8928 Binary files /dev/null and b/backend/sources/product-test-images/13010518 CHAMP SSG JUMBO BAKAR 500 GR-PAC__IMG_2747.jpeg differ diff --git a/backend/sources/product-test-images/13010524 CHAMP SSG JUMBO BAKAR 500 GR-PAC (NEW)__WhatsApp Image 2026-06-29 at 9.35.39 AM (1).jpeg b/backend/sources/product-test-images/13010524 CHAMP SSG JUMBO BAKAR 500 GR-PAC (NEW)__WhatsApp Image 2026-06-29 at 9.35.39 AM (1).jpeg new file mode 100644 index 0000000..59dbd35 Binary files /dev/null and b/backend/sources/product-test-images/13010524 CHAMP SSG JUMBO BAKAR 500 GR-PAC (NEW)__WhatsApp Image 2026-06-29 at 9.35.39 AM (1).jpeg differ diff --git a/backend/sources/product-test-images/13012206 ASIMO SOSIS AYAM KOMBINASI 500 GR__IMG_2521 2.jpeg b/backend/sources/product-test-images/13012206 ASIMO SOSIS AYAM KOMBINASI 500 GR__IMG_2521 2.jpeg new file mode 100644 index 0000000..92452e8 Binary files /dev/null and b/backend/sources/product-test-images/13012206 ASIMO SOSIS AYAM KOMBINASI 500 GR__IMG_2521 2.jpeg differ diff --git a/backend/sources/product-test-images/13030501 CHAMP CHICK MEATBALL 200 GR__WhatsApp Image 2026-07-01 at 4.14.08 PM (1).jpeg b/backend/sources/product-test-images/13030501 CHAMP CHICK MEATBALL 200 GR__WhatsApp Image 2026-07-01 at 4.14.08 PM (1).jpeg new file mode 100644 index 0000000..fe38768 Binary files /dev/null and b/backend/sources/product-test-images/13030501 CHAMP CHICK MEATBALL 200 GR__WhatsApp Image 2026-07-01 at 4.14.08 PM (1).jpeg differ diff --git a/backend/sources/product-test-images/13070506 CHAMP FRANKFURTER SSG 375GR__IMG_20260707_104918.jpeg b/backend/sources/product-test-images/13070506 CHAMP FRANKFURTER SSG 375GR__IMG_20260707_104918.jpeg new file mode 100644 index 0000000..636a526 Binary files /dev/null and b/backend/sources/product-test-images/13070506 CHAMP FRANKFURTER SSG 375GR__IMG_20260707_104918.jpeg differ diff --git a/backend/sources/product-test-images/13100512 CHAMP CHICK SSG S-SANTAP ORIG 546GR (CAN)__WhatsApp Image 2026-07-01 at 14.40.36.jpeg b/backend/sources/product-test-images/13100512 CHAMP CHICK SSG S-SANTAP ORIG 546GR (CAN)__WhatsApp Image 2026-07-01 at 14.40.36.jpeg new file mode 100644 index 0000000..f0abed7 Binary files /dev/null and b/backend/sources/product-test-images/13100512 CHAMP CHICK SSG S-SANTAP ORIG 546GR (CAN)__WhatsApp Image 2026-07-01 at 14.40.36.jpeg differ diff --git a/backend/sources/product-test-images/15010101 FIESTA SHOESTRING 500 GR__IMG_2560 2.jpeg b/backend/sources/product-test-images/15010101 FIESTA SHOESTRING 500 GR__IMG_2560 2.jpeg new file mode 100644 index 0000000..aff2594 Binary files /dev/null and b/backend/sources/product-test-images/15010101 FIESTA SHOESTRING 500 GR__IMG_2560 2.jpeg differ diff --git a/backend/sources/product-test-images/15010102 FIESTA SHOESTRING 1000 GR__IMG_2569 2.jpeg b/backend/sources/product-test-images/15010102 FIESTA SHOESTRING 1000 GR__IMG_2569 2.jpeg new file mode 100644 index 0000000..71c03f8 Binary files /dev/null and b/backend/sources/product-test-images/15010102 FIESTA SHOESTRING 1000 GR__IMG_2569 2.jpeg differ diff --git a/backend/sources/product-test-images/15010107 FIESTA FRENCH F SHOESTRING INSTITUSI 2KG__IMG_2580 2.jpeg b/backend/sources/product-test-images/15010107 FIESTA FRENCH F SHOESTRING INSTITUSI 2KG__IMG_2580 2.jpeg new file mode 100644 index 0000000..e307807 Binary files /dev/null and b/backend/sources/product-test-images/15010107 FIESTA FRENCH F SHOESTRING INSTITUSI 2KG__IMG_2580 2.jpeg differ diff --git a/backend/sources/product-test-images/15020101 FIESTA STRAIGHT CUT 500 GR__WhatsApp Image 2026-07-08 at 13.02.16.jpeg b/backend/sources/product-test-images/15020101 FIESTA STRAIGHT CUT 500 GR__WhatsApp Image 2026-07-08 at 13.02.16.jpeg new file mode 100644 index 0000000..f9e14ff Binary files /dev/null and b/backend/sources/product-test-images/15020101 FIESTA STRAIGHT CUT 500 GR__WhatsApp Image 2026-07-08 at 13.02.16.jpeg differ diff --git a/backend/sources/product-test-images/15020102 FIESTA STRAIGHT CUT 1000 GR__IMG_6844.jpeg b/backend/sources/product-test-images/15020102 FIESTA STRAIGHT CUT 1000 GR__IMG_6844.jpeg new file mode 100644 index 0000000..95e7b0b Binary files /dev/null and b/backend/sources/product-test-images/15020102 FIESTA STRAIGHT CUT 1000 GR__IMG_6844.jpeg differ diff --git a/backend/sources/product-test-images/15030101 FIESTA CRINKLE CUT 500 GR__WhatsApp Image 2026-05-28 at 11.46.30.jpeg b/backend/sources/product-test-images/15030101 FIESTA CRINKLE CUT 500 GR__WhatsApp Image 2026-05-28 at 11.46.30.jpeg new file mode 100644 index 0000000..6047be8 Binary files /dev/null and b/backend/sources/product-test-images/15030101 FIESTA CRINKLE CUT 500 GR__WhatsApp Image 2026-05-28 at 11.46.30.jpeg differ diff --git a/backend/sources/product-test-images/15030102 FIESTA CRINKLE CUT 1000 GR__IMG_6828.jpeg b/backend/sources/product-test-images/15030102 FIESTA CRINKLE CUT 1000 GR__IMG_6828.jpeg new file mode 100644 index 0000000..9161a44 Binary files /dev/null and b/backend/sources/product-test-images/15030102 FIESTA CRINKLE CUT 1000 GR__IMG_6828.jpeg differ diff --git a/backend/sources/product-test-images/16060113 FIESTA CHICK SIOMAY 180GR (NEW)__WhatsApp Image 2026-06-29 at 9.23.11 AM (1).jpeg b/backend/sources/product-test-images/16060113 FIESTA CHICK SIOMAY 180GR (NEW)__WhatsApp Image 2026-06-29 at 9.23.11 AM (1).jpeg new file mode 100644 index 0000000..e96b193 Binary files /dev/null and b/backend/sources/product-test-images/16060113 FIESTA CHICK SIOMAY 180GR (NEW)__WhatsApp Image 2026-06-29 at 9.23.11 AM (1).jpeg differ diff --git a/backend/sources/product-test-images/16060114 FIESTA GYOZA 180GR__WhatsApp Image 2026-07-01 at 15.28.49 (1).jpeg b/backend/sources/product-test-images/16060114 FIESTA GYOZA 180GR__WhatsApp Image 2026-07-01 at 15.28.49 (1).jpeg new file mode 100644 index 0000000..6cbfda4 Binary files /dev/null and b/backend/sources/product-test-images/16060114 FIESTA GYOZA 180GR__WhatsApp Image 2026-07-01 at 15.28.49 (1).jpeg differ diff --git a/backend/sources/product-test-images/17200109 FIESTA RTS C-TERIYAKI 300GR__WhatsApp Image 2026-07-01 at 15.59.33.jpeg b/backend/sources/product-test-images/17200109 FIESTA RTS C-TERIYAKI 300GR__WhatsApp Image 2026-07-01 at 15.59.33.jpeg new file mode 100644 index 0000000..03cb499 Binary files /dev/null and b/backend/sources/product-test-images/17200109 FIESTA RTS C-TERIYAKI 300GR__WhatsApp Image 2026-07-01 at 15.59.33.jpeg differ diff --git a/backend/sources/product-test-images/17210106 FIESTA RTS B-YAKINIKU 300GR-PAC__WhatsApp Image 2026-07-08 at 13.15.26.jpeg b/backend/sources/product-test-images/17210106 FIESTA RTS B-YAKINIKU 300GR-PAC__WhatsApp Image 2026-07-08 at 13.15.26.jpeg new file mode 100644 index 0000000..e51ced8 Binary files /dev/null and b/backend/sources/product-test-images/17210106 FIESTA RTS B-YAKINIKU 300GR-PAC__WhatsApp Image 2026-07-08 at 13.15.26.jpeg differ diff --git a/backend/sources/product-test-images/17210107 FIESTA RTS B-RENDANG 300GR-PAC__WhatsApp Image 2026-07-08 at 13.07.40 (1).jpeg b/backend/sources/product-test-images/17210107 FIESTA RTS B-RENDANG 300GR-PAC__WhatsApp Image 2026-07-08 at 13.07.40 (1).jpeg new file mode 100644 index 0000000..3910727 Binary files /dev/null and b/backend/sources/product-test-images/17210107 FIESTA RTS B-RENDANG 300GR-PAC__WhatsApp Image 2026-07-08 at 13.07.40 (1).jpeg differ diff --git a/backend/sources/product-test-images/17210108 FIESTA RTS B-BLACKPEPPER 300GR-PAC__IMG_20260707_105448.jpeg b/backend/sources/product-test-images/17210108 FIESTA RTS B-BLACKPEPPER 300GR-PAC__IMG_20260707_105448.jpeg new file mode 100644 index 0000000..e8ac954 Binary files /dev/null and b/backend/sources/product-test-images/17210108 FIESTA RTS B-BLACKPEPPER 300GR-PAC__IMG_20260707_105448.jpeg differ diff --git a/backend/sources/product-test-images/17210109 FIESTA RTS B-BULGOGI 300GR-PAC__WhatsApp Image 2026-07-01 at 4.23.06 PM (1).jpeg b/backend/sources/product-test-images/17210109 FIESTA RTS B-BULGOGI 300GR-PAC__WhatsApp Image 2026-07-01 at 4.23.06 PM (1).jpeg new file mode 100644 index 0000000..4228dec Binary files /dev/null and b/backend/sources/product-test-images/17210109 FIESTA RTS B-BULGOGI 300GR-PAC__WhatsApp Image 2026-07-01 at 4.23.06 PM (1).jpeg differ diff --git a/backend/sources/product-test-images/20040101 FIESTA RAMEN BEKU 570GR__WhatsApp Image 2026-07-01 at 15.36.22 (1).jpeg b/backend/sources/product-test-images/20040101 FIESTA RAMEN BEKU 570GR__WhatsApp Image 2026-07-01 at 15.36.22 (1).jpeg new file mode 100644 index 0000000..79b8f6c Binary files /dev/null and b/backend/sources/product-test-images/20040101 FIESTA RAMEN BEKU 570GR__WhatsApp Image 2026-07-01 at 15.36.22 (1).jpeg differ diff --git a/backend/sources/product-test-images/20120102 FIESTA T-B AYAM GORENG 80 GR__WhatsApp Image 2026-07-01 at 4.19.00 PM (1).jpeg b/backend/sources/product-test-images/20120102 FIESTA T-B AYAM GORENG 80 GR__WhatsApp Image 2026-07-01 at 4.19.00 PM (1).jpeg new file mode 100644 index 0000000..39c9a1d Binary files /dev/null and b/backend/sources/product-test-images/20120102 FIESTA T-B AYAM GORENG 80 GR__WhatsApp Image 2026-07-01 at 4.19.00 PM (1).jpeg differ diff --git a/backend/sources/product-test-images/20120105 FIESTA T-B SERBAGUNA 80 GR__WhatsApp Image 2026-07-01 at 4.17.59 PM (1).jpeg b/backend/sources/product-test-images/20120105 FIESTA T-B SERBAGUNA 80 GR__WhatsApp Image 2026-07-01 at 4.17.59 PM (1).jpeg new file mode 100644 index 0000000..b9e7e50 Binary files /dev/null and b/backend/sources/product-test-images/20120105 FIESTA T-B SERBAGUNA 80 GR__WhatsApp Image 2026-07-01 at 4.17.59 PM (1).jpeg differ diff --git a/backend/sources/product-test-images/20120115 FIESTA RACIK AYAM GORENG 20GR__WhatsApp Image 2026-07-01 at 15.45.39.jpeg b/backend/sources/product-test-images/20120115 FIESTA RACIK AYAM GORENG 20GR__WhatsApp Image 2026-07-01 at 15.45.39.jpeg new file mode 100644 index 0000000..825afc0 Binary files /dev/null and b/backend/sources/product-test-images/20120115 FIESTA RACIK AYAM GORENG 20GR__WhatsApp Image 2026-07-01 at 15.45.39.jpeg differ diff --git a/backend/sources/product-test-images/20120116 FIESTA RACIK NASI GORENG 20 GR-PAC__WhatsApp Image 2026-05-28 at 12.01.42 (1).jpeg b/backend/sources/product-test-images/20120116 FIESTA RACIK NASI GORENG 20 GR-PAC__WhatsApp Image 2026-05-28 at 12.01.42 (1).jpeg new file mode 100644 index 0000000..55669d7 Binary files /dev/null and b/backend/sources/product-test-images/20120116 FIESTA RACIK NASI GORENG 20 GR-PAC__WhatsApp Image 2026-05-28 at 12.01.42 (1).jpeg differ diff --git a/backend/sources/product-test-images/21000123 FIESTA RICE W-GEPREK CHICKEN 320GR-PAC__IMG_2645.jpeg b/backend/sources/product-test-images/21000123 FIESTA RICE W-GEPREK CHICKEN 320GR-PAC__IMG_2645.jpeg new file mode 100644 index 0000000..b1465f9 Binary files /dev/null and b/backend/sources/product-test-images/21000123 FIESTA RICE W-GEPREK CHICKEN 320GR-PAC__IMG_2645.jpeg differ diff --git a/backend/sources/product-test-images/21000126 NEW FIESTA CHICK RENDANG W RICE 320GR (PAC)__WhatsApp Image 2026-07-08 at 13.33.16 (1).jpeg b/backend/sources/product-test-images/21000126 NEW FIESTA CHICK RENDANG W RICE 320GR (PAC)__WhatsApp Image 2026-07-08 at 13.33.16 (1).jpeg new file mode 100644 index 0000000..5d92321 Binary files /dev/null and b/backend/sources/product-test-images/21000126 NEW FIESTA CHICK RENDANG W RICE 320GR (PAC)__WhatsApp Image 2026-07-08 at 13.33.16 (1).jpeg differ diff --git a/backend/sources/product-test-images/21000130 NEW FIESTA RICE W-C CHEESE BULDAK 320GR (PAC)__WhatsApp Image 2026-07-01 at 4.21.08 PM (1).jpeg b/backend/sources/product-test-images/21000130 NEW FIESTA RICE W-C CHEESE BULDAK 320GR (PAC)__WhatsApp Image 2026-07-01 at 4.21.08 PM (1).jpeg new file mode 100644 index 0000000..6a5afcf Binary files /dev/null and b/backend/sources/product-test-images/21000130 NEW FIESTA RICE W-C CHEESE BULDAK 320GR (PAC)__WhatsApp Image 2026-07-01 at 4.21.08 PM (1).jpeg differ diff --git a/backend/sources/product-test-images/21000137 FIESTA HAINAMESE CHICKEN RICE 320GR (PAC)__IMG_2627.jpeg b/backend/sources/product-test-images/21000137 FIESTA HAINAMESE CHICKEN RICE 320GR (PAC)__IMG_2627.jpeg new file mode 100644 index 0000000..e4b0ac4 Binary files /dev/null and b/backend/sources/product-test-images/21000137 FIESTA HAINAMESE CHICKEN RICE 320GR (PAC)__IMG_2627.jpeg differ diff --git a/backend/sources/product-test-images/21010101 FIESTA TRUFFLE GYUDON 320GR__WhatsApp Image 2026-07-01 at 15.54.38 (1).jpeg b/backend/sources/product-test-images/21010101 FIESTA TRUFFLE GYUDON 320GR__WhatsApp Image 2026-07-01 at 15.54.38 (1).jpeg new file mode 100644 index 0000000..d620917 Binary files /dev/null and b/backend/sources/product-test-images/21010101 FIESTA TRUFFLE GYUDON 320GR__WhatsApp Image 2026-07-01 at 15.54.38 (1).jpeg differ diff --git a/backend/sources/product-test-images/21200107 NEW FIESTA SPAGHETTI CARBONARA 300GR (PAC)__WhatsApp Image 2026-07-01 at 4.22.18 PM.jpeg b/backend/sources/product-test-images/21200107 NEW FIESTA SPAGHETTI CARBONARA 300GR (PAC)__WhatsApp Image 2026-07-01 at 4.22.18 PM.jpeg new file mode 100644 index 0000000..2e989cd Binary files /dev/null and b/backend/sources/product-test-images/21200107 NEW FIESTA SPAGHETTI CARBONARA 300GR (PAC)__WhatsApp Image 2026-07-01 at 4.22.18 PM.jpeg differ diff --git a/backend/sources/product-test-images/README.md b/backend/sources/product-test-images/README.md new file mode 100644 index 0000000..04a17f8 --- /dev/null +++ b/backend/sources/product-test-images/README.md @@ -0,0 +1,21 @@ +# Product-scan validation images + +This folder is the **validation/test set** for the product-scan accuracy +harness (`backend/scripts/accuracy-check-scan.mts`) — real-world photos that +are *not* part of the classifier's reference dataset, so scoring against them +measures actual accuracy instead of memorization. + +**Workflow:** +1. Drop a photo here directly (flat, no subfolders — a filename with no `/` + is what marks an image as "validation" instead of "training"). +2. Label it via the `/manual-label-scan` page (correct `no_sku`, `nama_item`, + `expiry_date` by hand — don't just accept the AI-scan prefill, that would + make the ground truth equal to the model's own prediction). +3. Run `node scripts/accuracy-check-scan.mts` from `backend/` — the photo now + scores under "Validation Set", separate from "Training Set". + +**This is not where new training photos go.** To improve the classifier +itself (DINOv2 index / YOLO fine-tune), add photos to +`pfm-web-app/public/produk-pfm/foto-kemasan-v2//` instead, then +reindex/retrain per `docs/scan-product.md`'s "Model artifacts & retraining" +section.