import { emptySpec, KNOWN, matchKnown, normalizeToken } from './spec.js'; // Die Muster sind bewusst grosszuegig: Was sie einsammeln, wird // anschliessend gegen die bekannten Werte abgeglichen. Ein Treffer, // der dort nicht besteht, wird verworfen statt geraten. const CAPACITY_PATTERN = /\b([0-9OQBSIL]{1,3})\s?GB\b/g; // Kein \b am Ende: auf Etiketten folgt der Geschwindigkeit oft direkt // ein Buchstabe (PC4-2400T), und dort gibt es keine Wortgrenze. const SPEED_PATTERN = /\bPC4[-\s]?([0-9OQ]{4})/g; const RANK_PATTERN = /\b([0-9OQ][DS]?R[Xx][0-9OQ])\b/g; const PART_NUMBER_PATTERN = /\b([A-Z]{1,3}[0-9]{2,4}[A-Z0-9]{4,}(?:-[A-Z0-9]{2,6})?)\b/g; const DATE_CODE_PATTERN = /(?:^|\s)([0-9]{4})(?=\s|$)/g; function firstMatch(text, pattern, transform) { pattern.lastIndex = 0; let match; while ((match = pattern.exec(text)) !== null) { const value = transform(match[1]); if (value !== null) return value; } return null; } /** * Zerlegt den OCR-Rohtext eines Etiketts in Spec-Felder. * Jeder Kandidat wird gegen die bekannten Werte geprueft; besteht er * die Pruefung nicht, bleibt das Feld offen. */ export function extractFields(rawText) { const spec = emptySpec(); const text = normalizeToken(rawText).replace(/\s+/g, ' '); if (text === '') return spec; spec.capacityGb = firstMatch(text, CAPACITY_PATTERN, (raw) => matchKnown(raw, KNOWN.capacityGb), ); spec.speed = firstMatch(text, SPEED_PATTERN, (raw) => matchKnown(`PC4-${raw}`, KNOWN.speed), ); spec.rank = firstMatch(text, RANK_PATTERN, (raw) => matchKnown(raw, KNOWN.rank)); spec.formFactor = firstMatch(text, /\b(U?L?R?S?O?DIMM)\b/g, (raw) => matchKnown(raw, KNOWN.formFactor), ); // Teilenummer: unter den Kandidaten mit Bindestrich der laengste; // bei gleicher Laenge der zuerst vorkommende. Echte Hersteller- // Teilenummern sind laenger als die kurzen Codes, die sonst auf // Etiketten stehen. PART_NUMBER_PATTERN.lastIndex = 0; const candidates = [...text.matchAll(PART_NUMBER_PATTERN)].map((m) => m[1]); const withDash = candidates.filter((c) => c.includes('-')); spec.partNumber = withDash.length === 0 ? null : withDash.reduce((longest, current) => current.length > longest.length ? current : longest, ); // Datumscode: vierstellige Zahl der Form Jahr-Woche (JJWW), die fuer // sich allein steht. Nur Kandidaten mit plausiblem Jahr (10-39) und // gueltiger Kalenderwoche (01-53) kommen infrage; gibt es mehrere, // gewinnt der zuletzt vorkommende, weil der Datumscode auf diesen // Etiketten am Ende steht. DATE_CODE_PATTERN.lastIndex = 0; const dateCandidates = [...text.matchAll(DATE_CODE_PATTERN)].map((m) => m[1]); const plausibleDates = dateCandidates.filter((raw) => { const year = Number(raw.slice(0, 2)); const week = Number(raw.slice(2, 4)); return year >= 10 && year <= 39 && week >= 1 && week <= 53; }); spec.dateCode = plausibleDates.length === 0 ? null : plausibleDates[plausibleDates.length - 1]; return spec; }