feat: Ausverkauft-Auto-Markierung + Chatbot-Integration
Webhook:
- mark_ausverkauft_in_neuheiten(): Cross-Reference-Funktion
setzt Bit 0 in neuheiten.flags bei #out-Meldungen
- Aufruf sowohl bei neuen Submissions als auch Thread-Appends
- Gefixt: lokales 'import re' entfernt (überschattete globales re)
Chatbot /api/chat:
- Neuheiten-SELECT inkludiert flags-Spalte
- [Neuheit]-Einträge zeigen '🔴 AUSVERKAUFT' bei flags & 1
- [Ausverkauft-Meldung]-Suche in published Submissions
- System-Prompt um Ausverkauft-Regeln ergänzt
Flow: WhatsApp '#out Spielname' → Submission → neuheiten.flags
→ Chatbot warnt aktiv vor vergriffenen Spielen
This commit is contained in:
@@ -615,6 +615,24 @@ def _transcribe_video_bg(db_path: str, submission_id: int, video_path: str):
|
||||
auto_process_bg(db_path, submission_id)
|
||||
|
||||
|
||||
def mark_ausverkauft_in_neuheiten(db, spiel_titel):
|
||||
"""Cross-reference ausverkauft submission with neuheiten table.
|
||||
Sets bit 0 of neuheiten.flags to mark the game as sold out.
|
||||
Returns number of matched neuheiten rows."""
|
||||
if not spiel_titel:
|
||||
return 0
|
||||
try:
|
||||
# Use LIKE for fuzzy matching (handles minor spelling variations)
|
||||
result = db.execute(
|
||||
"UPDATE neuheiten SET flags = flags | 1 WHERE LOWER(titel) LIKE ?",
|
||||
(f"%{spiel_titel.lower().strip()}%",),
|
||||
)
|
||||
db.commit()
|
||||
return result.rowcount
|
||||
except Exception:
|
||||
return 0
|
||||
|
||||
|
||||
# ── Routes ──────────────────────────────────────────────────────────
|
||||
@app.route("/whatsapp/webhook", methods=["GET", "POST"])
|
||||
def whatsapp_webhook():
|
||||
@@ -753,6 +771,9 @@ def whatsapp_webhook():
|
||||
"UPDATE submissions SET category=?, spiel_titel=? WHERE id=?",
|
||||
("ausverkauft", out_title, recent["id"]),
|
||||
)
|
||||
# Auto-mark matching neuheiten as sold out
|
||||
if out_title:
|
||||
mark_ausverkauft_in_neuheiten(db, out_title)
|
||||
# Merge media paths (keep all, separated by ||)
|
||||
if media_path:
|
||||
merged_media = media_path
|
||||
@@ -772,6 +793,9 @@ def whatsapp_webhook():
|
||||
VALUES (?, ?, ?, ?, ?, ?, 'new', ?, ?, ?)""",
|
||||
("whatsapp", sender_id, sender_name, msg_type, content, media_path, halle, cat, spiel_titel),
|
||||
)
|
||||
# Auto-mark matching neuheiten as sold out
|
||||
if cat == "ausverkauft" and spiel_titel:
|
||||
mark_ausverkauft_in_neuheiten(db, spiel_titel)
|
||||
|
||||
# Auto-reply: only on NEW threads, not appends
|
||||
if not thread_found and META_TOKEN:
|
||||
@@ -789,7 +813,6 @@ def whatsapp_webhook():
|
||||
)
|
||||
send_whatsapp_message(sender_id, reply)
|
||||
# Thorsten voice: same text but without emojis
|
||||
import re
|
||||
tts_text = re.sub(r'[^\x00-\x7F\u00C0-\u00FF\u0100-\u017F\u0180-\u024F\u1E00-\u1EFF\u2018-\u201F\u2013\u2014\u2026\u00A0\u00AB\u00BB\u20AC]', '', reply)
|
||||
tts_text = re.sub(r' +', ' ', tts_text).strip()
|
||||
audio_path = generate_thorsten_tts(tts_text)
|
||||
@@ -3213,7 +3236,7 @@ def api_chat():
|
||||
params_n.extend(params_n[:]) # duplicate for WHERE clause
|
||||
score_expr_n = ' + '.join(score_parts_n)
|
||||
sql_n = (
|
||||
f"SELECT titel, untertitel, halle, stand, info_text, themen, web, ({score_expr_n}) AS relevance "
|
||||
f"SELECT titel, untertitel, halle, stand, info_text, themen, web, flags, ({score_expr_n}) AS relevance "
|
||||
"FROM neuheiten "
|
||||
f"WHERE {' OR '.join(like_clauses_n)} "
|
||||
"ORDER BY relevance DESC LIMIT 30"
|
||||
@@ -3235,12 +3258,43 @@ def api_chat():
|
||||
halle_stand = f"{n['halle'] or ''} {n['stand'] or ''}".strip()
|
||||
verlag = n['untertitel'] or ''
|
||||
themen = (n['themen'] or '').strip()
|
||||
ausverkauft = 'AUSVERKAUFT' if (n['flags'] or 0) & 1 else ''
|
||||
context_parts.append(
|
||||
f"[Neuheit] {n['titel']}"
|
||||
+ (f" | Verlag: {verlag}" if verlag else '')
|
||||
+ (f" | {halle_stand}" if halle_stand else '')
|
||||
+ (f" | 🔴 {ausverkauft}" if ausverkauft else '')
|
||||
+ (f" | {themen}" if themen else '')
|
||||
)
|
||||
|
||||
# Also search published submissions for ausverkauft stories with matching spiel_titel
|
||||
if meaningful_terms:
|
||||
ausverkauft_subs = []
|
||||
try:
|
||||
for t in meaningful_terms:
|
||||
rows = db.execute(
|
||||
"SELECT spiel_titel, content, halle, published_at FROM submissions "
|
||||
"WHERE status='published' AND deleted_at IS NULL "
|
||||
"AND category='ausverkauft' AND spiel_titel IS NOT NULL "
|
||||
"AND LOWER(spiel_titel) LIKE ? LIMIT 5",
|
||||
(f"%{t}%",),
|
||||
).fetchall()
|
||||
ausverkauft_subs.extend(rows)
|
||||
except Exception:
|
||||
pass
|
||||
# Deduplicate by spiel_titel
|
||||
seen_titles = set()
|
||||
for r in ausverkauft_subs[:10]:
|
||||
title = (r['spiel_titel'] or '').strip()
|
||||
if title and title.lower() not in seen_titles:
|
||||
seen_titles.add(title.lower())
|
||||
halle = r['halle'] or ''
|
||||
snippet = (r['content'] or '').strip()[:120]
|
||||
context_parts.append(
|
||||
f"[Ausverkauft-Meldung] {title}"
|
||||
+ (f" | {halle}" if halle else '')
|
||||
+ (f" | {snippet}" if snippet else '')
|
||||
)
|
||||
context = '\n'.join(context_parts)
|
||||
|
||||
# System prompt
|
||||
@@ -3256,6 +3310,9 @@ def api_chat():
|
||||
"- Bei Standortfragen nenne immer die Hallen-Nummer.\n"
|
||||
"- [Aussteller]-Einträge enthalten Verlage/Shops mit Halle, Stand und Land.\n"
|
||||
"- [Neuheit]-Einträge enthalten Brettspiel-Neuheiten mit Verlag, Halle, Stand und Kategorien.\n"
|
||||
"- [Neuheit] mit 'AUSVERKAUFT' = am Stand vergriffen — erwähne das aktiv.\n"
|
||||
"- [Ausverkauft-Meldung]-Einträge = Community-Berichte über vergriffene Spiele.\n"
|
||||
"- Bei Fragen wie 'ist X noch da?' prüfe Ausverkauft-Status und warne.\n"
|
||||
"- Wenn du einen Beitrag erwähnst, verlinke ihn IMMER mit der Beitrags-ID aus dem Kontext.\n"
|
||||
" Format: [Kurzbeschreibung](/beitrag/ID)\n"
|
||||
" Beispiel: 'Schau dir [diesen Erfahrungsbericht](/beitrag/53) an!'\n"
|
||||
|
||||
Reference in New Issue
Block a user