# Plan: Automatischer QC-Agent für Article-Server

> **Ziel:** Jeder Artikel auf dem Article-Server wird automatisch geprüft. Ein Icon zeigt den QC-Status. Erst nach bestandener Prüfung wird der Artikel für Daniel zum Publizieren freigegeben.

**Stand:** 22. Juni 2026 | **Autor:** Basti (CvD)

---

## 1. Workflow-Visualisierung

```
Artikel wird auf Article-Server hochgeladen
         │
         ▼
┌─────────────────────────────┐
│  🔴 QC AUSSTEHEND           │  ← Icon im Article-Server
│  "Warte auf Qualitätscheck" │
└─────────────┬───────────────┘
              │
    ┌─────────▼─────────┐
    │  QC-Agent startet  │  ← automatisch, asynchron
    │  (Cronjob/Subagent) │
    └─────────┬─────────┘
              │
    ┌─────────▼─────────┐
    │  Phase 1: Technik  │  ~5 Sekunden
    │  Phase 2: Inhalt   │  ~30 Sekunden
    │  Phase 3: Joomla   │  ~5 Sekunden
    └─────────┬─────────┘
              │
      ┌───────┴───────┐
      │               │
      ▼               ▼
┌──────────┐   ┌──────────┐
│ 🟢 FREI  │   │ 🔴 FEHLER│
│ "Geprüft"│   │ "X Fehler"│
└──────────┘   └──────────┘
```

---

## 2. QC-Phasen im Detail

### Phase 1: Technische Prüfung (Sekunden)

| # | Check | Methode | Fehlerbehandlung |
|---|---|---|---|
| T1 | Genau 1 Read-More | `grep -c system-readmore` | ❌ BLOCKER |
| T2 | Kein `<br>` nach Read-More | `grep -A1 system-readmore \| grep '<br>'` | ❌ BLOCKER |
| T3 | `<p class="meta">` nicht `<div>` | Regex | ❌ BLOCKER |
| T4 | Keine Emojis im Body | Unicode-Range grep | ⚠️ WARNUNG |
| T5 | Endet mit `</html>` | `tail -1` | ❌ BLOCKER |
| T6 | Alle Links `target="_blank"` | `grep -c '<a '` vs `grep -c 'target="_blank"'` | ❌ BLOCKER |
| T7 | Quellen als span-strong, nicht h2 | Regex | ❌ BLOCKER |
| T8 | Meta-Description gefüllt | `grep 'meta name="description" content="[a-zA-Z]'` | ❌ BLOCKER |
| T9 | Meta-Keywords gefüllt | `grep 'meta name="keywords" content="[a-zA-Z]'` | ⚠️ WARNUNG |
| T10 | QC-Zeile in `<p class="qc">` | Regex | ⚠️ WARNUNG |
| T11 | HTML wohlgeformt | `xmllint --html` | ❌ BLOCKER |
| T12 | Keine nackten Umlaut-Verluste | `fix_umlauts.py --check` | ❌ BLOCKER |

### Phase 2: Inhaltliche Prüfung (KI-gestützt, ~30 Sekunden)

| # | Check | Methode | Fehlerbehandlung |
|---|---|---|---|
| I1 | **Link-Gültigkeit** | HEAD-Request auf alle externen Links, max 5 parallel | ⚠️ 404 → WARNUNG, Link entfernen? |
| I2 | **Zahlen-Plausibilität** | KI: "Sind genannte Zahlen im Kontext plausibel? Z.B. kein Kickstarter über $50M" | ⚠️ WARNUNG bei Verdacht |
| I3 | **Genderneutralität** | KI: "Enthält der Text maskuline Generika (Spieler, Leser, Entwickler)?" | ⚠️ WARNUNG |
| I4 | **Quellen-Breite** | KI: "Stammen ALLE Quellen aus nur EINER Domain (z.B. nur Reddit)?" | ⚠️ WARNUNG |
| I5 | **Abkürzungen ausgeschrieben** | KI: "Werden Abkürzungen (BGG, SFG, TCG) bei Erstverwendung ausgeschrieben?" | ⚠️ WARNUNG |
| I6 | **Spieletitel in strong** | KI: "Sind alle Spieletitel in `<strong>` gewrappt?" | ⚠️ WARNUNG |
| I7 | **Keine Zugangsprobleme erwähnt** | KI: "Enthält der Text Sätze wie 'Seite nicht zugänglich'?" | ❌ BLOCKER |
| I8 | **Publikationsdatum der Quellen** | KI: "Sind Quellen aktuell (<14 Tage)? Falls älter: als Retro gekennzeichnet?" | ⚠️ WARNUNG |

