feat(sources): externer Reputations-Layer (IFCN + EUvsDisinfo)
Externe Datenquellen (kostenlos, Open Data) ergaenzen die LLM-geschaetzte Reliability-Achse mit objektiven Signalen: - IFCN-Signatories (raw.githubusercontent.com/IFCN/verified-signatories): Plain-Text-Liste anerkannter Faktencheck-Organisationen. - EUvsDisinfo (Zenodo CSV): Pro-Kreml-Desinformations-Datenbank. Schema-Erweiterung: - ifcn_signatory, eu_disinfo_listed, eu_disinfo_case_count, eu_disinfo_last_seen, external_data_synced_at. Service src/services/external_reputation.py: - sync_ifcn_signatories(), sync_eu_disinfo(), apply_reputation_overrides(), sync_all() mit Domain-Normalisierung (lowercase, ohne www., ohne Schema). Reliability-Override-Regeln (laufen nach Approve und manuellem Sync): - ifcn_signatory=1 -> reliability=sehr_hoch - eu_disinfo_case_count >= 5 -> reliability=sehr_niedrig - eu_disinfo_case_count >= 1 -> Reliability eine Stufe runter (max niedrig) API: POST /api/sources/external-reputation/sync (Admin, BackgroundTask). Filter: ?ifcn_signatory=true, ?eu_disinfo_listed=true. UI: - Filter-Dropdown "Externe Reputation" im Quellen-Modal. - Badges: gruenes "IFCN" und rotes "EU-Desinfo (n)". - Tooltip macht Reliability-Quelle transparent: "(IFCN-Faktenchecker)", "(EU-Desinfo, n Faelle)" oder "(LLM-Schaetzung)". - "Externe Daten syncen"-Button im Review-Toolbar (Admin-only). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -1193,7 +1193,20 @@ const UI = {
|
||||
}
|
||||
const rel = feed.reliability;
|
||||
if (rel && rel !== 'na') {
|
||||
parts.push(`<span class="source-reliability-dot rel-${this.escape(rel)}" title="Glaubwürdigkeit: ${this.escape(this._reliabilityLabels[rel] || rel)}" aria-label="Glaubwürdigkeit: ${this.escape(this._reliabilityLabels[rel] || rel)}"></span>`);
|
||||
const relLabel = this._reliabilityLabels[rel] || rel;
|
||||
const relSource = feed.ifcn_signatory ? '(IFCN-Faktenchecker)'
|
||||
: (feed.eu_disinfo_listed ? `(EU-Desinfo, ${feed.eu_disinfo_case_count || 0} Fälle)`
|
||||
: '(LLM-Schätzung)');
|
||||
const relTitle = `Glaubwürdigkeit: ${relLabel} ${relSource}`;
|
||||
parts.push(`<span class="source-reliability-dot rel-${this.escape(rel)}" title="${this.escape(relTitle)}" aria-label="${this.escape(relTitle)}"></span>`);
|
||||
}
|
||||
if (feed.ifcn_signatory) {
|
||||
parts.push(`<span class="source-ifcn-badge" title="IFCN-zertifizierter Faktenchecker" aria-label="IFCN-Faktenchecker">✓ IFCN</span>`);
|
||||
}
|
||||
if (feed.eu_disinfo_listed) {
|
||||
const cnt = feed.eu_disinfo_case_count || 0;
|
||||
const title = `EUvsDisinfo: ${cnt} dokumentierte Desinformations-Fälle`;
|
||||
parts.push(`<span class="source-eu-disinfo-badge" title="${this.escape(title)}" aria-label="${this.escape(title)}">⚠ EU-Desinfo (${cnt})</span>`);
|
||||
}
|
||||
if (feed.state_affiliated) {
|
||||
parts.push(`<span class="source-state-badge" title="Staatsnah/-kontrolliert" aria-label="Staatsnah">⚑</span>`);
|
||||
@@ -1285,7 +1298,15 @@ const UI = {
|
||||
lines.push('Politisch: ' + (pl ? pl.full : firstFeed.political_orientation));
|
||||
}
|
||||
if (firstFeed.reliability && firstFeed.reliability !== 'na') {
|
||||
lines.push('Glaubwürdigkeit: ' + (this._reliabilityLabels[firstFeed.reliability] || firstFeed.reliability));
|
||||
const relLabel = this._reliabilityLabels[firstFeed.reliability] || firstFeed.reliability;
|
||||
const relSrc = firstFeed.ifcn_signatory ? ' (IFCN-Faktenchecker)'
|
||||
: (firstFeed.eu_disinfo_listed ? ` (EU-Desinfo, ${firstFeed.eu_disinfo_case_count || 0} Fälle)`
|
||||
: ' (LLM-Schätzung)');
|
||||
lines.push('Glaubwürdigkeit: ' + relLabel + relSrc);
|
||||
}
|
||||
if (firstFeed.ifcn_signatory) lines.push('IFCN-Faktenchecker: ja');
|
||||
if (firstFeed.eu_disinfo_listed) {
|
||||
lines.push(`EUvsDisinfo: ${firstFeed.eu_disinfo_case_count || 0} Fälle` + (firstFeed.eu_disinfo_last_seen ? ` (zuletzt ${firstFeed.eu_disinfo_last_seen})` : ''));
|
||||
}
|
||||
if (firstFeed.state_affiliated) lines.push('Staatsnah: ja');
|
||||
if (Array.isArray(firstFeed.alignments) && firstFeed.alignments.length > 0) {
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren