feat: Projektgeruest und Spec-Grundlagen

This commit is contained in:
vchuser
2026-07-28 14:11:54 +02:00
parent 82898a1539
commit 3aa54d6c78
8 changed files with 1239 additions and 0 deletions
+14
View File
@@ -0,0 +1,14 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<base href="./" />
<title>RAM-Sortierhilfe</title>
<link rel="stylesheet" href="src/styles.css" />
</head>
<body>
<main id="app"></main>
<script type="module" src="src/main.js"></script>
</body>
</html>
+1077
View File
File diff suppressed because it is too large Load Diff
+27
View File
@@ -0,0 +1,27 @@
{
"name": "ram-sortierhilfe",
"version": "1.0.0",
"description": "> Short description of what this project does.",
"main": "index.js",
"directories": {
"doc": "docs"
},
"scripts": {
"test": "node --test test/",
"dev": "vite --host 0.0.0.0",
"build": "vite build",
"preview": "vite preview --host 0.0.0.0 --port ${PORT:-4173}"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "module",
"private": "true",
"devDependencies": {
"vite": "^8.1.5"
},
"dependencies": {
"tesseract.js": "^7.0.0",
"zxing-wasm": "^3.1.2"
}
}
+1
View File
@@ -0,0 +1 @@
document.querySelector('#app').textContent = 'RAM-Sortierhilfe';
+55
View File
@@ -0,0 +1,55 @@
// Reines Modul: kein window, kein document, kein localStorage.
/** Bekannte Werte. Der Erwartungsraum ist klein - das repariert OCR-Lesefehler. */
export const KNOWN = {
capacityGb: [4, 8, 16, 32, 64, 128, 256],
speed: [
'PC4-1600', 'PC4-1866', 'PC4-2133', 'PC4-2400',
'PC4-2666', 'PC4-2933', 'PC4-3200',
],
formFactor: ['UDIMM', 'SODIMM', 'RDIMM', 'LRDIMM'],
rank: ['1Rx8', '1Rx4', '2Rx8', '2Rx4', '4Rx4', '8Rx4', '2DRx4', '4DRx4', '2DRx8'],
};
/** @returns {{capacityGb: null, formFactor: null, rank: null, speed: null, partNumber: null, dateCode: null}} */
export function emptySpec() {
return {
capacityGb: null,
formFactor: null,
rank: null,
speed: null,
partNumber: null,
dateCode: null,
};
}
/** Grossbuchstaben, ohne Rand-Leerzeichen, mit vereinheitlichten Bindestrichen. */
export function normalizeToken(raw) {
if (typeof raw !== 'string') return '';
return raw
.replace(/[-―−]/g, '-')
.trim()
.toUpperCase();
}
/**
* Merkmalskombination, die ueber die Stapelzugehoerigkeit entscheidet.
* Der Datumscode gehoert bewusst nicht dazu.
*/
export function fingerprint(spec) {
const parts = [
spec.formFactor,
spec.capacityGb,
spec.rank,
spec.speed,
spec.partNumber,
];
return parts
.map((value) => (value === null || value === undefined ? '?' : normalizeToken(String(value))))
.join('|');
}
/** Ohne Kapazitaet ist keine sinnvolle Zuordnung moeglich. */
export function isUsable(spec) {
return typeof spec.capacityGb === 'number' && Number.isFinite(spec.capacityGb);
}
+2
View File
@@ -0,0 +1,2 @@
:root { color-scheme: dark; }
body { margin: 0; font-family: system-ui, sans-serif; }
+56
View File
@@ -0,0 +1,56 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { KNOWN, emptySpec, normalizeToken, fingerprint, isUsable } from '../src/spec.js';
test('KNOWN enthaelt die erwarteten Wertelisten', () => {
assert.ok(KNOWN.capacityGb.includes(64));
assert.ok(KNOWN.speed.includes('PC4-2400'));
assert.ok(KNOWN.formFactor.includes('LRDIMM'));
assert.ok(KNOWN.rank.includes('4DRx4'));
});
test('emptySpec liefert alle Felder als null', () => {
assert.deepEqual(emptySpec(), {
capacityGb: null,
formFactor: null,
rank: null,
speed: null,
partNumber: null,
dateCode: null,
});
});
test('normalizeToken macht gross, trimmt und vereinheitlicht Bindestriche', () => {
assert.equal(normalizeToken(' pc42400 '), 'PC4-2400');
assert.equal(normalizeToken('4drx4'), '4DRX4');
assert.equal(normalizeToken(''), '');
assert.equal(normalizeToken(null), '');
});
test('fingerprint setzt die Felder in fester Reihenfolge zusammen', () => {
const spec = {
capacityGb: 64,
formFactor: 'LRDIMM',
rank: '4DRx4',
speed: 'PC4-2400',
partNumber: 'M386A8K40BM1-CRC4Y',
dateCode: '1908',
};
assert.equal(fingerprint(spec), 'LRDIMM|64|4DRX4|PC4-2400|M386A8K40BM1-CRC4Y');
});
test('fingerprint laesst den Datumscode aussen vor', () => {
const a = { capacityGb: 64, formFactor: 'LRDIMM', rank: '4DRx4', speed: 'PC4-2400', partNumber: 'X', dateCode: '1908' };
const b = { ...a, dateCode: '2013' };
assert.equal(fingerprint(a), fingerprint(b));
});
test('fingerprint markiert fehlende Felder mit einem Fragezeichen', () => {
const spec = { ...emptySpec(), capacityGb: 32 };
assert.equal(fingerprint(spec), '?|32|?|?|?');
});
test('isUsable verlangt eine Kapazitaet', () => {
assert.equal(isUsable({ ...emptySpec(), capacityGb: 64 }), true);
assert.equal(isUsable(emptySpec()), false);
});
+7
View File
@@ -0,0 +1,7 @@
import { defineConfig } from 'vite';
export default defineConfig({
// Relative Basis, damit die App auch hinter einem Pfad-Proxy laeuft.
base: './',
server: { host: '0.0.0.0' },
});