Fix: Duplikat-Check + Stale-Check in Streaming-Endpoint

- Duplikat-Check in search-fix auf source_id+type umgestellt
- Stale-Check im Streaming-Endpoint überspringt web_sources

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
claude-dev
2026-03-08 19:05:46 +01:00
Ursprung d46eac4733
Commit 29f3e73480

Datei anzeigen

@@ -1,9 +1,9 @@
import os import os
"""Grundquellen-Verwaltung und Kundenquellen-Übersicht.""" """Grundquellen-Verwaltung und Kundenquellen-Übersicht."""
import sys import sys
import logging import logging
# Monitor-Source-Rules verfügbar machen # Monitor-Source-Rules verfügbar machen
sys.path.insert(0, "/home/claude-dev/AegisSight-Monitor/src") sys.path.insert(0, "/home/claude-dev/AegisSight-Monitor/src")
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
@@ -298,7 +298,7 @@ async def add_discovered_sources(
existing_urls.add(feed["url"]) existing_urls.add(feed["url"])
added += 1 added += 1
# Web-Source für die Domain anlegen wenn noch nicht vorhanden # Web-Source für die Domain anlegen wenn noch nicht vorhanden
if feeds and feeds[0].get("domain"): if feeds and feeds[0].get("domain"):
domain = feeds[0]["domain"] domain = feeds[0]["domain"]
cursor = await db.execute( cursor = await db.execute(
@@ -319,7 +319,7 @@ async def add_discovered_sources(
# --- Health-Check & Vorschläge --- # --- Health-Check & Vorschläge ---
@router.get("/health") @router.get("/health")
async def get_health( async def get_health(
@@ -327,7 +327,7 @@ async def get_health(
db: aiosqlite.Connection = Depends(db_dependency), db: aiosqlite.Connection = Depends(db_dependency),
): ):
"""Health-Check-Ergebnisse abrufen.""" """Health-Check-Ergebnisse abrufen."""
# Prüfen ob Tabelle existiert # Prüfen ob Tabelle existiert
cursor = await db.execute( cursor = await db.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='source_health_checks'" "SELECT name FROM sqlite_master WHERE type='table' AND name='source_health_checks'"
) )
@@ -369,7 +369,7 @@ async def get_suggestions(
admin: dict = Depends(get_current_admin), admin: dict = Depends(get_current_admin),
db: aiosqlite.Connection = Depends(db_dependency), db: aiosqlite.Connection = Depends(db_dependency),
): ):
"""Alle Vorschläge abrufen (pending zuerst, dann letzte 20 bearbeitete).""" """Alle Vorschläge abrufen (pending zuerst, dann letzte 20 bearbeitete)."""
cursor = await db.execute( cursor = await db.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='source_suggestions'" "SELECT name FROM sqlite_master WHERE type='table' AND name='source_suggestions'"
) )
@@ -432,7 +432,7 @@ async def update_suggestion(
"SELECT id FROM sources WHERE url = ? AND tenant_id IS NULL", (url,) "SELECT id FROM sources WHERE url = ? AND tenant_id IS NULL", (url,)
) )
if await cursor.fetchone(): if await cursor.fetchone():
result_action = "übersprungen (URL bereits vorhanden)" result_action = "übersprungen (URL bereits vorhanden)"
new_status = "rejected" new_status = "rejected"
else: else:
await db.execute( await db.execute(
@@ -442,7 +442,7 @@ async def update_suggestion(
) )
result_action = f"Quelle '{name}' angelegt" result_action = f"Quelle '{name}' angelegt"
else: else:
result_action = "übersprungen (keine URL)" result_action = "übersprungen (keine URL)"
new_status = "rejected" new_status = "rejected"
elif stype == "deactivate_source": elif stype == "deactivate_source":
@@ -455,7 +455,7 @@ async def update_suggestion(
source_id = suggestion["source_id"] source_id = suggestion["source_id"]
if source_id: if source_id:
await db.execute("DELETE FROM sources WHERE id = ?", (source_id,)) await db.execute("DELETE FROM sources WHERE id = ?", (source_id,))
result_action = "Quelle gelöscht" result_action = "Quelle gelöscht"
elif stype == "fix_url": elif stype == "fix_url":
source_id = suggestion["source_id"] source_id = suggestion["source_id"]
@@ -465,7 +465,7 @@ async def update_suggestion(
result_action = f"URL aktualisiert" result_action = f"URL aktualisiert"
# Auto-Reject: Wenn fix_url oder add_source akzeptiert wird, # Auto-Reject: Wenn fix_url oder add_source akzeptiert wird,
# zugehörige deactivate_source-Vorschläge automatisch ablehnen # zugehörige deactivate_source-Vorschläge automatisch ablehnen
if stype in ("fix_url", "add_source") and suggestion.get("source_id"): if stype in ("fix_url", "add_source") and suggestion.get("source_id"):
await db.execute( await db.execute(
"UPDATE source_suggestions SET status = 'rejected', reviewed_at = CURRENT_TIMESTAMP " "UPDATE source_suggestions SET status = 'rejected', reviewed_at = CURRENT_TIMESTAMP "
@@ -640,7 +640,7 @@ async def run_health_check_stream(
# Stale + Duplikate (schnell, kein Fortschritt noetig) # Stale + Duplikate (schnell, kein Fortschritt noetig)
for source in sources: for source in sources:
if source["source_type"] == "excluded": if source["source_type"] in ("excluded", "web_source"):
continue continue
article_count = source.get("article_count") or 0 article_count = source.get("article_count") or 0
if article_count == 0: if article_count == 0:
@@ -697,7 +697,7 @@ async def search_fix_for_source(
admin: dict = Depends(get_current_admin), admin: dict = Depends(get_current_admin),
db: aiosqlite.Connection = Depends(db_dependency), db: aiosqlite.Connection = Depends(db_dependency),
): ):
"""Sonnet mit WebSearch nach Lösung für eine kaputte Quelle suchen lassen.""" """Sonnet mit WebSearch nach Lösung für eine kaputte Quelle suchen lassen."""
import json as _json import json as _json
cursor = await db.execute( cursor = await db.execute(
@@ -710,7 +710,7 @@ async def search_fix_for_source(
source = dict(source) source = dict(source)
# Health-Check-Probleme für diese Quelle laden # Health-Check-Probleme für diese Quelle laden
cursor = await db.execute( cursor = await db.execute(
"SELECT check_type, status, message FROM source_health_checks WHERE source_id = ?", "SELECT check_type, status, message FROM source_health_checks WHERE source_id = ?",
(source_id,), (source_id,),
@@ -729,14 +729,14 @@ Kategorie: {source['category']}
Probleme: Probleme:
{issues_text} {issues_text}
Aufgabe: Suche im Internet nach funktionierenden Alternativen für diese Quelle. Aufgabe: Suche im Internet nach funktionierenden Alternativen für diese Quelle.
- Finde konkrete RSS-Feed-URLs die tatsächlich funktionieren - Finde konkrete RSS-Feed-URLs die tatsächlich funktionieren
- Prüfe ob es alternative Zugangswege gibt (andere Subdomains, Feed-Aggregatoren, alternative URLs) - Prüfe ob es alternative Zugangswege gibt (andere Subdomains, Feed-Aggregatoren, alternative URLs)
- Gibt es eine Lösung oder ist die Quelle nur noch per WebSearch erreichbar? - Gibt es eine Lösung oder ist die Quelle nur noch per WebSearch erreichbar?
Regeln: Regeln:
- Maximal 3 Lösungen vorschlagen (die besten) - Maximal 3 Lösungen vorschlagen (die besten)
- Verwende echte deutsche Umlaute (ü, ä, ö, ß), keine Umschreibungen (ue, ae, oe, ss) - Verwende echte deutsche Umlaute (ü, ä, ö, ß), keine Umschreibungen (ue, ae, oe, ss)
Antworte NUR mit einem JSON-Objekt: Antworte NUR mit einem JSON-Objekt:
{{ {{
@@ -746,10 +746,10 @@ Antworte NUR mit einem JSON-Objekt:
"type": "replace_url|add_feed|deactivate", "type": "replace_url|add_feed|deactivate",
"name": "Anzeigename", "name": "Anzeigename",
"url": "https://...", "url": "https://...",
"description": "Kurze Begründung" "description": "Kurze Begründung"
}} }}
], ],
"summary": "Zusammenfassung in 1-2 Sätzen" "summary": "Zusammenfassung in 1-2 Sätzen"
}} }}
Nur das JSON, kein anderer Text.""" Nur das JSON, kein anderer Text."""
@@ -767,7 +767,7 @@ Nur das JSON, kein anderer Text."""
else: else:
result = {"fixable": False, "solutions": [], "summary": response[:500]} result = {"fixable": False, "solutions": [], "summary": response[:500]}
# Lösungen als Vorschläge speichern # Lösungen als Vorschläge speichern
await db.executescript(""" await db.executescript("""
CREATE TABLE IF NOT EXISTS source_suggestions ( CREATE TABLE IF NOT EXISTS source_suggestions (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -793,10 +793,10 @@ Nur das JSON, kein anderer Text."""
title = f"{source['name']}: {sol.get('description', sol_type)[:120]}" title = f"{source['name']}: {sol.get('description', sol_type)[:120]}"
# Duplikat-Check # Duplikat-Check: gleicher Typ + gleiche Quelle bereits pending?
cursor = await db.execute( cursor = await db.execute(
"SELECT id FROM source_suggestions WHERE title = ? AND status = 'pending'", "SELECT id FROM source_suggestions WHERE suggestion_type = ? AND source_id = ? AND status = 'pending'",
(title,), (suggestion_type, source_id),
) )
if await cursor.fetchone(): if await cursor.fetchone():
continue continue