diff --git a/src/pn-decoder.js b/src/pn-decoder.js new file mode 100644 index 0000000..4980362 --- /dev/null +++ b/src/pn-decoder.js @@ -0,0 +1,34 @@ +import { emptySpec, normalizeToken } from './spec.js'; +import { VENDOR_TABLES } from './pn-tables.js'; + +/** + * Leitet aus einer Hersteller-Teilenummer die Spec-Felder ab. + * Nicht ableitbare Felder bleiben null - das ist kein Fehler, + * sondern der regulaere Uebergang zum OCR-Weg. + */ +export function decodePartNumber(pn) { + const spec = emptySpec(); + const normalized = normalizeToken(pn); + if (normalized === '') return spec; + + spec.partNumber = normalized; + + for (const table of VENDOR_TABLES) { + const match = normalized.match(table.pattern); + if (!match) continue; + + const [, formCode, densityCode, speedCode] = match; + + spec.formFactor = table.formFactor[formCode] ?? null; + spec.speed = table.speed[speedCode] ?? null; + + const density = table.density[densityCode]; + if (density) { + spec.capacityGb = density.capacityGb; + spec.rank = density.rank; + } + break; + } + + return spec; +} diff --git a/src/pn-tables.js b/src/pn-tables.js new file mode 100644 index 0000000..d1665eb --- /dev/null +++ b/src/pn-tables.js @@ -0,0 +1,40 @@ +// Herstellertabellen fuer den Teilenummer-Decoder. +// +// ACHTUNG: Nur der Samsung-Eintrag 'A8K40' und der Geschwindigkeitscode 'CRC' +// sind gegen ein reales Modul geprueft (64GB 4DRx4 PC4-2400T LRDIMM, +// M386A8K40BM1-CRC4Y). Alle uebrigen Eintraege stammen aus der veroeffentlichten +// Systematik und muessen vor produktivem Einsatz gegen reale Module bestaetigt +// werden - siehe Task 14. Ein falscher Eintrag faellt beim Sortieren dadurch +// auf, dass der OCR-Klartext dem dekodierten Wert widerspricht. + +export const VENDOR_TABLES = [ + { + vendor: 'Samsung', + // M-- + // Hinweis: Die im Brief angegebene Fassung dieses Musters liess die + // fuehrende Generationskennung 'A' ausserhalb der Erfassungsgruppe und + // erfasste dadurch nie den vollstaendigen Dichte-Code 'A8K40' - die + // Tabellensuche schlug immer fehl. Hier korrigiert: 'A' gehoert zur + // erfassten Dichte-Gruppe, die Revision hat feste Laenge 3. + pattern: /^M(\d{3})(A[A-Z0-9]{4})[A-Z0-9]{3}-([A-Z]{3})/, + formFactor: { + 378: 'UDIMM', + 391: 'UDIMM', + 393: 'RDIMM', + 386: 'LRDIMM', + 471: 'SODIMM', + 474: 'SODIMM', + }, + density: { + // geprueft am Referenzmodul: + A8K40: { capacityGb: 64, rank: '4DRx4' }, + }, + speed: { + CPB: 'PC4-2133', + CRC: 'PC4-2400', + CTD: 'PC4-2666', + CVF: 'PC4-2933', + CWE: 'PC4-3200', + }, + }, +]; diff --git a/test/pn-decoder.test.js b/test/pn-decoder.test.js new file mode 100644 index 0000000..5cd18b4 --- /dev/null +++ b/test/pn-decoder.test.js @@ -0,0 +1,41 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { decodePartNumber } from '../src/pn-decoder.js'; + +test('dekodiert die Samsung-Teilenummer aus dem Referenzmodul', () => { + const spec = decodePartNumber('M386A8K40BM1-CRC4Y'); + assert.equal(spec.formFactor, 'LRDIMM'); + assert.equal(spec.speed, 'PC4-2400'); + assert.equal(spec.capacityGb, 64); + assert.equal(spec.rank, '4DRx4'); + assert.equal(spec.partNumber, 'M386A8K40BM1-CRC4Y'); +}); + +test('erkennt die Bauform auch ohne bekannten Dichte-Code', () => { + const spec = decodePartNumber('M393A2K43BB1-CTD'); + assert.equal(spec.formFactor, 'RDIMM'); + assert.equal(spec.speed, 'PC4-2666'); + assert.equal(spec.capacityGb, null, 'unbekannter Dichte-Code laesst das Feld offen'); + assert.equal(spec.rank, null); + assert.equal(spec.partNumber, 'M393A2K43BB1-CTD'); +}); + +test('normalisiert Kleinschreibung und Leerzeichen', () => { + const spec = decodePartNumber(' m386a8k40bm1-crc4y '); + assert.equal(spec.capacityGb, 64); + assert.equal(spec.partNumber, 'M386A8K40BM1-CRC4Y'); +}); + +test('unbekannter Hersteller liefert ein leeres Spec mit gesetzter Teilenummer', () => { + const spec = decodePartNumber('7325773'); + assert.equal(spec.formFactor, null); + assert.equal(spec.speed, null); + assert.equal(spec.capacityGb, null); + assert.equal(spec.partNumber, '7325773'); +}); + +test('leere Eingabe liefert ein vollstaendig leeres Spec', () => { + const spec = decodePartNumber(''); + assert.equal(spec.partNumber, null); + assert.equal(spec.capacityGb, null); +});