Pengenalan Dekode VIN Massal

Dekode satu VIN adalah jalur mudah. Produk nyata mendekode armada, daftar marketplace, dan antrean impor. NHTSA vPIC menyediakan DecodeVinValuesBatch untuk mengirim banyak VIN dalam satu permintaan. Ini mengurangi jumlah permintaan, tetapi tidak menghapus batas kecepatan dan tidak menjamin setiap baris berhasil. Artikel ini membahas cara kerja dekode massal, cara menangani kegagalan parsial, dan pola TypeScript untuk menjaga UI tetap jujur saat separuh batch berhasil dan separuh gagal.

Alasan batch ada. DecodeVinValues (dan varian Extended) hanya menerima satu VIN. Jika dealer mengunggah 200 daftar, 200 panggilan berurutan lambat, berlebihan, dan mudah dibatasi. Batch memungkinkan POST daftar VIN dan menerima array Results dalam satu respons. Gunakan batch saat: memvalidasi CSV atau spreadsheet VIN; halaman marketplace menampilkan banyak daftar dan perlu atribut identitas tiap item; pekerjaan malam menyegarkan cache dekode untuk kumpulan VIN tetap. Jangan gunakan batch untuk menggantikan cache. Jika VIN sama didekode tiap tampilan halaman, cache berdasarkan VIN terlebih dahulu. Batch untuk banyak VIN berbeda, bukan untuk mengulang VIN sama.

Yang diharapkan endpoint. Endpoint batch vPIC menerima daftar VIN terpisah (umum: titik koma di body formulir, tergantung rute). Respons mengembalikan struktur hasil datar mirip dekode tunggal: tiap baris berisi Make, Model, ModelYear, ErrorCode, ErrorText, dan atribut lain. Model mental: satu respons HTTP dapat berisi banyak hasil logis. HTTP 200 berarti panggilan batch diterima dan diparsing, bukan berarti semua VIN berhasil didekode. Tiap baris adalah cerita sukses atau gagal sendiri. Perlakukan batch sebagai peta dari VIN masukan ke baris dekode (atau baris error), bukan boolean tunggal.

Manajemen Batas Kecepatan API

Batas kecepatan tetap berlaku. API publik NHTSA gratis dan bersama. Meledakkan ratusan panggilan batch per menit memicu kegagalan sementara, body kosong, atau latensi tinggi. Aturan produk: batasi permintaan keluar bersamaan (satu atau pool kecil); batasi VIN per batch (mulai 50, sesuaikan dari latensi dan tingkat error nyata); lakukan backoff dengan jitter untuk 429 / 5xx / Results kosong; deduplikasi VIN sebelum panggilan agar tidak membayar dua kali; simpan baris sukses agar retry hanya mencakup yang gagal. Batch mengurangi overhead koneksi, bukan memberi throughput tak terbatas.

const MAX_PER_BATCH = 50;
const MAX_CONCURRENT = 1;

export function chunkVins(vins: string[], size = MAX_PER_BATCH): string[][] {
  const unique = [...new Set(vins.map((v) => v.trim().toUpperCase()).filter(Boolean))];
  const chunks: string[][] = [];
  for (let i = 0; i < unique.length; i += size) {
    chunks.push(unique.slice(i, i + size));
  }
  return chunks;
}

Bagi menjadi chunk. Proses dengan MAX_CONCURRENT. Catat jumlah VIN yang dicoba versus jumlah baris yang diterima.

Menangani Kasus Kegagalan Parsial

Kegagalan parsial normal. Dalam batch 50 VIN: 40 baris dengan Make / Model / ModelYear solid; 6 baris dengan ErrorText terkait check digit atau dekode tidak lengkap; 4 baris hilang atau tidak berguna karena masukan terpotong, diberi padding, atau bukan 17 karakter. Jika kode memeriksa results.every(r => r.Make) dan melempar saat kosong, data bagus terbuang. Jika hanya memeriksa response.ok, kartu kosong ditampilkan seolah lengkap. Kontrak lebih baik:

type BatchItem =
  | { vin: string; status: "ok"; make: string; model: string; year: string; raw: Record<string, unknown> }
  | { vin: string; status: "partial"; make?: string; model?: string; year?: string; notes: string; raw: Record<string, unknown> }
  | { vin: string; status: "failed"; reason: string };

function clean(raw: unknown): string | null {
  if (raw == null) return null;
  const s = String(raw).trim();
  if (!s || s === "0" || /^not\s*applicable$/i.test(s)) return null;
  return s;
}

export function classifyRow(vin: string, row: Record<string, unknown>): BatchItem {
  const make = clean(row.Make);
  const model = clean(row.Model);
  const year = clean(row.ModelYear);
  const notes = clean(row.ErrorText) ?? clean(row.ErrorCode) ?? "";

  if (make && model && year) {
    if (notes) {
      return { vin, status: "partial", make, model, year, notes, raw: row };
    }
    return { vin, status: "ok", make, model, year, raw: row };
  }

  if (make || model || year) {
    return {
      vin,
      status: "partial",
      make: make ?? undefined,
      model: model ?? undefined,
      year: year ?? undefined,
      notes: notes || "Incomplete identity fields",
      raw: row,
    };
  }

  return {
    vin,
    status: "failed",
    reason: notes || "No usable decode fields",
  };
}

