-- Migration p2-005: Akteurslexikon und Amtsinhaber -- -- psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f migrations/p2-005-akteure.sql -- -- Aendert an raw_items und raw_item_versionen nichts und loescht keine Zeile. -- -- Die Ebene 'akteure' ist seit p2-004 erlaubt; hier entsteht nur, worauf das -- Modul sich stuetzt. Das Lexikon ist Stammdatenbestand, keine Ableitung: -- es ueberlebt einen coder_version-Wechsel und wird beim Neuaufbau der -- Phase-2-Ausgabe nicht mit weggeworfen. begin; -- Lexikon. Struktur wie in der Spezifikation, Abschnitt 4. -- -- id traegt ein Typkuerzel: per: organisation: par: sta: amt:. Das ist -- Absicht - eine ID soll ohne Nachschlagen lesbar sein, und die Nutzlast -- der Codings wird von Menschen gelesen. create table if not exists akteure ( id text primary key, typ text not null check (typ in ('person', 'organisation', 'partei', 'staat', 'amt')), name text not null, aliase text[] not null default '{}', land text, -- ISO-3166-1 alpha-3 rolle text, -- GOV LEG OPP JUD MIL MED BUS NGO INT gueltig_von date, gueltig_bis date, wikidata_qid text, quelle text not null, -- 'wikidata:@' oder 'hand' notiz text, check (gueltig_von is null or gueltig_bis is null or gueltig_bis >= gueltig_von) ); -- Der Alias-Match des Moduls ist ein Nachschlagen ueber dieses Feld. create index if not exists akteure_aliase_idx on akteure using gin (aliase); create index if not exists akteure_typ_idx on akteure (typ); -- Ein QID darf nicht zweimal auftauchen, sonst zerfaellt eine Person in -- zwei IDs und die Zeitreihen zerfallen mit ihr. Teilindex, weil handisch -- ergaenzte Eintraege kein QID haben. create unique index if not exists akteure_qid_idx on akteure (wikidata_qid) where wikidata_qid is not null; -- Amtsinhaber: welche Person hatte wann welches Amt. -- -- Ohne diese Tabelle bezeichnet "der Bundeskanzler" eine Person statt eines -- Amtes zu einem Zeitpunkt. Der Korpus wird Amtswechsel ueberspannen, und -- rueckwirkend falsch aufgeloeste Aemter waeren nicht wiederzuerkennen. -- -- bis is null heisst amtierend, nicht unbekannt. Wo das Ende unbekannt ist, -- gehoert eine Notiz dazu. create table if not exists amtsinhaber ( amt_id text not null references akteure (id) on delete cascade, person_id text not null references akteure (id) on delete cascade, von date not null, bis date, quelle text not null, notiz text, primary key (amt_id, person_id, von), check (bis is null or bis >= von) ); create index if not exists amtsinhaber_amt_idx on amtsinhaber (amt_id, von); create index if not exists amtsinhaber_person_idx on amtsinhaber (person_id); -- Bewusst *keine* Ausschlussbeschraenkung gegen ueberlappende Amtszeiten. -- -- Eine solche waere hier schlicht falsch: Doppelspitzen sind im deutschen -- Parteiensystem der Normalfall, nicht die Ausnahme. Gleichzeitig ist eine -- Ueberlappung beim Kanzleramt ein Datenfehler. Die Unterscheidung haengt -- am Amt, nicht an der Zeile, und laesst sich in einer Beschraenkung nicht -- ausdruecken. Also wird sie sichtbar gemacht statt verboten - der Lader -- meldet, was hier auftaucht. create or replace view amtsinhaber_ueberschneidungen as select a.amt_id, k.name as amt, a.person_id as person_a, b.person_id as person_b, greatest(a.von, b.von) as ab, -- null heisst "beide noch offen". 'infinity'::date waere praeziser, -- laesst sich aber von keinem Client als Datum lesen. case when a.bis is null or b.bis is null then null else least(a.bis, b.bis) end as bis from amtsinhaber a join amtsinhaber b on a.amt_id = b.amt_id and a.person_id < b.person_id -- Halboffen: der Tag der Amtsuebergabe gehoert dem Nachfolger. -- Wikidata traegt ihn bei beiden ein, und mit '[]' waere jeder -- Amtswechsel eine Ueberschneidung. and daterange(a.von, a.bis, '[)') && daterange(b.von, b.bis, '[)') join akteure k on k.id = a.amt_id; -- Betrieb, Spezifikation Abschnitt 4: was die NER gefunden, das Lexikon -- aber nicht aufgeloest hat. Haeufige Formen wandern von Hand ins Lexikon. -- Die Sicht liest nur die jeweils juengste coder_version, sonst zaehlt sie -- alte Laeufe mit. create or replace view akteure_unaufgeloest as select e ->> 'form' as form, e ->> 'typ' as typ, count(*) as nennungen, count(distinct c.raw_item_id) as items, min(r.pubdate) as zuerst, max(r.pubdate) as zuletzt from codings c join raw_items r on r.id = c.raw_item_id cross join lateral jsonb_array_elements(c.nutzlast -> 'erkannt') e where c.ebene = 'akteure' and c.coder_version = (select max(coder_version) from codings where ebene = 'akteure') and e ->> 'id' is null group by 1, 2; insert into schema_migrationen (name) values ('p2-005-akteure') on conflict (name) do nothing; commit;