Dublettenprüfung

This commit is contained in:
irrlicht
2026-09-07 14:30:02 +02:00
parent 861caae57c
commit 50d497925f
11 changed files with 1648 additions and 17 deletions
+156
View File
@@ -0,0 +1,156 @@
# Befunde aus dem Dublettenmodul
Gemessen am Bestand vom 4.–7.9.2026: 3407 Items, 12 Quellen, 106 Slots.
## Das Verfahren der Spezifikation findet keine Übernahmen
67 Gruppen, 147 Mitglieder, **null davon über Häuser hinweg**. Alle gefundenen
Gruppen sind Wiederholungen innerhalb eines Feeds.
Das ist kein Schwellenproblem, das sich durch Nachjustieren lösen ließe. Ein
vollständiger Vergleich aller 599.004 hausübergreifenden Paare des stärksten
Tages (6.9., 1162 vergleichbare Items) ergibt:
| Jaccard über Titel+Teaser | Paare über Häuser |
|---|---|
| ≥ 0.85 (Schwelle der Spec) | **0** |
| ≥ 0.7 | 1 |
| ≥ 0.5 | 3 |
| ≥ 0.3 | 8 |
Der höchste erreichte Wert im gesamten Tag liegt bei 0.65. Die Schwelle wird
nie berührt.
Die Hamming-Grenze ist dabei unschuldig: von 224 Paaren mit Jaccard ≥ 0.85
verwirft sie 6. Der Vorfilter über SimHash-Bänder kostet also fast nichts —
er ist gut, er hat nur nichts zu tun.
## Warum: der Titel wird übernommen, der Teaser nicht
Die Übernahmen sind da, sie sehen nur anders aus als angenommen. Beispiele
vom 6.9., alle mit **zeichengleichem Titel** nach Normalisierung:
```
zeit / handelsblatt Wahl in Sachsen-Anhalt: Klüssendorf: Frage nach SPD-Vorsitz …
zeit / handelsblatt Ukraine-Krieg: Selenskyj mit wenig Hoffnung auf baldigen Frieden
heise / tagesschau Deutsche Isar-Aerospace-Rakete erfolgreich ins All gestartet
handelsblatt / tagesschau +++ Iran-Krieg +++: Tote und Verletzte bei neuen Angriffen
```
Die Häuser übernehmen die Agenturüberschrift wörtlich und schreiben den
Teaser selbst. Werden beide in einen Sack geworfen, verdünnt der Teaser genau
das Signal, das trägt: dieselben Paare landen bei Jaccard 0.29 bis 0.38.
Gemessen, wenn **nur der Titel** verglichen wird (6.9., 1144 Items mit
mindestens fünf Titelwörtern):
| Kriterium | Paare | davon über Häuser |
|---|---|---|
| Titel zeichengleich | 61 | **22** |
| Titel-Jaccard ≥ 0.75 | 16 | **9** |
31 hausübergreifende Übernahmen an einem Tag statt null. Die Differenz
zwischen „zeichengleich" und „≥ 0.75" ist fast immer ein vorangestelltes
Ressortkürzel des Hauses: `Raumfahrt: Deutsche Isar-Aerospace-Rakete …` gegen
`Deutsche Isar-Aerospace-Rakete …`, oder `Notfälle:` gegen `Flugverkehr:` vor
derselben dpa-Zeile.
## Vorschlag
Vergleichsbasis auf den **Titel** umstellen, den Teaser nur noch als
Bestätigung heranziehen, wo er auf beiden Seiten vorliegt. Zusätzlich das
führende Ressortpräfix (`Wort:` am Anfang) in der Normalisierung abtrennen —
danach fällt ein Teil der 0.75er-Fälle in die Zeichengleichheit.
Das ist eine Änderung an Abschnitt 3 der Spezifikation, nicht an ihrer Absicht:
„near-exact, kein thematisches Clustering" bleibt. Die Abnahmekriterien wären
neu zu setzen, weil sich mit der Basis auch die Grundgesamtheit ändert.
## Umgesetzt, mit diesem Ergebnis
Der Vorschlag ist gebaut und ist jetzt die Vorgabe (`--basis titel`, dazu das
abgetrennte Ressortkürzel). Auf demselben Bestand:
| | Titel + Teaser | Titel |
|---|---|---|
| Gruppen gesamt | 67 | 135 |
| Mitglieder | 147 | 290 |
| **Gruppen über mehrere Häuser** | **0** | **63** |
| Items in solchen Gruppen | 0 | 143 |
Gruppengrößen: 240 Paare, 33 Dreier, 12 Vierer, 5 Fünfer. Die größte ist
dieselbe Zeile über den Start der Isar-Aerospace-Rakete bei handelsblatt,
heise, tagesschau und zeit.
Auf dem gemessenen Tag (6.9.) erreicht das Verfahren 42 von 45
hausübergreifenden Paaren mit Jaccard ≥ 0.70. Die Schwellen kosten dabei
nichts: an der Jaccard-Schwelle gehen 0 Paare verloren, an der
Hamming-Grenze 0. Die drei fehlenden scheitern am SimHash-Bandvorfilter —
bei sechs bis acht Trigrammen je Titel ist SimHash ein grobes Werkzeug. Ein
Trigramm-Index als zweiter Kandidatenweg würde sie einfangen.
Die Schwelle selbst ist unkritisch: zwischen Jaccard 0.70 und 0.85 liegt auf
diesem Bestand kein einziges Paar. Nach dem Abtrennen des Ressortkürzels sind
die Übernahmen entweder zeichengleich oder deutlich verschieden.
## Nebenbefund: Volltext hilft hier nicht
Auf den ersten Blick läge nahe, den Volltext zu nehmen, wo er da ist. Genau
das verbietet sich: die Abdeckung ist ungleich (faz 311/489, heise 174/196,
aber derstandard, welt, dlf, taz, sz jeweils 0), und ein Paar aus einem Haus
mit und einem ohne Volltext ist der häufigste Fall überhaupt. Eine je Item
verschiedene Vergleichsbasis macht genau die Paare unauffindbar, um die es
geht.
## Nebenbefund: `agentur_vermutet` greift kaum
Zwei Belege im gesamten Bestand, beide `reuters`. Die Agenturkennung steht in
RSS-Titeln und -Teasern praktisch nie; sie taucht erst im Volltext oder in der
Bildunterschrift auf. Das Feld bleibt, liefert aber auf RSS-Basis fast immer
`null`.
## Was beim Aufräumen weggefallen ist
Nach den Experimenten einmal gemessen, welche Regel auf den 3407 Items
überhaupt jemals greift:
| Regel | Treffer |
|---|---|
| Ressortkürzel abtrennen | 1870 |
| Anführungszeichen/Striche vereinheitlichen | 654 |
| `+++ … +++` entfernen | 8 |
| NFKC | 4 |
| `Eilmeldung:` entfernen | **0** |
| Ortsmarke `Berlin (dpa) -` entfernen | **0** |
| HTML-Tags entfernen | **0** (auch im Teaser) |
| Entities auflösen im Titel | **0** |
Entfernt wurden `_EILMELDUNG` und `_ORTSMARKE`. Beide stehen so in Abschnitt 3
der Spezifikation, greifen aber auf RSS-Titeln nie — die Ortsmarke steht im
Volltext einer Agenturmeldung, nicht in der Überschrift. Kommt Volltext als
Quelle hinzu, gehören sie zurück.
Das Entfernen von HTML-Tags bleibt trotz null Treffern: Phase 1 zieht die
`links` aus dem Teaser-HTML, HTML ist im Feed also grundsätzlich zu erwarten.
Der doppelte Entity-Durchlauf (vor *und* nach dem Tag-Entfernen) ist auf einen
reduziert.
Ebenfalls raus: der ungenutzte `anzahl`-Parameter von `baender()`, zwei
ungenutzte Argumente von `stichprobe()`, der `version`-Parameter von
`eintragen()`, ein `__main__`-Block in `version.py`, der eine Version ohne
Moduleinstellungen ausgab und damit nie die echte.
Zusammengelegt: `lade()` hatte am Ende selbst gruppiert, und `main()` rechnete
dieselbe Gruppierung anschließend ein zweites Mal. Beides steckt jetzt in
`lade_und_gruppiere()` — beim Slot-Lauf gehört es ohnehin zusammen, weil sich
erst an den Gruppen zeigt, wieviel geladen werden muss.
Zwei Kommentarblöcke zur Vergleichsbasis widersprachen sich nach der
Umstellung; der ältere behauptete noch „verglichen wird immer Titel + Teaser".
Die Schwellen selbst sind alle belegt. Von 778 Kandidatenpaaren verwirft das
Datumsfenster 223, die Regel „gleiches Haus, anderer Tag" 48 und die
Hamming-Grenze 330; 177 werden bestätigt. Nur die Jaccard-Bestätigung verwirft
nichts mehr — sie bleibt als Präzisionszusage, weil SimHash kollidieren kann.
Der Kettenschnitt trifft genau eine von 135 Komponenten und bleibt aus
demselben Grund.