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:
@@ -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;
|
||||||
|
}
|
||||||
@@ -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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -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);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user