feat: tabellengesteuerter Teilenummer-Decoder

Deckt die Samsung-Referenz-Teilenummer aus dem physisch vorhandenen
Modul ab. Unbekannte Fragmente (z.B. nicht gelistete Dichte-Codes)
liefern bewusst null statt einer Vermutung - das ist der geplante
Uebergang zum OCR-Weg, kein Fehlerfall.

Das im Task-Brief vorgegebene Regex-Muster liess das literale 'A' der
Dichte-Kennung ausserhalb der Erfassungsgruppe, wodurch die
Tabellensuche nach 'A8K40' nie greifen konnte. Muster korrigiert, damit
die ebenfalls vorgegebenen Tests bestehen; Tabelleninhalte und
Warnkommentar blieben wortgleich.
This commit is contained in:
vchuser
2026-07-28 14:29:13 +02:00
parent 8199d25a6e
commit c580d4f924
3 changed files with 115 additions and 0 deletions
+34
View File
@@ -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;
}
+40
View File
@@ -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<bauform><dichte>-<rev(3)>-<speed><rest>
// 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',
},
},
];
+41
View File
@@ -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);
});