Bildleser: den Titel gegenlesen -- und ein Protokollwort, das kein Satz war

ZWEI KLEINE SACHEN, BEIDE DERSELBEN ART: Etwas steht still da und
sieht fast richtig aus.

1. DER TITEL WIRD GEGENGELESEN

Anlass war ein echter Fall aus dem Betrieb: Filipe hat den Bildweg
benutzt, und aus "Refined visuals Goal 1.0" wurde "Refined visvals
Goal 1.0" -- ein u als v. Genau die Verwechslung, die man beim
Korrekturlesen ueberliest, weil das Wort noch fast richtig aussieht.

NACHGEMESSEN, OB DIE SPRACHWAHL HILFT: auf dem Server gegen die
gespeicherte Pruefdatei mit deu, eng, deu+eng und eng+deu -- alle vier
lesen "visuals" RICHTIG. Der Fehler liess sich also nicht nachstellen,
und eine Einstellung zu aendern, deren Wirkung man nicht messen kann,
ist Raten, das danach wie eine Loesung aussieht. Es bleibt bei `deu`.

Was stattdessen hilft: Der Titel steht auf der Seite ZWEIMAL -- oben
als Ueberschrift (gross, fett) und im Raster hinter "Gruppenname"
(klein, normal). Zwei Schriftgroessen heisst zwei Gelegenheiten, sich
zu verlesen, und zwar unabhaengig voneinander. Gehen die Lesungen
auseinander, sagt die Kachel es, und beide stehen im Auszug.

EIN HINWEIS, KEINE ABLEHNUNG -- anders als beim Zeitraum. Ein falsches
Datum ist eine Falschauskunft: Die Kachel behauptet einen Termin, den
sie nicht kennt, und niemand sieht es ihr an. Ein verlesener Buchstabe
im Titel ist ein Schoenheitsfehler; daran eine ganze Kachel scheitern
zu lassen waere unverhaeltnismaessig.

2. "Kampagne bremse" IST KEIN SATZ

Die Protokollseite baut ihren Anzeigetext aus dem Schluessel:
`aktionLesbar()` in personen.js macht aus Unterstrichen Leerzeichen
und schreibt nur den ersten Buchstaben gross. Aus `kampagne_bremse`
wurde damit "Kampagne bremse" -- Maschinensprache mit einem grossen
Anfangsbuchstaben.

Gemessen an allen sechs Woertern dieses Hauses war es das einzige
kaputte: "Kampagne eingelesen", "Kampagne gescheitert", "Event
geteilt" und sogar "Event zurueckgezogen" (die Umlautliste macht
daraus "zurueckgezogen" mit u-Umlaut) lesen sich als Satz. Die Regel
dahinter: Hauptwort plus Mittelwort, nie zwei Hauptwoerter.

UND DAS UMBENENNEN WAR UMSONST ZU HABEN -- vorher gezaehlt, weil es
bei Altbestand eine andere Rechnung waere: In der Live-Datenbank steht
KEINE Zeile mit dem alten Wort (die Bremse hat nie gegriffen; zum
Vergleich event_geteilt 7, kampagne_eingelesen 3). Es gibt also
nichts, was danach krumm angezeigt wuerde.

