Korrekturen aus zwei Pruefrunden vor der MBE-Vorfuehrung

Demo-Seite:
- Ampel in Kalendertagen, naechstes kommendes Datum, "Termin am"/"Frist bis"
- nie "Deutsch | Deutsch"; Beraterin bekommt immer Deutsch (d=1), das Handy
  erkennt seine Sprache selbst und speichert die PC-Vorgabe nicht dauerhaft
- Code-Feld ausserhalb von #eingabe; tolerante Code-Eingabe; Ablaufdatum
  im Banner; leeres/abgelaufenes Einrichtungs-Kontingent ohne Kaufangebot,
  mit Telefonnummer; nach Aufladen reicht ein Neuladen
- Handy-Modus: endet nach dem ersten Brief, uebersteht ein Neuladen
  (sessionStorage), keine Sackgasse ohne Zugang, ehrlicher Hinweis +
  "Noch einmal senden", wenn das Ergebnis den PC nicht erreicht
- PC wartet geduldig, solange verbunden; meldet langsame Analysen, fehlende
  Verbindung, fehlenden Zugang; Warnung beim Verlassen
- Einwilligung pro Brief, nennt Gesundheit/Herkunft/Aufenthalt
- localStorage nur ueber abgesicherten Helfer; Dateifeld wird geleert
- RTL (bdi, dir=auto), Sprachliste mit deutschem Zusatz, groesserer QR

Worker: KI-Aufruf und Router-catch liefern immer JSON mit CORS; Logs ohne
Briefinhalt; [observability]; Nutzungszaehler je Analyse (Datum, Sprache,
390 Tage); normCode + 30 Fehlversuche; Uebergabe-TTL gleitend.
Werkzeug: Auswertung pro Woche/Sprache, Eingabepruefung, --bis in der Hilfe.
Datenschutz: SCC statt DPF, "in der Regel 30 Tage", Server-Protokolle,
Nutzungszaehler.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
JUSNandClaude Opus 5.5 committed 2026-10-22 20:34:15 +02:00
1 parent 91437d37b4
commit 2af4b7ed41
6 files changed
+448 -102

No files matched your search

+45 -6
View File
@@ -1,8 +1,9 @@
/**
* Einrichtungs-Codes für Beratungsstellen anlegen und verwalten.
*
* node tools/einrichtungs-code.js neu "MBE Donnersberg" MBE --briefe 100 --bis 2026-12-18
* node tools/einrichtungs-code.js neu "MBE Donnersberg" MBE --briefe 100 --monate 1
* node tools/einrichtungs-code.js stand MBE-K7QX
* node tools/einrichtungs-code.js stand MBE-K7QX (mit Nutzung nach Woche und Sprache)
* node tools/einrichtungs-code.js aufladen MBE-K7QX 100
* node tools/einrichtungs-code.js verlaengern MBE-K7QX 2027-06-30
*
@@ -49,8 +50,26 @@ function plusMonate(monate) {
return d.toISOString().slice(0, 10);
}
/** Wie im Worker: „mbe k7qx" → „MBE-K7QX". */
function normCode(c) {
const roh = String(c || '').toUpperCase().replace(/[^A-Z0-9]/g, '');
return roh.length > 4 ? `${roh.slice(0, -4)}-${roh.slice(-4)}` : roh;
}
/** Nutzung je Analyse (vom Worker: nutzung:<walletId>:<datum>:<sprache>:<zufall>). */
function nutzung(walletId) {
let liste = [];
try {
liste = JSON.parse(kv('list', '--prefix', `nutzung:${walletId}:`));
} catch { return null; }
return liste.map((e) => {
const [, , datum, sprache] = e.name.split(':');
return { datum, sprache };
});
}
function ladeCode(code) {
code = String(code || '').toUpperCase();
code = normCode(code);
const walletId = lies(`code:${code}`);
if (!walletId) abbruch(`Code ${code} gibt es nicht.`);
const einrichtung = JSON.parse(lies(`einrichtung:${walletId}`) || '{}');
@@ -58,14 +77,31 @@ function ladeCode(code) {
return { code, walletId, einrichtung, rest };
}
function zeige({ code, einrichtung, rest }) {
function zeige({ code, einrichtung, rest, walletId }) {
const heute = new Date().toISOString().slice(0, 10);
const n = walletId ? nutzung(walletId) : null;
let auswertung = '';
if (n && n.length) {
const proWoche = {}, proSprache = {};
for (const { datum, sprache } of n) {
const d = new Date(`${datum}T00:00:00Z`);
const montag = new Date(d.getTime() - ((d.getUTCDay() + 6) % 7) * 86400000).toISOString().slice(0, 10);
proWoche[montag] = (proWoche[montag] || 0) + 1;
proSprache[sprache] = (proSprache[sprache] || 0) + 1;
}
auswertung = `
Genutzt: ${n.length} Briefe
Pro Woche: ${Object.keys(proWoche).sort().map((w) => `ab ${w}: ${proWoche[w]}`).join(' · ')}
Sprachen: ${Object.entries(proSprache).sort((a, b) => b[1] - a[1]).map(([s, z]) => `${s} ${z}`).join(' · ')}`;
} else if (n) {
auswertung = '\n Genutzt: noch keine Briefe';
}
console.log(`
Einrichtung: ${einrichtung.name}
Code: \x1b[1m${code}\x1b[0m
Übrig: ${rest} Briefe
Gültig bis: ${einrichtung.ablauf}${einrichtung.ablauf < heute ? ' \x1b[31m(abgelaufen)\x1b[0m' : ''}
Link: https://behoerdenklar.de/demo?code=${code}
Link: https://behoerdenklar.de/demo?code=${code}${auswertung}
`);
}
@@ -78,7 +114,10 @@ if (befehl === 'neu') {
abbruch('So geht es: node tools/einrichtungs-code.js neu "MBE Donnersberg" MBE --briefe 100 --monate 1');
}
const briefe = parseInt(option('briefe', '100'), 10);
const ablauf = option('bis', plusMonate(parseInt(option('monate', '1'), 10)));
const monate = parseInt(option('monate', '1'), 10);
const ablauf = option('bis', Number.isFinite(monate) && monate > 0 ? plusMonate(monate) : '');
if (!(briefe > 0)) abbruch('--briefe braucht eine Zahl größer 0, z. B. --briefe 100');
if (!/^\d{4}-\d{2}-\d{2}$/.test(ablauf)) abbruch('--bis braucht ein Datum wie 2026-12-18 (oder --monate 1)');
const code = `${praefix}-${zufall(4)}`;
if (lies(`code:${code}`)) abbruch('Zufällig schon vergeben — einfach nochmal ausführen.');
const walletId = crypto.randomUUID();
@@ -86,7 +125,7 @@ if (befehl === 'neu') {
schreib(`einrichtung:${walletId}`, JSON.stringify({ name, ablauf }));
schreib(`code:${code}`, walletId);
console.log('\n \x1b[32m✓ Code angelegt\x1b[0m');
zeige({ code, einrichtung: { name, ablauf }, rest: briefe });
zeige({ code, einrichtung: { name, ablauf }, rest: briefe, walletId });
} else if (befehl === 'stand') {
zeige(ladeCode(a));
} else if (befehl === 'aufladen') {