feat: Projektgeruest und Spec-Grundlagen
This commit is contained in:
+14
@@ -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>
|
||||
Generated
+1077
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
document.querySelector('#app').textContent = 'RAM-Sortierhilfe';
|
||||
+55
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
:root { color-scheme: dark; }
|
||||
body { margin: 0; font-family: system-ui, sans-serif; }
|
||||
@@ -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(' pc4‑2400 '), '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);
|
||||
});
|
||||
@@ -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' },
|
||||
});
|
||||
Reference in New Issue
Block a user