GEPRUEFT: 48 (Bildleser, +5 mit Gegenprobe, dass der Hinweis NICHT
immer kommt), 190 (Route, unveraendert). Keine ausgelieferte Datei
beruehrt, also kein neuer Stempel noetig.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-10-08 12:17:57 +02:00
co-authored by Claude Opus 5
parent 0d52c4dc05
commit 354dfd9be9
4 changed files with 133 additions and 6 deletions
+41 -2
View File
@@ -402,14 +402,47 @@ export function leseBild(text) {
/* ERST „Gruppenname", DANN die Kopfzeile. Im Raster steht der Titel
ohne Abzeichen und ohne Bedienelemente daneben -- also sauberer.
Die Kopfzeile ist die Ausweiche. */
let titel = titelSaeubern(feld.gruppenname || "");
if (!titel || titel.length < 2) titel = titelSaeubern(alle[0]);
const titelRaster = titelSaeubern(feld.gruppenname || "");
const titelKopf = titelSaeubern(alle[0] || "");
let titel = titelRaster && titelRaster.length >= 2 ? titelRaster : titelKopf;
if (!titel || titel.length < 2) {
throw new BildUnlesbar(
"auf dem Bild war kein Titel zu lesen", "pflicht");
}
titel = titel.slice(0, 160);
/* ---- DER TITEL WIRD GEGENGELESEN (08.10.2026) ----------------
Er steht auf der Seite ZWEIMAL: oben als Ueberschrift (gross
und fett) und im Raster hinter „Gruppenname" (klein und
normal). Zwei Schriftgroessen heisst zwei Gelegenheiten, sich
zu verlesen -- und die Texterkennung verliest sich bei beiden
unabhaengig voneinander.
ANLASS WAR EIN ECHTER FALL: Am 08.10.2026 wurde aus „Refined
visuals Goal 1.0" ein „Refined visvals Goal 1.0". Ein u als v
-- genau die Verwechslung, die man beim Korrekturlesen
ueberliest, weil das Wort noch fast richtig aussieht. Auf der
Kachel stand sie still da.
EIN HINWEIS, KEINE ABLEHNUNG -- anders als beim Zeitraum. Ein
falsches Datum ist eine Falschauskunft: Die Kachel behauptet
einen Termin, den sie nicht kennt, und niemand sieht es ihr an.
Ein verlesener Buchstabe im Titel ist ein Schoenheitsfehler;
daran eine ganze Kachel scheitern zu lassen waere unverhaeltnis-
maessig. Also: anlegen, und dazusagen, wo man hinsehen soll.
VERGLICHEN WIRD OHNE GROSS-/KLEINSCHREIBUNG und ohne
Leerraum: Die Ueberschrift steht in einer anderen Schrift, und
ob dort ein Leerzeichen mehr steht, ist kein Befund. */
const nurKern = (s) => String(s || "").toLocaleLowerCase("de-DE")
.replace(/\s+/g, " ").trim();
if (titelRaster && titelKopf && nurKern(titelRaster) !== nurKern(titelKopf)) {
hinweise.push(`Der Titel steht zweimal auf dem Bild und wurde `
+ `unterschiedlich gelesen („${titelRaster}“ und „${titelKopf}“) `
+ "— bitte den Titel kurz nachsehen.");
}
/* ---- Zeitzone -------------------------------------------------- */
const zoneTreffer = ZONE.exec(roh);
const zone = zoneTreffer ? zoneTreffer[1] : null;
@@ -593,6 +626,12 @@ export function leseBild(text) {
erkannter_text: roh.slice(0, 8000),
erkannte_zeilen: alle.length,
zeitraum_quelle: quelle,
/* BEIDE TITEL KOMMEN MIT. Faellt spaeter auf, dass der
falsche gewonnen hat, steht der andere schon da -- und man
sieht, ob die Texterkennung sich einmal oder zweimal
verlesen hat. */
titel_raster: titelRaster || null,
titel_kopf: titelKopf || null,
zeitraum_gegenprobe: gegenStart ? `${gegenStart}..${gegenEnde}` : null,
},
};
+64
View File
@@ -243,6 +243,70 @@ melde("=== 2. Lieber keine Kachel als eine mit erfundenem Termin ===");
ok(h.hinweise.some((x) => /nur an einer Stelle/i.test(x)),
` und die Kachel sagt, dass nicht gegengeprueft werden konnte`);
/* ---- DER TITEL WIRD GEGENGELESEN (08.10.2026) --------------
ANLASS WAR EIN ECHTER FALL: Filipe hat den Bildweg benutzt, und
aus „Refined visuals Goal 1.0“ wurde „Refined visvals Goal 1.0“
— ein u als v. Genau die Verwechslung, die man beim
Korrekturlesen überliest, weil das Wort noch fast richtig
aussieht. Auf der Kachel stand sie still da.
NACHGEMESSEN, OB DIE SPRACHWAHL HILFT: Auf dem Server gegen die
gespeicherte Prüfdatei mit `deu`, `eng`, `deu+eng` und
`eng+deu` — alle vier lesen „visuals“ RICHTIG. Der Fehler ließ
sich also nicht nachstellen, und eine Einstellung zu ändern,
deren Wirkung man nicht messen kann, ist Raten. Es bleibt bei
`deu`.
WAS STATTDESSEN HILFT: Der Titel steht auf der Seite ZWEIMAL —
oben als Überschrift (groß, fett) und im Raster hinter
„Gruppenname“ (klein, normal). Zwei Schriftgrößen heißt zwei
Gelegenheiten, sich zu verlesen, und zwar unabhängig
voneinander. Gehen die beiden Lesungen auseinander, sagt die
Kachel es.
EIN HINWEIS, KEINE ABLEHNUNG — anders als beim Zeitraum. Ein
falsches Datum ist eine Falschauskunft; ein verlesener
Buchstabe im Titel ein Schönheitsfehler. Daran eine ganze
Kachel scheitern zu lassen wäre unverhältnismäßig. */
{
const zweierlei = [
"【Star Light】Refined visvals Goal 1.0 In Bearbeitung",
"Gruppenname 【Star Light】Refined visuals Goal 1.0 Zeitzone Europe/Berlin",
"Daten 01.10.2026 00:00 ~ 31.10.2026 22:59",
].join("\n");
const g = leseBild(zweierlei);
/* ERST BELEGEN, DASS DIE KACHEL ÜBERHAUPT ENTSTEHT. Ohne diese
Zeile wäre das Folgende auch bei einer Ablehnung grün. */
ok(g.pflicht.titel.includes("Refined"),
`die Kachel entsteht trotzdem: ${JSON.stringify(g.pflicht.titel)}`);
ok(g.hinweise.some((h) => /zweimal auf dem Bild/i.test(h)),
`und sagt, dass der Titel zweimal verschieden gelesen wurde: `
+ `${JSON.stringify(g.hinweise.find((h) => /zweimal/i.test(h)) || null)}`);
/* BEIDE LESUNGEN KOMMEN MIT. Fällt später auf, dass die falsche
gewonnen hat, steht die andere schon da. */
ok(g.auszug.titel_raster && g.auszug.titel_kopf
&& g.auszug.titel_raster !== g.auszug.titel_kopf,
`und beide stehen im Auszug: ${JSON.stringify(
[g.auszug.titel_raster, g.auszug.titel_kopf])}`);
/* GEGENPROBE: Stimmen beide überein, gibt es KEINEN Hinweis.
Ohne sie wäre „der Hinweis kommt“ auch dann grün, wenn er
immer käme — und ein Hinweis, der immer kommt, wird
überlesen (CLAUDE.md, 03.09.2026). */
const einig = zweierlei.replace("visvals", "visuals");
const h = leseBild(einig);
ok(!h.hinweise.some((x) => /zweimal auf dem Bild/i.test(x)),
`Gegenprobe: bei zwei gleichen Lesungen kommt kein Hinweis `
+ `(${h.hinweise.length} Hinweise)`);
/* UND DER ECHTE ERKENNUNGSTEXT LÖST IHN AUCH NICHT AUS. Sonst
stünde er an jeder Kachel, und das wäre derselbe Fehler. */
const echt = leseBild(ERKANNT);
ok(!echt.hinweise.some((x) => /zweimal auf dem Bild/i.test(x)),
"und am gespeicherten Erkennungstext ebenso wenig");
}
/* Ein Ende vor dem Anfang ist ein Zahlendreher, kein Zeitraum. */
wirft("Kampagne Z\nGruppenname Kampagne Z\nDaten 20.11.2026 ~ 05.11.2026",
"pflicht", "Ende vor Anfang");
+1 -1
View File
@@ -1487,7 +1487,7 @@ melde("=== 14. Zwanzig in einer Stunde, dann ist Schluss ===");
/* UND DIE BREMSE VERLÄNGERT SICH NICHT SELBST: Ihre eigene Meldung
zählt nicht mit, sonst käme man nie wieder heraus. */
const bremsZaehler = dbLesen("SELECT COUNT(*) AS n FROM protokoll"
+ " WHERE aktion = 'kampagne_bremse'")[0].n;
+ " WHERE aktion = 'kampagne_gebremst'")[0].n;
ok(bremsZaehler >= 1, `${bremsZaehler} Bremsvorgänge stehen im Protokoll`);
const gezaehlt = dbLesen("SELECT COUNT(*) AS n FROM protokoll"
+ " WHERE aktion IN ('kampagne_eingelesen','kampagne_gescheitert','kampagne_schon_da')"
+27 -3
View File
@@ -235,7 +235,7 @@ export function istCreatorNetwork(roh) {
* einen Abruf nach draussen aus, ohne gezaehlt zu werden. Gemessen:
* 21 Versuche, 0 gebremst.
*
* "kampagne_bremse" steht BEWUSST nicht in der Liste: Sonst zaehlte
* "kampagne_gebremst" steht BEWUSST nicht in der Liste: Sonst zaehlte
* die Bremse ihre eigenen Meldungen mit und verlaengerte sich
* selbst -- man kaeme nie wieder heraus. */
function versucheLetzteStunde(personId) {
@@ -572,7 +572,30 @@ kampagneRouter.post("/workspace/api/kampagne/einlesen", async (req, res) => {
const min = Math.max(1, Math.ceil((frei.getTime() - Date.now()) / 60000));
/* Hier NICHT über `schiefgegangen`: Sonst zählte die Bremse
ihre eigene Meldung mit und verlängerte sich selbst. */
protokolliere("kampagne_bremse", {
/* HAUPTWORT PLUS MITTELWORT, NIE ZWEI HAUPTWOERTER
(berichtigt 08.10.2026).
Die Protokollseite baut ihren Anzeigetext aus dem
Schluessel: `aktionLesbar()` in personen.js macht aus
Unterstrichen Leerzeichen und schreibt nur den ERSTEN
Buchstaben gross. Aus `kampagne_bremse` wurde damit
„Kampagne bremse" -- Maschinensprache mit einem grossen
Anfangsbuchstaben, und genau davor warnt
pruef-meldungen.
Gemessen an den anderen fuenf Woertern dieses Hauses:
„Kampagne eingelesen", „Kampagne gescheitert", „Event
geteilt", „Event zurueckgezogen" (die Umlautliste macht
daraus „zurückgezogen") lesen sich als Satz. Nur dieses
eine nicht.
UMBENENNEN WAR HIER UMSONST ZU HABEN: Gemessen am
08.10.2026 steht in der Live-Datenbank KEINE einzige
Zeile mit dem alten Wort -- die Bremse hat noch nie
gegriffen. Es gibt also keinen Altbestand, der danach
krumm angezeigt wuerde. Bei einem Wort mit Zeilen in der
Tabelle waere die Rechnung eine andere. */
protokolliere("kampagne_gebremst", {
personId: person.id, rolle: person.rolle, ip,
detail: `${anzahl} Versuche in einer Stunde`,
});
@@ -871,7 +894,8 @@ kampagneRouter.post("/workspace/api/kampagne/bild",
if (anzahl >= KAMPAGNEN_JE_STUNDE) {
const frei = new Date(Date.parse(aeltester) + 3600_000);
const min = Math.max(1, Math.ceil((frei.getTime() - Date.now()) / 60000));
protokolliere("kampagne_bremse", {
/* Dasselbe Wort wie oben -- Begruendung dort. */
protokolliere("kampagne_gebremst", {
personId: person.id, rolle: person.rolle, ip,
detail: kurz(`bild: ${anzahl} in der letzten Stunde`),
});