diff --git a/server/kampagne-bild-lesen.mjs b/server/kampagne-bild-lesen.mjs index 408d1f9a..bd14512a 100644 --- a/server/kampagne-bild-lesen.mjs +++ b/server/kampagne-bild-lesen.mjs @@ -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, }, }; diff --git a/server/pruef-kampagne-bild.mjs b/server/pruef-kampagne-bild.mjs index 804d7686..9a6b3a58 100644 --- a/server/pruef-kampagne-bild.mjs +++ b/server/pruef-kampagne-bild.mjs @@ -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"); diff --git a/server/pruef-kampagne.mjs b/server/pruef-kampagne.mjs index b05db74d..46550f41 100644 --- a/server/pruef-kampagne.mjs +++ b/server/pruef-kampagne.mjs @@ -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')" diff --git a/server/workspace-kampagne.js b/server/workspace-kampagne.js index e36578a0..df253fd9 100644 --- a/server/workspace-kampagne.js +++ b/server/workspace-kampagne.js @@ -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`), });