refactor(klassifikation): Klassifikation aus Monitor entfernt — Pflege jetzt in der Verwaltung

Endpoints unter /api/sources/classification/* weg, Service-Module (source_classifier, external_reputation) gelöscht. Quellen-Modal verliert Tab Klassifikations-Review, Klassifikations-Section in der Edit-Form, alle Bulk-Buttons (Sync, Klassifikation starten, Bulk-Approve). API-Methoden in api.js entfernt, alignment-Helper raus, saveSource entschlackt.

Read-Only bleibt: Filter-Dropdowns über der Quellenliste (Politik, Medientyp, Reliability, Externe Reputation, Alignment) und Inline-Badges (_renderClassificationBadges + Label-Maps in components.js). Kunde sieht nur freigegebene Werte.

GET /api/sources liefert weiter Klassifikations-Felder + alignments für die Anzeige; SourceCreate/SourceUpdate akzeptieren keine Klassifikations-Felder mehr.

Bulk-Klassifikations-Skripte entfernt — Pflege läuft über Verwaltungs-UI.
Dieser Commit ist enthalten in:
Claude Code
2026-05-09 22:01:20 +00:00
Ursprung 5f053a3eca
Commit b90e47ff3f
10 geänderte Dateien mit 6150 neuen und 7831 gelöschten Zeilen

Datei anzeigen

@@ -2702,12 +2702,6 @@ async handleRefresh() {
async openSourceManagement() {
openModal('modal-sources');
await this.loadSources();
// Admin sieht den Review-Tab
const reviewTab = document.getElementById('sources-tab-review');
if (reviewTab && this.user && this.user.role === 'org_admin') {
reviewTab.style.display = '';
this._refreshReviewBadge().catch(() => {});
}
},
async loadSources() {
@@ -2728,122 +2722,6 @@ async handleRefresh() {
}
},
async _refreshReviewBadge() {
try {
const stats = await API.getClassificationStats();
const badge = document.getElementById('sources-review-count');
if (badge) badge.textContent = String(stats.pending_review || 0);
} catch (_) { /* still ok */ }
},
switchSourcesTab(tab) {
const listView = document.getElementById('sources-list-view');
const reviewView = document.getElementById('sources-review-view');
const tabList = document.getElementById('sources-tab-list');
const tabReview = document.getElementById('sources-tab-review');
if (!listView || !reviewView) return;
if (tab === 'review') {
listView.style.display = 'none';
reviewView.style.display = '';
if (tabList) { tabList.classList.remove('active'); tabList.setAttribute('aria-selected', 'false'); }
if (tabReview) { tabReview.classList.add('active'); tabReview.setAttribute('aria-selected', 'true'); }
this.loadClassificationQueue();
} else {
listView.style.display = '';
reviewView.style.display = 'none';
if (tabList) { tabList.classList.add('active'); tabList.setAttribute('aria-selected', 'true'); }
if (tabReview) { tabReview.classList.remove('active'); tabReview.setAttribute('aria-selected', 'false'); }
}
},
async loadClassificationQueue() {
const list = document.getElementById('sources-review-list');
if (!list) return;
const minConf = parseFloat(document.getElementById('review-min-confidence')?.value || '0');
list.innerHTML = '<div class="empty-state-text" style="padding:var(--sp-3xl);text-align:center;">Lade...</div>';
try {
const items = await API.getClassificationQueue(200, minConf);
this._reviewItems = items;
const countEl = document.getElementById('review-pending-count');
if (countEl) countEl.textContent = String(items.length);
if (items.length === 0) {
list.innerHTML = '<div class="empty-state-text" style="padding:var(--sp-3xl);text-align:center;">Keine ausstehenden Vorschlaege.</div>';
return;
}
list.innerHTML = items.map(item => UI.renderClassificationQueueItem(item)).join('');
} catch (err) {
list.innerHTML = `<div class="empty-state-text" style="padding:var(--sp-3xl);text-align:center;color:var(--danger);">Fehler: ${err.message}</div>`;
}
},
async approveClassification(id) {
try {
await API.approveClassification(id);
UI.showToast('Klassifikation uebernommen.', 'success');
await this.loadClassificationQueue();
this._refreshReviewBadge();
} catch (err) {
UI.showToast('Approve fehlgeschlagen: ' + err.message, 'error');
}
},
async rejectClassification(id) {
try {
await API.rejectClassification(id);
UI.showToast('Vorschlag verworfen.', 'success');
await this.loadClassificationQueue();
this._refreshReviewBadge();
} catch (err) {
UI.showToast('Reject fehlgeschlagen: ' + err.message, 'error');
}
},
async reclassifySource(id) {
const btn = document.querySelector(`[data-reclassify-id="${id}"]`);
if (btn) { btn.disabled = true; btn.textContent = '...'; }
try {
await API.reclassifySource(id);
UI.showToast('Neu klassifiziert.', 'success');
await this.loadClassificationQueue();
} catch (err) {
UI.showToast('Reclassify fehlgeschlagen: ' + err.message, 'error');
} finally {
if (btn) { btn.disabled = false; btn.textContent = 'Neu klassifizieren'; }
}
},
async triggerBulkClassify() {
if (!confirm('Bulk-Klassifikation aller noch nicht klassifizierten Quellen starten? Lauft im Hintergrund (~3-5 Sek pro Quelle, ~0.02 USD pro Quelle).')) return;
try {
const r = await API.triggerBulkClassify(500, true);
UI.showToast(`Bulk-Klassifikation gestartet (limit=${r.limit}). Nachschauen mit Reload.`, 'info');
} catch (err) {
UI.showToast('Start fehlgeschlagen: ' + err.message, 'error');
}
},
async bulkApproveHighConfidence() {
if (!confirm('Alle Vorschlaege mit Konfidenz >= 0.85 genehmigen?')) return;
try {
const r = await API.bulkApproveClassifications(0.85);
UI.showToast(`${r.approved_count} Vorschlaege uebernommen.`, 'success');
await this.loadClassificationQueue();
this._refreshReviewBadge();
} catch (err) {
UI.showToast('Bulk-Approve fehlgeschlagen: ' + err.message, 'error');
}
},
async triggerExternalReputationSync() {
if (!confirm('IFCN- und EUvsDisinfo-Datenbanken jetzt syncen? Lauft im Hintergrund (~30 Sek).')) return;
try {
await API.triggerExternalReputationSync();
UI.showToast('Externer Sync gestartet. Quellenliste in 30 Sek neu laden.', 'info');
} catch (err) {
UI.showToast('Sync fehlgeschlagen: ' + err.message, 'error');
}
},
renderSourceStats(stats) {
const bar = document.getElementById('sources-stats-bar');
if (!bar) return;
@@ -3200,13 +3078,6 @@ async handleRefresh() {
document.getElementById('src-discover-btn').disabled = false;
document.getElementById('src-discover-btn').textContent = 'Erkennen';
document.getElementById('src-type-select').value = 'rss_feed';
// Klassifikations-Felder auf Default zurücksetzen
const polEl = document.getElementById('src-political'); if (polEl) polEl.value = 'na';
const mtEl = document.getElementById('src-mediatype'); if (mtEl) mtEl.value = 'sonstige';
const relEl = document.getElementById('src-reliability'); if (relEl) relEl.value = 'na';
const ccEl = document.getElementById('src-country'); if (ccEl) ccEl.value = '';
const saEl = document.getElementById('src-state-affiliated'); if (saEl) saEl.checked = false;
this._setAlignmentChips([]);
// Save-Button Text zurücksetzen
const saveBtn = document.querySelector('#src-discovery-result .sources-discovery-actions .btn-primary');
if (saveBtn) saveBtn.textContent = 'Speichern';
@@ -3388,19 +3259,6 @@ async handleRefresh() {
rss_url: source.url,
};
// Klassifikations-Felder setzen
const polEl = document.getElementById('src-political');
if (polEl) polEl.value = source.political_orientation || 'na';
const mtEl = document.getElementById('src-mediatype');
if (mtEl) mtEl.value = source.media_type || 'sonstige';
const relEl = document.getElementById('src-reliability');
if (relEl) relEl.value = source.reliability || 'na';
const ccEl = document.getElementById('src-country');
if (ccEl) ccEl.value = source.country_code || '';
const saEl = document.getElementById('src-state-affiliated');
if (saEl) saEl.checked = !!source.state_affiliated;
this._setAlignmentChips(source.alignments || []);
// Submit-Button-Text ändern
const saveBtn = document.querySelector('#src-discovery-result .sources-discovery-actions .btn-primary');
if (saveBtn) saveBtn.textContent = 'Quelle speichern';
@@ -3409,27 +3267,6 @@ async handleRefresh() {
if (form) form.scrollIntoView({ behavior: 'smooth', block: 'start' });
},
_setAlignmentChips(active) {
const chips = document.querySelectorAll('#src-alignments-chips .alignment-chip');
const set = new Set((active || []).map(a => (a || '').toLowerCase()));
chips.forEach(chip => {
if (set.has(chip.dataset.alignment)) chip.classList.add('active');
else chip.classList.remove('active');
});
},
_getAlignmentChips() {
return Array.from(document.querySelectorAll('#src-alignments-chips .alignment-chip.active'))
.map(chip => chip.dataset.alignment);
},
handleAlignmentChipClick(e) {
const chip = e.target.closest('.alignment-chip');
if (!chip) return;
e.preventDefault();
chip.classList.toggle('active');
},
async saveSource() {
const name = document.getElementById('src-name').value.trim();
if (!name) {
@@ -3445,12 +3282,6 @@ async handleRefresh() {
url: discovered.rss_url || (discovered.source_type === 'telegram_channel' ? (document.getElementById('src-domain').value || null) : null),
domain: document.getElementById('src-domain').value.trim() || discovered.domain || null,
notes: document.getElementById('src-notes').value.trim() || null,
political_orientation: document.getElementById('src-political')?.value || 'na',
media_type: document.getElementById('src-mediatype')?.value || 'sonstige',
reliability: document.getElementById('src-reliability')?.value || 'na',
country_code: (document.getElementById('src-country')?.value || '').trim().toUpperCase() || null,
state_affiliated: !!document.getElementById('src-state-affiliated')?.checked,
alignments: this._getAlignmentChips(),
};
if (!data.domain && discovered.domain) {