Aturan UI: tampilkan baris ok normal; tampilkan baris parsial dengan catatan singkat ("Decode incomplete: ..."); tampilkan baris gagal sebagai "Could not decode" dengan alasan, bukan Make/Model kosong. Jangan menciptakan trim, merek judul, atau riwayat untuk mengisi celah.

Menyelaraskan masukan dan keluaran. Respons batch tidak selalu mempertahankan urutan asli jika ada string buruk. Simpan daftar permintaan, normalisasi VIN sama sebelum dan sesudah, dan cocokkan berdasarkan string VIN bila memungkinkan.

export function indexByVin(items: BatchItem[]): Map<string, BatchItem> {
  return new Map(items.map((i) => [i.vin, i]));
}

export function mergeBatch(
  requested: string[],
  items: BatchItem[]
): BatchItem[] {
  const byVin = indexByVin(items);
  return requested.map((raw) => {
    const vin = raw.trim().toUpperCase();
    return (
      byVin.get(vin) ?? {
        vin,
        status: "failed" as const,
        reason: "Missing from batch response",
      }
    );
  });
}

Jika VIN tidak ada di Results, perlakukan sebagai item gagal untuk VIN itu, bukan sukses dengan field kosong.

Strategi Retry yang Efisien

Retry tanpa menggandakan kerja. Retry hanya baris gagal dan mungkin parsial, bukan seluruh batch. Jalankan batch 1..N. Kumpulkan VIN gagal (dan opsional parsial tipis). Tunggu dengan exponential backoff. Re-batch hanya VIN itu. Batasi total percobaan (mis. 3). Jangan loop tak terbatas untuk VIN yang gagal validasi check digit. Validasi panjang, charset (tanpa I/O/Q), dan check digit lokal sebelum panggilan jaringan. Sampah dalam batch membuang kuota dan mencemari ErrorText.

export async function decodeAll(
  vins: string[],
  decodeChunk: (chunk: string[]) => Promise<BatchItem[]>
): Promise<BatchItem[]> {
  const out: BatchItem[] = [];
  for (const chunk of chunkVins(vins)) {
    const first = await decodeChunk(chunk);
    out.push(...first.filter((i) => i.status !== "failed"));
    const retry = first.filter((i) => i.status === "failed").map((i) => i.vin);
    if (retry.length === 0) continue;
    await new Promise((r) => setTimeout(r, 1000 + Math.random() * 500));
    const second = await decodeChunk(retry);
    out.push(...second);
  }
  return out;
}

Sesuaikan jeda dari error teramati. Lebih baik membosankan dan lambat daripada diblokir.

Teks produk untuk UI batch. Saat pengguna mengunggah 100 VIN: "Decoded 86 of 100. 9 incomplete. 5 failed validation." Tautkan tiap baris gagal ke alasannya. Tawarkan unduhan sukses saja, atau sukses plus kolom catatan. Jangan beri label unggahan "fully verified" hanya karena sebagian besar baris mengembalikan Make. Dekode batch adalah pengayaan identitas, bukan penyelamatan, banjir, odometer, lien, atau riwayat pencurian.

Daftar Periksa Operasional dan Kesimpulan

Daftar periksa operasional. Validasi dan deduplikasi sebelum batching. Bagi dan serialisasi panggilan keluar. Klasifikasikan tiap baris: ok / partial / failed. Retry hanya yang gagal. Cache sukses berdasarkan VIN dengan TTL sesuai frekuensi pengecekan ulang. Catat jumlah, bukan daftar VIN lengkap, bila bisa menghindari menyimpan identifier mirip PII lebih lama dari perlu. Tampilkan ErrorText ke operator; sembunyikan jargon dari pembeli akhir atau terjemahkan. Pengungkapan: penulis memelihara VIN Lookup, dekode VIN gratis untuk atribut pabrikan berbasis data publik gaya NHTSA. Pola batch di atas sama dengan aturan kejujuran dekode tunggal: tampilkan apa yang kembali, biarkan kosong tetap kosong, dan jangan berpura-pura panggilan HTTP batch sukses berarti semua kendaraan "clear".

Kesimpulan. DecodeVinValuesBatch untuk banyak VIN berbeda, bukan izin mengabaikan cache atau batas kecepatan. Kegagalan parsial diharapkan; modelkan secara eksplisit. Cocokkan berdasarkan VIN, retry hanya gagal, validasi lokal terlebih dahulu. Laporkan jumlah jujur di UI. Jika sudah mengirim dekode satu VIN, menambah batch sebagian besar soal antrean dan klasifikasi. Bagian sulit adalah kepercayaan produk saat spreadsheet hanya sebagian besar benar.