companies: Aufrufe als Veröffentlichungsnachweis im Altbestand werten

Das Altsystem hat 2009–2018 rund 126 000 Meldungen auf `rejected` gesetzt,
83 000 davon mit Aufrufen — die waren online und sind pauschal offline
genommen worden. Wer nur `status = published` zählt, stellt pr4you, Blödorn
und Crossover Point mit Hunderten Meldungen neben yahoo@yahoo.com.

Eine Firma gilt jetzt als veröffentlicht, wenn sie eine Meldung mit
`published` ODER `hits > 0` hat. Nie veröffentlicht: 23 → 7 Adressen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-09-11 10:20:57 +00:00
parent 8a1e79aebb
commit 77318dcb79
3 changed files with 93 additions and 3 deletions

View file

@ -40,7 +40,8 @@ class CompanyAddressClassifier
* `pr@deutsche-stadtauskunft.ag` trägt 133 Namen und hat trotzdem 134
* Firmen mit veröffentlichten Meldungen. Das trennscharfe Signal ist die
* Veröffentlichung: Hinter einer Platzhalter- oder Wegwerfadresse ist nie
* etwas erschienen, hinter einer Agentur laufend.
* etwas erschienen, hinter einer Agentur laufend — auch wenn das Jahre her
* ist und die Meldungen heute offline sind (siehe `classifyAll()`).
*/
private const JunkPublishedRatio = 0.05;
@ -84,14 +85,24 @@ class CompanyAddressClassifier
*/
public function classifyAll(int $collectionThreshold): array
{
// Einmal alle Firmen mit mindestens einer veröffentlichten Meldung,
// Einmal alle Firmen, die JEMALS eine Meldung veröffentlicht haben,
// statt je Firma eine korrelierte Unterabfrage: Das läuft über den
// gesamten Bestand, und das ist eine Abfrage gegen hunderttausend.
//
// „Jemals" heißt: heute `published` ODER Aufrufe gezählt. Der Status
// allein trügt beim Altbestand — das Altsystem hat 2009–2018 rund
// 126 000 Meldungen auf `rejected` gesetzt, 83 000 davon mit Aufrufen.
// Eine abgelehnte Meldung wird nie aufgerufen; die waren online und
// sind später pauschal offline genommen worden. Ohne die Aufrufe
// stünden pr4you, Blödorn oder Crossover Point mit Hunderten Meldungen
// aus 2006–2011 als „nie veröffentlicht" neben `yahoo@yahoo.com`.
$withPublication = array_flip(
PressRelease::query()
->withoutGlobalScope(PortalScope::class)
->where('status', 'published')
->whereNotNull('company_id')
->where(function ($query): void {
$query->where('status', 'published')->orWhere('hits', '>', 0);
})
->distinct()
->pluck('company_id')
->all()

View file

@ -649,3 +649,55 @@ dieselbe Logik. 14 427 Adressen (75 %) sind zusammenführbar; hinter ihnen
stehen die 17 945 Firmen aus 1a.1. Der scharfe Lauf am selben Tag schrieb
dieselben 19 167 Zeilen nach `company_email_groups` — die Tabelle ist auf Live
gefüllt, 1a.3 kann darauf filtern.
### Korrektur: „veröffentlicht" heißt „jemals veröffentlicht"
Die 23 Nie-veröffentlicht-Adressen von Live hatten **alle** exakt null Firmen
mit Meldung — darunter pr4you (65 Firmen), Blödorn PR (35), Crossover Point
(31), GCE (48). Sieben Agenturen, die nie etwas veröffentlicht haben sollen,
sind kein Befund, sondern ein Messfehler.
Nachgeprüft in der Dev-DB (Kopie des Bestands):
| Adresse | Meldungen | Status | davon mit Aufrufen | Zeitraum |
|---|---:|---|---:|---|
| presse@pr4you.de | 199 | rejected | 199 | 2008–2011 |
| h.ballwanz@pr4you.de | 473 | rejected | 473 | 2006–2011 |
| info@bloedorn-pr.de | 194 | rejected | 193 | 2006–2011 |
| info@gce-agency.com | 328 | rejected | 328 | 2009–2011 |
| press@crossoverpoint.de | 149 | rejected | 148 | 2009–2011 |
| yahoo@yahoo.com | 5 176 | rejected | **0** | 2015–2019 |
| hcgdietinsight4@gmail.com | 73 | rejected | **0** | 2011–2012 |
**Ursache:** Das Altsystem hat 2009–2018 rund 126 000 Meldungen auf `rejected`
gesetzt (gegenüber 31 000 `published`); 83 000 davon tragen Aufrufe. Eine
abgelehnte Meldung wird nie aufgerufen — die waren online und sind später
pauschal offline genommen worden. `rejected` im Altbestand bedeutet
„offline", nicht „redaktionell abgelehnt". Der Importer hat den Status
korrekt übernommen; nur die Deutung war falsch.
**Fix** in `CompanyAddressClassifier::classifyAll()`: eine Firma gilt als
veröffentlicht, wenn sie eine Meldung mit `status = published` **oder**
`hits > 0` hat. Zwei Tests halten beide Seiten fest (Agentur mit alten
Offline-Meldungen → Sammeladresse; abgelehnte Meldungen ohne einen Aufruf →
nie veröffentlicht). Laufzeit unverändert (2,6 s).
**Wirkung** (Dev-DB, Stand etwas älter als Live): 23 → **7** nie
veröffentlicht, 16 wandern zur Sammeladresse. Die übrigen Klassen sind
unberührt — das Veröffentlichungssignal entscheidet nur zwischen „nie
veröffentlicht" und Sammeladresse. Die Sieben, die bleiben:
`yahoo@yahoo.com`, `pr@getarticlesdone.com`, `hcgdietinsight4@gmail.com`,
`zhangmh@itecheasy.com`, `andrewkarvert112@gmail.com`,
`kontakt@pr-vertretung.de` (159 Firmen, 1 mit Aufruf), `bukmaier@prdienst.de`
(28 Firmen, keine einzige Meldung). Letztere ist die einzige mit
Agentur-Namen — sie kann bei Bedarf per `manual_classification` umgestellt
werden.
**Nebenbefund für spätere Auswertungen:** Jede Statistik, die
`status = rejected` über den Altbestand als Ablehnung liest, misst falsch.
Die Ablehnungsquote aus 0.2 ist nicht betroffen (sie läuft über die
Statuslogs des neuen Systems).
**Deploy:** Nach dem Deploy `companies:classify-addresses` erneut laufen
lassen — die 16 Adressen werden dabei umklassifiziert, Handentscheidungen
bleiben.

View file

@ -152,6 +152,33 @@ test('a large but working agency is not mistaken for a placeholder', function ()
->and($classes['nie_veroeffentlicht']['adressen'])->toBe(0);
});
test('an agency whose releases went offline years ago still counts as published', function () {
/** @var TestCase $this */
// Der Altbestand: Das Altsystem hat 2009–2018 rund 126 000 Meldungen auf
// `rejected` gesetzt, 83 000 davon mit gezählten Aufrufen. Eine abgelehnte
// Meldung wird nie aufgerufen — die waren online. pr4you mit 65 Firmen
// und 199 solcher Meldungen aus 2008–2011 ist eine Agentur, kein Müll.
foreach (range(1, 30) as $i) {
$company = mergeCompany('presse@pr4you.de', fake()->unique()->company().' '.fake()->unique()->word());
PressRelease::factory()->create(['company_id' => $company->id, 'status' => 'rejected', 'hits' => 120]);
}
$classes = mergeClasses();
expect($classes['sammeladresse']['adressen'])->toBe(1)
->and($classes['nie_veroeffentlicht']['adressen'])->toBe(0);
});
test('a rejected release without a single view is no publication', function () {
/** @var TestCase $this */
foreach (range(1, 30) as $i) {
$company = mergeCompany('yahoo@yahoo.com', fake()->unique()->company().' '.fake()->unique()->word());
PressRelease::factory()->create(['company_id' => $company->id, 'status' => 'rejected', 'hits' => 0]);
}
expect(mergeClasses()['nie_veroeffentlicht']['adressen'])->toBe(1);
});
test('a single company on its own address is not part of the report', function () {
/** @var TestCase $this */
mergeCompany('allein@muster.de', 'Allein GmbH');