feat: OCR-Feldextraktion mit Abgleich gegen bekannte Werte

This commit is contained in:
vchuser
2026-07-28 14:46:52 +02:00
parent 4201643fc5
commit 9098a45aec
2 changed files with 111 additions and 0 deletions
+57
View File
@@ -0,0 +1,57 @@
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: der laengste Kandidat, der nicht der Seriennummer entspricht.
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[0] ?? null;
// Datumscode: vierstellige Zahl, die fuer sich allein steht.
DATE_CODE_PATTERN.lastIndex = 0;
const dateMatch = [...text.matchAll(DATE_CODE_PATTERN)].map((m) => m[1]);
spec.dateCode = dateMatch[0] ?? null;
return spec;
}