#!/usr/bin/env node /** * Quick manual verification for plate normalization. * * Usage: node scripts/verify-plate-normalization.mjs * * Demonstrates that: * 339ه‍77 (ه + U+200D) -> 339ه77 -> letter ه -> code 15 * 339ي77 (Arabic ي) -> 339ی77 -> letter ی -> code 16 */ // ---------- paste the pure function here so no build needed ---------- function normalizePlateText(text) { if (!text) return text; let result = String(text); result = result.normalize("NFKC"); result = result.replace(/[\u200C\u200D\u200E\u200F\uFEFF]/g, ""); result = result.replace(/\u064A/g, "\u06CC"); // Arabic ي → Persian ی result = result.trim(); return result; } // ---------- the canonical plate-letter → code mapping ---------- const PLATE_LETTER_MAP = { الف: 1, ب: 2, پ: 3, ج: 4, د: 5, س: 6, ص: 7, ط: 8, ع: 9, ق: 10, ل: 11, م: 12, ن: 13, و: 14, ه: 15, ی: 16, ک: 17, ژ: 18, ت: 19, ث: 20, ز: 21, ش: 22, ف: 23, گ: 24, }; function extractAndMap(plateString) { const normalized = normalizePlateText(plateString); // Simple parser: digits | letter | digits (left+right digits, 1 letter) const match = normalized.match(/^(\d+)([^\d]+)(\d+)$/); if (!match) return { input: plateString, normalized, error: "no match" }; const [, leftDigits, letterRaw, rightDigits] = match; const letter = normalizePlateText(letterRaw); const code = PLATE_LETTER_MAP[letter]; return { input: plateString, normalized, leftDigits, letter, rightDigits, code, ok: code !== undefined, }; } // ---------- test cases ---------- const cases = [ { desc: "ه + ZWJ (the original bug)", plate: "339ه\u200D77" }, { desc: "Arabic ي → Persian ی", plate: "339ي77" }, { desc: "ه + ZWNJ", plate: "339ه\u200C77" }, { desc: "RTL mark around letter", plate: "339\u200Fه\u200E77" }, { desc: "BOM + ZWJ combined", plate: "\uFEFF339ه\u200D77" }, { desc: "clean letter (no junk)", plate: "339ه77" }, { desc: "full plate: الف", plate: "11الف22" }, { desc: "full plate: ی", plate: "55ی99" }, ]; console.log("Plate Normalization Verification\n"); console.log("=".repeat(72)); let allPassed = true; for (const { desc, plate } of cases) { const r = extractAndMap(plate); const status = r.ok ? "PASS" : "FAIL"; if (!r.ok) allPassed = false; console.log(`\n[${status}] ${desc}`); console.log(` Input: ${JSON.stringify(plate)}`); console.log(` Normalized: ${JSON.stringify(r.normalized)}`); if (r.letter) { console.log(` Letter: ${r.letter} (U+${r.letter.charCodeAt(0).toString(16).toUpperCase().padStart(4, "0")})`); } console.log(` Code: ${r.code ?? "undefined"}`); } console.log("\n" + "=".repeat(72)); console.log(allPassed ? "\n All tests PASSED ✓" : "\n Some tests FAILED ✗"); process.exit(allPassed ? 0 : 1);