diff --git a/backend/app.py b/backend/app.py index 448f2da..fd0d4b5 100644 --- a/backend/app.py +++ b/backend/app.py @@ -122,7 +122,7 @@ def _table_exists(db, name: str) -> bool: @app.get("/api/podcasts/{podcast_id}/episodes/{episode_id}/claims") def get_episode_claims(podcast_id: str, episode_id: str, claim_type: Optional[str] = None): - """Claims (Behauptungen) für eine Episode.""" + """Claims (Behauptungen) für eine Episode, mit Match-Anzahlen je Relation (#16 Stufe 2).""" db = get_db() if not _table_exists(db, "claims"): db.close() @@ -135,8 +135,36 @@ def get_episode_claims(podcast_id: str, episode_id: str, claim_type: Optional[st params.append(claim_type) sql += " ORDER BY paragraph_idx, id" rows = db.execute(sql, params).fetchall() + claims_list = [dict(r) for r in rows] + + # Match-Counts und besten Match je claim_id anhaengen, falls Tabelle existiert + if claims_list and _table_exists(db, "claim_matches"): + ids = [c["id"] for c in claims_list] + placeholder = ",".join("?" * len(ids)) + match_rows = db.execute( + f"SELECT claim_id, relation, COUNT(*) c FROM claim_matches " + f"WHERE claim_id IN ({placeholder}) GROUP BY claim_id, relation", + ids, + ).fetchall() + counts = {} + for r in match_rows: + counts.setdefault(r["claim_id"], {})[r["relation"]] = r["c"] + # bester Match je claim (fuer Quick-Link) + best_rows = db.execute( + f"SELECT cm.claim_id, cm.relation, cm.target_podcast, cm.target_episode, " + f"cm.target_idx, cm.reason, cm.score " + f"FROM claim_matches cm " + f"WHERE cm.claim_id IN ({placeholder}) " + f"AND cm.id IN (SELECT MIN(id) FROM claim_matches WHERE claim_id IN ({placeholder}) " + f"GROUP BY claim_id) ", + ids + ids, + ).fetchall() + best = {r["claim_id"]: dict(r) for r in best_rows} + for c in claims_list: + c["match_counts"] = counts.get(c["id"], {}) + c["best_match"] = best.get(c["id"]) db.close() - return {"available": True, "claims": [dict(r) for r in rows]} + return {"available": True, "claims": claims_list} @app.get("/api/podcasts/{podcast_id}/episodes/{episode_id}/questions") diff --git a/webapp/index.html b/webapp/index.html index c9d428c..a817bcb 100644 --- a/webapp/index.html +++ b/webapp/index.html @@ -822,6 +822,15 @@ const AnalysisView = { if (this.mode === 'claims' && it.verifiable) { badges += `verifizierbar`; } + // Claim-Match-Badges (#16 Stufe 2) + if (this.mode === 'claims' && it.match_counts) { + const RC = {belegt: '#86efac', widerspricht: '#f87171', erweitert: '#60a5fa'}; + for (const [rel, cnt] of Object.entries(it.match_counts)) { + if (cnt > 0 && RC[rel]) { + badges += `${rel} ${cnt}`; + } + } + } let answerLink = ''; if (this.mode === 'questions') { const a = it.answered; @@ -844,11 +853,31 @@ const AnalysisView = { } } } + // Best-Match-Link fuer Claims + let matchLink = ''; + if (this.mode === 'claims' && it.best_match) { + const m = it.best_match; + const RC = {belegt: '#86efac', widerspricht: '#f87171', erweitert: '#60a5fa'}; + const col = RC[m.relation] || 'var(--text-muted)'; + const samePodcast = !m.target_podcast || m.target_podcast === CURRENT_PODCAST; + const arrow = samePodcast ? '→' : '↗'; + const target = samePodcast ? escHtml(m.target_episode) : `${escHtml(m.target_podcast)} / ${escHtml(m.target_episode)}`; + if (samePodcast) { + matchLink = `