feat(sources): strukturierte Klassifikation (Politik/Medientyp/Reliability/Alignments)
- Neue sources-Spalten: political_orientation (7+2 Stufen), media_type (20), reliability (5+1), state_affiliated, country_code, classification_source, classified_at sowie proposed_*-Spalten fuer LLM-Vorschlaege. - Neue source_alignments-Tabelle fuer Mehrfach-Tagging geopolitischer Naehe (prorussisch, proiranisch, prowestlich, ...). - API-Filter: ?political_orientation, ?media_type, ?reliability, ?state_affiliated, ?alignment. - create/update_source nehmen alignments[] entgegen und setzen classification_source automatisch auf 'manual' bei Klassifikations-Edits. Backwards-kompatibel: bestehendes bias/language/category bleibt unveraendert, Default fuer Bestandsquellen ist classification_source = 'legacy'. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -158,7 +158,31 @@ CREATE TABLE IF NOT EXISTS sources (
|
||||
article_count INTEGER DEFAULT 0,
|
||||
last_seen_at TIMESTAMP,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
tenant_id INTEGER REFERENCES organizations(id)
|
||||
tenant_id INTEGER REFERENCES organizations(id),
|
||||
language TEXT,
|
||||
bias TEXT,
|
||||
political_orientation TEXT DEFAULT 'na',
|
||||
media_type TEXT DEFAULT 'sonstige',
|
||||
reliability TEXT DEFAULT 'na',
|
||||
state_affiliated INTEGER DEFAULT 0,
|
||||
country_code TEXT,
|
||||
classification_source TEXT DEFAULT 'legacy',
|
||||
classified_at TIMESTAMP,
|
||||
proposed_political_orientation TEXT,
|
||||
proposed_media_type TEXT,
|
||||
proposed_reliability TEXT,
|
||||
proposed_state_affiliated INTEGER,
|
||||
proposed_country_code TEXT,
|
||||
proposed_alignments_json TEXT,
|
||||
proposed_confidence REAL,
|
||||
proposed_reasoning TEXT,
|
||||
proposed_at TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS source_alignments (
|
||||
source_id INTEGER NOT NULL REFERENCES sources(id) ON DELETE CASCADE,
|
||||
alignment TEXT NOT NULL,
|
||||
PRIMARY KEY (source_id, alignment)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS notifications (
|
||||
@@ -611,6 +635,57 @@ async def init_db():
|
||||
await db.execute("ALTER TABLE sources ADD COLUMN tenant_id INTEGER REFERENCES organizations(id)")
|
||||
await db.commit()
|
||||
|
||||
# Migration: language + bias (Freitext, schon laenger im Einsatz, Schema-Lueck schliessen)
|
||||
if "language" not in src_columns:
|
||||
await db.execute("ALTER TABLE sources ADD COLUMN language TEXT")
|
||||
await db.commit()
|
||||
if "bias" not in src_columns:
|
||||
await db.execute("ALTER TABLE sources ADD COLUMN bias TEXT")
|
||||
await db.commit()
|
||||
|
||||
# Migration: strukturierte Klassifikations-Spalten fuer sources
|
||||
for col, ddl in [
|
||||
("political_orientation", "ALTER TABLE sources ADD COLUMN political_orientation TEXT DEFAULT 'na'"),
|
||||
("media_type", "ALTER TABLE sources ADD COLUMN media_type TEXT DEFAULT 'sonstige'"),
|
||||
("reliability", "ALTER TABLE sources ADD COLUMN reliability TEXT DEFAULT 'na'"),
|
||||
("state_affiliated", "ALTER TABLE sources ADD COLUMN state_affiliated INTEGER DEFAULT 0"),
|
||||
("country_code", "ALTER TABLE sources ADD COLUMN country_code TEXT"),
|
||||
("classification_source", "ALTER TABLE sources ADD COLUMN classification_source TEXT DEFAULT 'legacy'"),
|
||||
("classified_at", "ALTER TABLE sources ADD COLUMN classified_at TIMESTAMP"),
|
||||
("proposed_political_orientation", "ALTER TABLE sources ADD COLUMN proposed_political_orientation TEXT"),
|
||||
("proposed_media_type", "ALTER TABLE sources ADD COLUMN proposed_media_type TEXT"),
|
||||
("proposed_reliability", "ALTER TABLE sources ADD COLUMN proposed_reliability TEXT"),
|
||||
("proposed_state_affiliated", "ALTER TABLE sources ADD COLUMN proposed_state_affiliated INTEGER"),
|
||||
("proposed_country_code", "ALTER TABLE sources ADD COLUMN proposed_country_code TEXT"),
|
||||
("proposed_alignments_json", "ALTER TABLE sources ADD COLUMN proposed_alignments_json TEXT"),
|
||||
("proposed_confidence", "ALTER TABLE sources ADD COLUMN proposed_confidence REAL"),
|
||||
("proposed_reasoning", "ALTER TABLE sources ADD COLUMN proposed_reasoning TEXT"),
|
||||
("proposed_at", "ALTER TABLE sources ADD COLUMN proposed_at TIMESTAMP"),
|
||||
]:
|
||||
if col not in src_columns:
|
||||
await db.execute(ddl)
|
||||
await db.commit()
|
||||
if any(c not in src_columns for c in ("political_orientation", "media_type", "reliability")):
|
||||
logger.info("Migration: Klassifikations-Spalten zu sources hinzugefuegt")
|
||||
|
||||
# Migration: source_alignments-Tabelle (Mehrfach-Tags fuer geopolitische Naehe)
|
||||
cursor = await db.execute(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name='source_alignments'"
|
||||
)
|
||||
if not await cursor.fetchone():
|
||||
await db.executescript(
|
||||
"""
|
||||
CREATE TABLE source_alignments (
|
||||
source_id INTEGER NOT NULL REFERENCES sources(id) ON DELETE CASCADE,
|
||||
alignment TEXT NOT NULL,
|
||||
PRIMARY KEY (source_id, alignment)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_source_alignments_alignment ON source_alignments(alignment);
|
||||
"""
|
||||
)
|
||||
await db.commit()
|
||||
logger.info("Migration: source_alignments-Tabelle erstellt")
|
||||
|
||||
# Migration: tenant_id fuer notifications
|
||||
cursor = await db.execute("PRAGMA table_info(notifications)")
|
||||
notif_columns = [row[1] for row in await cursor.fetchall()]
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren