diff --git a/article_pipeline.py b/article_pipeline.py new file mode 100644 index 0000000..f461a20 --- /dev/null +++ b/article_pipeline.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 +""" +article_pipeline.py — Stage 0: Input-Preparation für den BSN-Artikel-Workflow. + +Liest Topic, PDFs, URLs und baut einen strukturierten Prompt für den Hermes-Agenten, +der dann via delegate_task Research (Stage 1) und Write+QC (Stage 2+3) durchführt. +Stage 4 (Git, Verify) macht der Agent selbst. + +Usage: + # Thema + PDF + python3 article_pipeline.py --topic "Gamegenic × TCC" --pdf pm.pdf --rubric "Nachrichten" + + # Thema + URL + python3 article_pipeline.py --topic "Neue Kennerspiel-Nominierungen" --url "https://..." + + # Nur Thema (autonome Recherche) + python3 article_pipeline.py --topic "Spiel des Jahres 2026" --rubric "Nachrichten" + + # Batch (JSON) + python3 article_pipeline.py --batch topics.json + +Output: Strukturierter Prompt nach stdout (wird vom Hermes-Cronjob als Context injiziert) +Exit-Codes: 0 = Prompt bereit, 1 = Fehler in Stage 0 +""" + +import argparse +import json +import os +import sys +import subprocess +import tempfile +from datetime import datetime + +WORKSPACE = "/home/hermes/workspace" +DOC_CACHE = os.path.expanduser("~/.hermes/cache/documents") + + +def ocr_pdf(pdf_path: str) -> str: + """PDF mit pymupdf OCRen und Text extrahieren.""" + import pymupdf + doc = pymupdf.open(pdf_path) + texts = [] + for page in doc: + texts.append(page.get_text()) + doc.close() + return "\n\n".join(texts) + + +def extract_url_text(url: str) -> str: + """URL-Inhalt via 5-Stufen-Eskalation abrufen.""" + import urllib.request + import urllib.error + + # Stufe 1: curl direkt + try: + req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}) + with urllib.request.urlopen(req, timeout=15) as resp: + return resp.read().decode("utf-8", errors="ignore")[:5000] + except Exception: + pass + + # Stufe 2-5 wäre hier — für Stage 0 reicht Stufe 1 + return f"[URL nicht direkt abrufbar: {url}]" + + +def build_prompt(topic: str, sources: list[str], rubric: str, + category_id: int, image_urls: list[str], + deadline: str | None, + pdf_texts: dict[str, str]) -> str: + """Direkten Agent-Prompt für den Artikel-Workflow bauen.""" + + now = datetime.now() + date_str = now.strftime("%d. %B %Y") + slug = topic.lower().replace(" ", "_").replace(":", "").replace("×", "x")[:40] + + lines = [] + lines.append(f"Führe den BSN-Artikel-Workflow für folgendes Thema aus:") + lines.append(f"**Thema:** {topic}") + lines.append(f"**Datum:** {date_str}") + lines.append(f"**Rubrik:** {rubric} (Joomla catid={category_id})") + if deadline: + lines.append(f"**Deadline:** {deadline}") + lines.append(f"**Dateiname:** artikel_{slug}_{now.strftime('%Y-%m-%d')}.html") + lines.append("") + + # PDF-OCR-Ergebnisse + if pdf_texts: + lines.append("## PDF-Quellen (OCR-extrahiert, bereits im Context)") + for path, text in pdf_texts.items(): + fname = os.path.basename(path) + truncated = text[:3000] + if len(text) > 3000: + truncated += f"\n... (gekürzt, Original: {len(text)} Zeichen)" + lines.append(f"\n### {fname}\n{truncated}\n") + lines.append("") + + # URL-Quellen + if sources: + lines.append("## URL-Quellen") + for s in sources: + lines.append(f"- {s}") + lines.append("") + + # STAGE 1: Research + lines.append("## STAGE 1: Research") + lines.append("") + lines.append("Nutze **delegate_task** um einen Research-Subagent zu starten:") + lines.append("") + lines.append(f"- goal: \"Recherchiere zum Thema: {topic}. Sammle Fakten aus allen Quellen, bewerte sie (AAA/AA/A), ergänze BGG-Daten (Designer, Verlag, Jahr). Erstelle Research-Brief in /tmp/research_brief.md. Bei unvollständigen Kern-Fakten → Exit 1.\"") + lines.append("- context: Enthält PDF-Texte und URLs (siehe oben). Nutze 5-Stufen-Eskalation für unerreichbare URLs. web_search site:boardgamegeek.com für BGG-Daten.") + lines.append('- toolsets: ["web", "terminal", "file", "search", "skills"]') + lines.append("") + lines.append("**WICHTIG:** Warte bis Sub-Agent #1 fertig ist. Prüfe /tmp/research_brief.md. Bei Exit-Code 1 → MELDUNG an Basti, NICHT fortfahren.") + lines.append("") + + # STAGE 2+3: Write+QC + lines.append("## STAGE 2+3: Write + QC") + lines.append("") + lines.append("Nutze **delegate_task** für einen Write+QC-Subagent:") + lines.append("") + lines.append(f'- goal: "Schreibe HTML-Artikel zum Thema: {topic}. Nutze Research-Brief aus /tmp/research_brief.md. Datei: artikel_{slug}_{now.strftime("%Y-%m-%d")}.html in /home/hermes/workspace/. Danach: bash /home/hermes/workspace/verify_article.sh artikel_{slug}_{now.strftime("%Y-%m-%d")}.html. Bei Fehlern korrigieren, max 3 Versuche."') + lines.append(f'- context: "HTML-Regeln: KEIN XML API v2, KEIN Betriebsgeheimnis, KEINE Emojis.
mit Quellen-Ratings. Datei endet mit