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
77 lines
2.9 KiB
JavaScript
77 lines
2.9 KiB
JavaScript
// One-off script: pull every Validation Set image that had at least one
|
|
// mismatched field (or failed to scan entirely) out of
|
|
// sources/product-test-images-fixed/ into sources/product-test-images-undetected/,
|
|
// renamed to show which field(s) missed, plus a manifest.md with
|
|
// expected-vs-predicted per field — so a human can tell at a glance whether
|
|
// a miss is an OCR problem, a classification problem, or the photo itself
|
|
// lacking the data (e.g. expiry code out of frame).
|
|
// Usage: node scripts/build-undetected-set.mjs <path-to-detail-dump.json>
|
|
import fs from "fs";
|
|
import path from "path";
|
|
|
|
const detailPath = process.argv[2];
|
|
if (!detailPath) {
|
|
console.error("Usage: node scripts/build-undetected-set.mjs <detail-dump.json>");
|
|
process.exit(1);
|
|
}
|
|
|
|
const FIXED_DIR = path.join("sources", "product-test-images-fixed");
|
|
const OUT_DIR = path.join("sources", "product-test-images-undetected");
|
|
|
|
const detail = JSON.parse(fs.readFileSync(detailPath, "utf8"));
|
|
|
|
if (fs.existsSync(OUT_DIR)) fs.rmSync(OUT_DIR, { recursive: true });
|
|
fs.mkdirSync(OUT_DIR, { recursive: true });
|
|
|
|
const manifestRows = [];
|
|
let copied = 0;
|
|
|
|
for (const item of detail.validation) {
|
|
const failedFields = item.checks.filter((c) => !c.match);
|
|
if (!failedFields.length) continue;
|
|
|
|
const ext = path.extname(item.filename);
|
|
const base = path.basename(item.filename, ext);
|
|
const tag = failedFields.map((c) => c.field).join(",");
|
|
const destName = `${base} [FAIL ${tag}]${ext}`;
|
|
fs.copyFileSync(path.join(FIXED_DIR, item.filename), path.join(OUT_DIR, destName));
|
|
copied++;
|
|
|
|
for (const c of item.checks) {
|
|
manifestRows.push({
|
|
image: destName,
|
|
field: c.field,
|
|
match: c.match,
|
|
expected: c.expected || "(empty)",
|
|
predicted: c.predicted || "(empty — not detected)"
|
|
});
|
|
}
|
|
}
|
|
|
|
for (const f of detail.failed) {
|
|
const srcPath = path.join(FIXED_DIR, f.filename);
|
|
if (!fs.existsSync(srcPath)) continue;
|
|
const ext = path.extname(f.filename);
|
|
const base = path.basename(f.filename, ext);
|
|
const destName = `${base} [FAILED-scan]${ext}`;
|
|
fs.copyFileSync(srcPath, path.join(OUT_DIR, destName));
|
|
copied++;
|
|
manifestRows.push({ image: destName, field: "(entire scan)", match: false, expected: "-", predicted: `ERROR: ${f.error}` });
|
|
}
|
|
|
|
const lines = [
|
|
"# Undetected / mismatched images",
|
|
"",
|
|
`Generated ${detail.timestamp} from the accuracy-check-scan.mts detail dump.`,
|
|
`${copied} of ${detail.validation.length + detail.failed.length} Validation Set images had at least one wrong/missing field or failed to scan.`,
|
|
"",
|
|
"| Image | Field | OK? | Expected | Predicted |",
|
|
"|---|---|---|---|---|"
|
|
];
|
|
for (const r of manifestRows) {
|
|
lines.push(`| ${r.image} | ${r.field} | ${r.match ? "✓" : "✗"} | ${r.expected} | ${r.predicted} |`);
|
|
}
|
|
fs.writeFileSync(path.join(OUT_DIR, "manifest.md"), lines.join("\n") + "\n", "utf8");
|
|
|
|
console.log(`Copied ${copied} images into ${OUT_DIR}, wrote manifest.md (${manifestRows.length} rows).`);
|