Website (14 monoliths split): - compliance/page.tsx (1,519 → 9), docs/audit (1,262 → 20) - quality (1,231 → 16), alerts (1,203 → 10), docs (1,202 → 11) - i18n.ts (1,173 → 8 language files) - unity-bridge (1,094 → 12), backlog (1,087 → 6) - training (1,066 → 8), rag (1,063 → 8) - Deleted index_original.ts (4,899 LOC dead backup) Studio-v2 (5 monoliths split): - meet/page.tsx (1,481 → 9), messages (1,166 → 9) - AlertsB2BContext.tsx (1,165 → 5 modules) - alerts-b2b/page.tsx (1,019 → 6), korrektur/archiv (1,001 → 6) All existing imports preserved. Zero new TypeScript errors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
431 lines
28 KiB
TypeScript
431 lines
28 KiB
TypeScript
'use client'
|
|
|
|
export default function DocumentationTab() {
|
|
return (
|
|
<div className="bg-white rounded-xl border border-slate-200 p-6 overflow-auto max-h-[calc(100vh-200px)]">
|
|
<div className="prose prose-slate max-w-none prose-headings:font-semibold prose-h1:text-2xl prose-h2:text-xl prose-h3:text-lg prose-pre:bg-slate-900 prose-pre:text-slate-100">
|
|
{/* Header */}
|
|
<div className="not-prose mb-8 pb-6 border-b border-slate-200">
|
|
<h1 className="text-2xl font-bold text-slate-900">BreakPilot Alerts Agent</h1>
|
|
<p className="text-sm text-slate-500 mt-1">Version: 1.0.0 | Stand: Januar 2026 | Autor: BreakPilot Development Team</p>
|
|
</div>
|
|
|
|
{/* Audit Box */}
|
|
<div className="not-prose bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
|
|
<h3 className="font-semibold text-blue-900 mb-2">Audit-Relevante Informationen</h3>
|
|
<p className="text-sm text-blue-800">
|
|
Dieses Dokument dient als technische Dokumentation fuer das Alert-Monitoring-System der BreakPilot Plattform.
|
|
Es ist fuer Audits durch Bildungstraeger und Datenschutzbeauftragte konzipiert.
|
|
</p>
|
|
</div>
|
|
|
|
{/* Ziel des Systems */}
|
|
<h2>Ziel des Alert-Systems</h2>
|
|
<p>Das System ermoeglicht automatisierte Ueberwachung von Bildungsthemen mit:</p>
|
|
<ul>
|
|
<li><strong>Google Alerts Integration</strong>: RSS-Feeds von Google Alerts automatisch abrufen</li>
|
|
<li><strong>RSS/Atom Feeds</strong>: Beliebige Nachrichtenquellen einbinden</li>
|
|
<li><strong>KI-Relevanzpruefung</strong>: Automatische Bewertung der Relevanz durch LLM</li>
|
|
<li><strong>Regelbasierte Filterung</strong>: Flexible Regeln fuer automatische Sortierung</li>
|
|
<li><strong>Multi-Channel Actions</strong>: E-Mail, Webhook, Slack Benachrichtigungen</li>
|
|
<li><strong>Few-Shot Learning</strong>: Profil verbessert sich durch Nutzerfeedback</li>
|
|
</ul>
|
|
|
|
{/* Datenschutz Compliance */}
|
|
<h2>Datenschutz-Compliance</h2>
|
|
<DocTable
|
|
headers={['Massnahme', 'Umsetzung', 'Wirkung']}
|
|
rows={[
|
|
['100% Self-Hosted', 'Alle Dienste auf eigenen Servern', 'Keine Cloud-Abhaengigkeit'],
|
|
['Lokale KI', 'Ollama/vLLM on-premise', 'Keine Daten an OpenAI etc.'],
|
|
['URL-Deduplizierung', 'SHA256-Hash, Tracking entfernt', 'Minimale Datenspeicherung'],
|
|
['Soft-Delete', 'Archivierung statt Loeschung', 'Audit-Trail erhalten'],
|
|
['RBAC', 'Rollenbasierte Zugriffskontrolle', 'Nur autorisierter Zugriff'],
|
|
]}
|
|
/>
|
|
|
|
{/* Architektur */}
|
|
<h2>1. Systemarchitektur</h2>
|
|
<h3>Gesamtarchitektur</h3>
|
|
<pre className="text-xs leading-tight overflow-x-auto">{ARCHITECTURE_DIAGRAM}</pre>
|
|
|
|
{/* Datenfluss */}
|
|
<h3>Datenfluss bei Alert-Verarbeitung</h3>
|
|
<pre className="text-xs leading-tight overflow-x-auto">{DATAFLOW_DIAGRAM}</pre>
|
|
|
|
{/* Feed Ingestion */}
|
|
<h2>2. Feed Ingestion</h2>
|
|
<h3>RSS Fetcher</h3>
|
|
<DocTable
|
|
headers={['Eigenschaft', 'Wert', 'Beschreibung']}
|
|
rows={[
|
|
['Parser', 'feedparser 6.x', 'Standard RSS/Atom Parser'],
|
|
['HTTP Client', 'httpx (async)', 'Non-blocking Fetches'],
|
|
['Timeout', '30 Sekunden', 'Konfigurierbar'],
|
|
['Parallelitaet', 'Ja (asyncio.gather)', 'Mehrere Feeds gleichzeitig'],
|
|
]}
|
|
/>
|
|
|
|
<h3>Deduplizierung</h3>
|
|
<p>Die Deduplizierung verhindert doppelte Alerts durch:</p>
|
|
<ol>
|
|
<li><strong>URL-Normalisierung</strong>: Tracking-Parameter entfernen (utm_*, fbclid, gclid), Hostname lowercase, Trailing Slash entfernen</li>
|
|
<li><strong>URL-Hash</strong>: SHA256 der normalisierten URL, erste 16 Zeichen als Index</li>
|
|
</ol>
|
|
|
|
{/* Rule Engine */}
|
|
<h2>3. Rule Engine</h2>
|
|
<h3>Unterstuetzte Operatoren</h3>
|
|
<DocTable
|
|
headers={['Operator', 'Beschreibung', 'Beispiel']}
|
|
rows={[
|
|
['contains', 'Text enthaelt', 'title contains "Inklusion"'],
|
|
['not_contains', 'Text enthaelt nicht', 'title not_contains "Werbung"'],
|
|
['equals', 'Exakte Uebereinstimmung', 'status equals "new"'],
|
|
['regex', 'Regulaerer Ausdruck', 'title regex "\\d{4}"'],
|
|
['gt / lt', 'Groesser/Kleiner', 'relevance_score gt 0.8'],
|
|
['in', 'In Liste enthalten', 'title in ["KI", "AI"]'],
|
|
]}
|
|
monoFirstCol
|
|
/>
|
|
|
|
<h3>Verfuegbare Felder</h3>
|
|
<DocTable
|
|
headers={['Feld', 'Typ', 'Beschreibung']}
|
|
rows={[
|
|
['title', 'String', 'Alert-Titel'],
|
|
['snippet', 'String', 'Textausschnitt'],
|
|
['url', 'String', 'Artikel-URL'],
|
|
['source', 'Enum', 'google_alerts_rss, rss_feed, manual'],
|
|
['relevance_score', 'Float', '0.0 - 1.0'],
|
|
['relevance_decision', 'Enum', 'KEEP, DROP, REVIEW'],
|
|
]}
|
|
monoFirstCol
|
|
/>
|
|
|
|
<h3>Aktionen</h3>
|
|
<DocTable
|
|
headers={['Aktion', 'Beschreibung', 'Konfiguration']}
|
|
rows={[
|
|
['keep', 'Als relevant markieren', '-'],
|
|
['drop', 'Archivieren', '-'],
|
|
['tag', 'Tags hinzufuegen', '{"tags": ["wichtig"]}'],
|
|
['email', 'E-Mail senden', '{"to": "x@y.de"}'],
|
|
['webhook', 'HTTP POST', '{"url": "https://..."}'],
|
|
['slack', 'Slack-Nachricht', '{"webhook_url": "..."}'],
|
|
]}
|
|
monoFirstCol
|
|
/>
|
|
|
|
{/* KI Relevanzpruefung */}
|
|
<h2>4. KI-Relevanzpruefung</h2>
|
|
<h3>LLM Scorer</h3>
|
|
<DocTable
|
|
headers={['Eigenschaft', 'Wert', 'Beschreibung']}
|
|
rows={[
|
|
['Gateway URL', 'http://localhost:8000/llm', 'LLM Gateway Endpoint'],
|
|
['Modell', 'breakpilot-teacher-8b', 'Fein-getuntes Llama 3.1'],
|
|
['Temperatur', '0.3', 'Niedrig fuer Konsistenz'],
|
|
['Max Tokens', '500', 'Response-Limit'],
|
|
]}
|
|
/>
|
|
|
|
<h3>Bewertungskriterien</h3>
|
|
<div className="not-prose overflow-x-auto">
|
|
<table className="min-w-full text-sm">
|
|
<thead className="bg-slate-50">
|
|
<tr>
|
|
<th className="px-4 py-2 text-left font-semibold">Entscheidung</th>
|
|
<th className="px-4 py-2 text-left font-semibold">Score-Bereich</th>
|
|
<th className="px-4 py-2 text-left font-semibold">Bedeutung</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-slate-200">
|
|
<tr className="bg-green-50"><td className="px-4 py-2 font-semibold text-green-800">KEEP</td><td className="px-4 py-2">0.7 - 1.0</td><td className="px-4 py-2">Klar relevant, in Inbox anzeigen</td></tr>
|
|
<tr className="bg-amber-50"><td className="px-4 py-2 font-semibold text-amber-800">REVIEW</td><td className="px-4 py-2">0.4 - 0.7</td><td className="px-4 py-2">Unsicher, Nutzer entscheidet</td></tr>
|
|
<tr className="bg-red-50"><td className="px-4 py-2 font-semibold text-red-800">DROP</td><td className="px-4 py-2">0.0 - 0.4</td><td className="px-4 py-2">Irrelevant, automatisch archivieren</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<h3>Few-Shot Learning</h3>
|
|
<p>Das Profil verbessert sich durch Nutzerfeedback:</p>
|
|
<ol>
|
|
<li>Nutzer markiert Alert als relevant/irrelevant</li>
|
|
<li>Alert wird als positives/negatives Beispiel gespeichert</li>
|
|
<li>Beispiele werden in den Prompt eingefuegt (max. 5 pro Kategorie)</li>
|
|
<li>LLM lernt aus konkreten Beispielen des Nutzers</li>
|
|
</ol>
|
|
|
|
{/* Relevanz Profile */}
|
|
<h2>5. Relevanz-Profile</h2>
|
|
<h3>Standard-Bildungsprofil</h3>
|
|
<DocTable
|
|
headers={['Prioritaet', 'Gewicht', 'Keywords']}
|
|
rows={[
|
|
['Inklusion', '0.9', 'inklusiv, Foerderbedarf, Behinderung'],
|
|
['Datenschutz Schule', '0.85', 'DSGVO, Schuelerfotos, Einwilligung'],
|
|
['Schulrecht Bayern', '0.8', 'BayEUG, Schulordnung, KM'],
|
|
['Digitalisierung Schule', '0.7', 'DigitalPakt, Tablet-Klasse'],
|
|
]}
|
|
/>
|
|
|
|
<h3>Standard-Ausschluesse</h3>
|
|
<ul>
|
|
<li>Stellenanzeige</li>
|
|
<li>Praktikum gesucht</li>
|
|
<li>Werbung</li>
|
|
<li>Pressemitteilung</li>
|
|
</ul>
|
|
|
|
{/* API Endpoints */}
|
|
<h2>6. API Endpoints</h2>
|
|
<h3>Alerts API</h3>
|
|
<DocTable
|
|
headers={['Endpoint', 'Methode', 'Beschreibung']}
|
|
rows={[
|
|
['/api/alerts/inbox', 'GET', 'Inbox Items abrufen'],
|
|
['/api/alerts/ingest', 'POST', 'Manuell Alert importieren'],
|
|
['/api/alerts/run', 'POST', 'Scoring-Pipeline starten'],
|
|
['/api/alerts/feedback', 'POST', 'Relevanz-Feedback geben'],
|
|
['/api/alerts/stats', 'GET', 'Statistiken abrufen'],
|
|
]}
|
|
monoFirstCol
|
|
/>
|
|
|
|
<h3>Topics API</h3>
|
|
<DocTable
|
|
headers={['Endpoint', 'Methode', 'Beschreibung']}
|
|
rows={[
|
|
['/api/alerts/topics', 'GET', 'Topics auflisten'],
|
|
['/api/alerts/topics', 'POST', 'Neues Topic erstellen'],
|
|
['/api/alerts/topics/{id}', 'PUT', 'Topic aktualisieren'],
|
|
['/api/alerts/topics/{id}', 'DELETE', 'Topic loeschen (CASCADE)'],
|
|
['/api/alerts/topics/{id}/fetch', 'POST', 'Manueller Feed-Abruf'],
|
|
]}
|
|
monoFirstCol
|
|
/>
|
|
|
|
<h3>Rules & Profile API</h3>
|
|
<DocTable
|
|
headers={['Endpoint', 'Methode', 'Beschreibung']}
|
|
rows={[
|
|
['/api/alerts/rules', 'GET/POST', 'Regeln verwalten'],
|
|
['/api/alerts/rules/{id}', 'PUT/DELETE', 'Regel bearbeiten/loeschen'],
|
|
['/api/alerts/profile', 'GET', 'Profil abrufen'],
|
|
['/api/alerts/profile', 'PUT', 'Profil aktualisieren'],
|
|
['/api/alerts/scheduler/status', 'GET', 'Scheduler-Status'],
|
|
]}
|
|
monoFirstCol
|
|
/>
|
|
|
|
{/* Datenbank Schema */}
|
|
<h2>7. Datenbank-Schema</h2>
|
|
<h3>Tabellen</h3>
|
|
<DocTable
|
|
headers={['Tabelle', 'Beschreibung', 'Wichtige Felder']}
|
|
rows={[
|
|
['alert_topics', 'Feed-Quellen', 'name, feed_url, feed_type, is_active, fetch_interval'],
|
|
['alert_items', 'Einzelne Alerts', 'title, url, url_hash, relevance_score, relevance_decision'],
|
|
['alert_rules', 'Filterregeln', 'name, conditions (JSON), action_type, priority'],
|
|
['alert_profiles', 'Nutzer-Profile', 'priorities, exclusions, positive/negative_examples'],
|
|
]}
|
|
monoFirstCol
|
|
/>
|
|
|
|
{/* DSGVO */}
|
|
<h2>8. DSGVO-Konformitaet</h2>
|
|
<h3>Rechtsgrundlage (Art. 6 DSGVO)</h3>
|
|
<DocTable
|
|
headers={['Verarbeitung', 'Rechtsgrundlage', 'Umsetzung']}
|
|
rows={[
|
|
['Feed-Abruf', 'Art. 6(1)(f) - Berechtigtes Interesse', 'Informationsbeschaffung'],
|
|
['Alert-Speicherung', 'Art. 6(1)(f) - Berechtigtes Interesse', 'Nur oeffentliche Informationen'],
|
|
['LLM-Scoring', 'Art. 6(1)(f) - Berechtigtes Interesse', 'On-Premise, keine PII'],
|
|
['Profil-Learning', 'Art. 6(1)(a) - Einwilligung', 'Opt-in durch Nutzung'],
|
|
]}
|
|
/>
|
|
|
|
<h3>Technische Datenschutz-Massnahmen</h3>
|
|
<ul>
|
|
<li><strong>Datenminimierung</strong>: Nur Titel, URL, Snippet - keine personenbezogenen Daten</li>
|
|
<li><strong>Lokale Verarbeitung</strong>: Ollama/vLLM on-premise - kein Datenabfluss an Cloud</li>
|
|
<li><strong>Pseudonymisierung</strong>: UUIDs statt Namen</li>
|
|
<li><strong>Automatische Loeschung</strong>: 90 Tage Retention fuer archivierte Alerts</li>
|
|
<li><strong>Audit-Logging</strong>: Stats und Match-Counts fuer Nachvollziehbarkeit</li>
|
|
</ul>
|
|
|
|
{/* Open Source */}
|
|
<h2>9. Open Source Lizenzen (SBOM)</h2>
|
|
<h3>Python Dependencies</h3>
|
|
<DocTable
|
|
headers={['Komponente', 'Lizenz', 'Kommerziell']}
|
|
rows={[
|
|
['FastAPI', 'MIT', 'Ja'],
|
|
['SQLAlchemy', 'MIT', 'Ja'],
|
|
['httpx', 'BSD-3-Clause', 'Ja'],
|
|
['feedparser', 'BSD-2-Clause', 'Ja'],
|
|
['APScheduler', 'MIT', 'Ja'],
|
|
]}
|
|
greenLastCol
|
|
/>
|
|
|
|
<h3>KI-Komponenten</h3>
|
|
<DocTable
|
|
headers={['Komponente', 'Lizenz', 'Kommerziell']}
|
|
rows={[
|
|
['Ollama', 'MIT', 'Ja'],
|
|
['Llama 3.1', 'Meta Llama 3', 'Ja*'],
|
|
['vLLM', 'Apache-2.0', 'Ja'],
|
|
]}
|
|
greenLastCol
|
|
/>
|
|
|
|
{/* Kontakt */}
|
|
<h2>10. Kontakt & Support</h2>
|
|
<DocTable
|
|
headers={['Kontakt', 'Adresse']}
|
|
rows={[
|
|
['Technischer Support', 'support@breakpilot.de'],
|
|
['Datenschutzbeauftragter', 'dsb@breakpilot.de'],
|
|
['Dokumentation', 'docs.breakpilot.de'],
|
|
['GitHub', 'github.com/breakpilot'],
|
|
]}
|
|
/>
|
|
|
|
{/* Footer */}
|
|
<div className="not-prose mt-8 pt-6 border-t border-slate-200 text-sm text-slate-500">
|
|
<p>Dokumentation erstellt: Januar 2026 | Version: 1.0.0</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Helper component for repetitive doc tables */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
interface DocTableProps {
|
|
headers: string[]
|
|
rows: string[][]
|
|
monoFirstCol?: boolean
|
|
greenLastCol?: boolean
|
|
}
|
|
|
|
function DocTable({ headers, rows, monoFirstCol, greenLastCol }: DocTableProps) {
|
|
return (
|
|
<div className="not-prose overflow-x-auto">
|
|
<table className="min-w-full text-sm">
|
|
<thead className="bg-slate-50">
|
|
<tr>
|
|
{headers.map((h) => (
|
|
<th key={h} className="px-4 py-2 text-left font-semibold">{h}</th>
|
|
))}
|
|
</tr>
|
|
</thead>
|
|
<tbody className="divide-y divide-slate-200">
|
|
{rows.map((row, ri) => (
|
|
<tr key={ri}>
|
|
{row.map((cell, ci) => (
|
|
<td
|
|
key={ci}
|
|
className={`px-4 py-2${ci === 0 && monoFirstCol ? ' font-mono text-xs' : ''}${ci === row.length - 1 && greenLastCol ? ' text-green-600' : ''}`}
|
|
>
|
|
{cell}
|
|
</td>
|
|
))}
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* ASCII diagrams kept as constants to avoid JSX noise */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
const ARCHITECTURE_DIAGRAM = `\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
|
|
\u2502 BreakPilot Alerts Frontend \u2502
|
|
\u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
|
|
\u2502 \u2502 Dashboard \u2502 \u2502 Inbox \u2502 \u2502 Topics \u2502 \u2502 Profile \u2502 \u2502
|
|
\u2502 \u2502 (Stats) \u2502 \u2502 (Review) \u2502 \u2502 (Feeds) \u2502 \u2502 (Learning) \u2502 \u2502
|
|
\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
|
|
\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
|
|
\u2502
|
|
v
|
|
\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
|
|
\u2502 Ingestion Layer \u2502
|
|
\u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
|
|
\u2502 \u2502 RSS Fetcher \u2502 \u2502 Email Parser \u2502 \u2502 APScheduler \u2502 \u2502
|
|
\u2502 \u2502 (feedparser) \u2502 \u2502 (geplant) \u2502 \u2502 (AsyncIO) \u2502 \u2502
|
|
\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
|
|
\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
|
|
\u2502 \u2502 \u2502
|
|
\u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
|
|
\u2502 \u2502 Deduplication (URL-Hash + SimHash) \u2502 \u2502
|
|
\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
|
|
\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
|
|
\u2502
|
|
v
|
|
\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
|
|
\u2502 Processing Layer \u2502
|
|
\u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
|
|
\u2502 \u2502 Rule Engine \u2502 \u2502
|
|
\u2502 \u2502 Operatoren: contains, regex, gt/lt, in, starts_with \u2502 \u2502
|
|
\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
|
|
\u2502 \u2502 \u2502
|
|
\u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
|
|
\u2502 \u2502 LLM Relevance Scorer \u2502 \u2502
|
|
\u2502 \u2502 Output: { score, decision: KEEP/DROP/REVIEW, summary } \u2502 \u2502
|
|
\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
|
|
\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
|
|
\u2502
|
|
v
|
|
\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
|
|
\u2502 Action Layer \u2502
|
|
\u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
|
|
\u2502 \u2502 Email Action \u2502 \u2502 Webhook Action \u2502 \u2502 Slack Action \u2502 \u2502
|
|
\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
|
|
\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
|
|
\u2502
|
|
v
|
|
\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
|
|
\u2502 Storage Layer \u2502
|
|
\u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502
|
|
\u2502 \u2502 PostgreSQL \u2502 \u2502 Valkey \u2502 \u2502 LLM Gateway \u2502 \u2502
|
|
\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502
|
|
\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518`
|
|
|
|
const DATAFLOW_DIAGRAM = `1. APScheduler triggert Fetch (alle 60 Min. default)
|
|
\u2502
|
|
v
|
|
2. RSS Fetcher holt Feed von Google Alerts
|
|
\u2502
|
|
v
|
|
3. Deduplizierung prueft URL-Hash
|
|
\u2502
|
|
\u251c\u2500\u2500 URL bekannt \u2500\u2500> Uebersprungen
|
|
\u2514\u2500\u2500 URL neu \u2500\u2500> Weiter
|
|
\u2502
|
|
v
|
|
4. Alert in Datenbank gespeichert (Status: NEW)
|
|
\u2502
|
|
v
|
|
5. Rule Engine evaluiert aktive Regeln
|
|
\u2502
|
|
\u251c\u2500\u2500 Regel matcht \u2500\u2500> Aktion ausfuehren
|
|
\u2514\u2500\u2500 Keine Regel \u2500\u2500> LLM Scoring
|
|
\u2502
|
|
v
|
|
6. LLM Relevance Scorer
|
|
\u2502
|
|
\u251c\u2500\u2500 KEEP (>= 0.7) \u2500\u2500> Inbox
|
|
\u251c\u2500\u2500 REVIEW (0.4-0.7) \u2500\u2500> Inbox (Pruefung)
|
|
\u2514\u2500\u2500 DROP (< 0.4) \u2500\u2500> Archiviert
|
|
\u2502
|
|
v
|
|
7. Nutzer-Feedback \u2500\u2500> Profile aktualisieren`
|