### Phase 3: Joomla-Readiness

| # | Check | Methode | Fehlerbehandlung |
|---|---|---|---|
| J1 | Bild in article_images.json gemappt | JSON-Key-Check | ⚠️ WARNUNG |
| J2 | Bild-Datei existiert auf Disk | `os.path.exists()` | ❌ BLOCKER |
| J3 | Bild-Format korrekt (joomlaImage) | Regex auf image_intro in Payload | ❌ BLOCKER |
| J4 | Kategorie gesetzt | Meta-Tag oder Default | ⚠️ WARNUNG |
| J5 | Artikel-Server antwortet mit 200 | `curl localhost:8890/artikel_...` | ❌ BLOCKER |

---

## 3. BLOCKER vs WARNUNG

| Level | Bedeutung | Artikel nutzbar? |
|---|---|---|
| **❌ BLOCKER** | Artikel kann SO nicht publiziert werden | ❌ Nein — muss korrigiert werden |
| **⚠️ WARNUNG** | Potenzielles Problem, Publizieren trotzdem möglich | ⚠️ Ja, aber mit Hinweis an Daniel |

**Minimum für 🟢 FREI: 0 BLOCKER**

---

## 4. Icon-System im Article-Server

| Icon | Zustand | Aktion |
|---|---|---|
| 🔴 | QC ausstehend | Warten |
| 🟡 | QC läuft | Warten |
| 🟢 | QC bestanden (0 Blocker) | Publizieren freigegeben |
| 🟠 | QC mit Warnungen (0 Blocker, >0 Warnungen) | Publizieren möglich, Warnungen einsehbar |
| 🔴❌ | QC fehlgeschlagen (>0 Blocker) | Fehlerliste einsehbar, Korrektur nötig |

---

## 5. Technische Umsetzung

### 5.1 QC-Agent als Skill

```yaml
name: article-qc
description: Automatischer Qualitätscheck für Artikel auf dem Article-Server
trigger: Artikel-Upload auf Port 8890
```

### 5.2 article_server.py Erweiterung

```python
# Neue Felder im Artikel-Objekt
{
    "filename": "artikel_xxx.html",
    "qc_status": "pending|running|passed|warning|failed",
    "qc_score": {"blockers": 0, "warnings": 2, "total_checks": 20},
    "qc_details": [
        {"check": "T1", "status": "pass", "message": "Read-More: 1"},
        {"check": "I1", "status": "warn", "message": "3 Links antworten mit 404"}
    ],
    "qc_timestamp": "2026-06-22T10:45:00"
}
```

### 5.3 Ablauf

1. **Trigger:** Sobald eine neue HTML-Datei auf dem Artikel-Server erscheint (Upload oder write_file)
2. **article_server.py** setzt Status auf `pending`, zeigt 🔴
3. **article_server.py** startet `bash verify_article.sh` (Phase 1)
4. Bei 0 Blockern: startet Subagent für Phase 2+3
5. Subagent returned QC-Report als JSON
6. article_server.py speichert Ergebnis in `article_qc.json`
7. Frontend zeigt aktualisiertes Icon

---

## 6. Umsetzungsschritte

| Schritt | Was | Aufwand |
|---|---|---|
| 1 | `article_qc.py` schreiben (Phase 1 Checks + JSON-Output) | 30 Min |
| 2 | `article_server.py` erweitern (QC-Status-Feld + Icon-Logik) | 45 Min |
| 3 | Subagent-Prompt für Phase 2+3 schreiben | 15 Min |
| 4 | `article_server.html` Frontend updaten (Icons) | 30 Min |
| 5 | Testen mit 3 Artikeln (Deal, Crowdfunding, BGA) | 30 Min |
| 6 | Skill `article-qc` anlegen | 10 Min |

---

## 7. Erfolgskriterium

> **99% fehlerfreie Artikel** = 0 BLOCKER + Daniel muss NICHTS manuell nachkorrigieren.

Gemessen über 10 aufeinanderfolgende Artikel.
