seepuls/docs/SCREENSHOT_SUBMIT.md
Till JS 0465d16d6b fix(submit): mana-llm Default-Modell google/gemini-2.5-flash statt gpt-4o-mini
Empirisch gegen llm.mana.how (2026-05-27): mana-llm bedient ollama/* +
google/*, KEIN gpt-4o-mini (→ 500 no_healthy_provider). google/gemini-2.5-
flash ist verfügbar + vision-fähig. Default jetzt env-überschreibbar
(MANA_LLM_MODEL / MANA_LLM_VISION_MODEL).

Korrigiert zugleich die F1-Annahme in docs/SCREENSHOT_SUBMIT.md: der
mana-auth-Service-Key war NIE der Vision-Blocker — llm.mana.how hat Auth
aus (GPU_API_KEY leer). Key bleibt nur für den Crawl-Pfad relevant
(research.mana.how → 401).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 15:21:23 +02:00

9.2 KiB
Raw Permalink Blame History

Screenshot-Submit — „Tipp einreichen"

Stand: 2026-05-26. Phase σ-submit-0 (Foundation) in Arbeit.

Problem

Clubs, Bars und selbstverwaltete Häuser (Contrast, P-Club, Kula …) publizieren ihr Programm fast nur auf Instagram/Facebook-Stories — genau dort, wo der Aggregator-Crawler nicht hinkommt:

  • AGGREGATOR_POLICY.md §1 verbietet Login-Bypass („Inhalte hinter Login werden nicht gecrawlt, Punkt") nicht-verhandelbar.
  • Meta-ToS verbietet automatisierten Zugriff mit eingeloggtem Account; Bann + Rechts-Exposure (DSGVO, §87b UrhG) wären die Folge.

Resultat: Von 63 curated Venues haben nur 7 Events. Alle Clubs/Bars — inkl. Contrast mit fester Dienstagsparty — sind leer. Der Crawl-Weg ist für diese Kategorie strukturell verschlossen.

Lösung

Der Mensch macht den Zugriff, die Maschine die Struktur. Eine Nutzer:in sieht einen Post in ihrem Feed, screenshottet ihn und teilt ihn an Seepuls. mana-llm-Vision extrahiert die Tatsachen (Titel, Datum, Ort), ein Mensch moderiert, dann wird der Event publiziert.

Das ist die Memoro-Logik (Input legitim vom Menschen, KI verarbeitet) — und kategorisch anders als ein Scraper: Seepuls berührt Instagram nie.

Nutzer:in sieht Post/Story  ──screenshot──►  Share-Sheet / Upload / Mail
                                                     │
                                   POST /api/v1/submissions
                                   { image, post_url?, note? }
                                                     │
                              mana-llm Vision  →  { title, starts_at, venue_hint, … }
                                                     │   (best-effort; Bild = Input)
                                       Venue-Matching (Fuzzy → 63 Venues)
                                                     │
                              event_submissions (status: pending)
                                                     │   Bild nach Entscheidung gepurged
                                          Moderations-Queue (Admin)
                                                     │   bestätigen / korrigieren
                                          events (live, „gemeldet · Quelle …")

Die drei nicht-verhandelbaren Regeln

Aus AGGREGATOR_POLICY.md abgeleitet. Test-pflichtig.

  1. Screenshot ist Input, nie Output. Wir extrahieren Tatsachen (§5: „Tatsachen sind frei"). Das Bild, das Flyer-Artwork, der 1:1-Text werden nie re-publiziert. Der öffentliche Event zeigt Titel + Datum + Ort + ≤200 Zeichen + Quell-Link. Der rohe Screenshot wird nach der Moderations-Entscheidung gepurged (imagePurgedAt).
  2. Es ist ein Tipp, kein Claim. Wer screenshottet, ist meist nicht der Betreiber. Also: Moderations-Queue statt Direkt-Publish, Attribution „von Nutzer:in gemeldet · Quelle: instagram.com/…", nie „verifiziert vom Betreiber". Take-Down-Flow (§4b) bleibt voll.
  3. Quelle mit-erfassen. Submit fragt zusätzlich nach dem Post-Link (für §2-Attribution). Fehlt er, liest die Vision den @-Handle und wir verlinken aufs Profil.

DSGVO-Fußnote: Screenshots können Gesichter/Namen enthalten → schmale, zweckgebundene Verarbeitung (Fakten ziehen), Bild danach löschen, Take-Down bleibt. Kein Bild-Archiv.

Foundation-Gaps (was auf der Plattform noch nicht läuft)

# Gap Ebene Status
F1 Service-Key für seepuls bei mana-authfalsche Annahme. Empirisch (2026-05-27): llm.mana.how hat Auth aus (GPU_API_KEY leer) → Vision braucht keinen Key. Der echte Blocker war das Modell: seepuls-Default gpt-4o-mini → 500 (mana-llm bedient ollama/*+google/*). Fix: Default auf google/gemini-2.5-flash (vision-fähig, env-überschreibbar). Hinweis: research.mana.how verlangt sehr wohl einen Key (401) — relevant nur für den Crawl-Pfad, nicht für Submit. Code Modell-Fix; Key nur für Crawl offen
F2 seepuls mana-llm-Client kann nur Text, kein Bild Code dieser Build
F3 event_submissions-Tabelle existiert nicht Code dieser Build
F4 share_receive-Handler leer, accepts: [] Code dieser Build (eigener /submissions-Endpoint)
F5 Vision-Extraction-Service fehlt Code dieser Build
F6 Venue-Matching fehlt Code dieser Build
F7 Moderations-Route fehlt Code dieser Build
F8 Transienter Bild-Speicher + Purge-Cron (mana-media) Code/Ops Phase 2
F9 Submit-UI (Web + PWA share_target + native Share-Extension) Code Phase 3

Graceful Degradation als Design-Prinzip: Die Vision (F1/F2/F5) ist eine Anreicherung, kein Hard-Block. Fehlt der Service-Key, landet die Submission trotzdem in der Queue — mit Notiz + Bild-Referenz, und die Moderator:in füllt die Felder manuell. So funktioniert Submit + Moderation ohne den Plattform-Onboarding-Schritt; die KI macht's nur bequemer.

Phasen

σ-submit-0 — Foundation (dieser Build, Branch feat/screenshot-submit)

  • event_submissions-Schema + Migration (F3)
  • mana-llm extractStructuredFromImage() (F2)
  • services/screenshot-extraction.ts — Vision→Fakten, graceful (F5)
  • services/venue-match.ts — reine Fuzzy-Funktion, unit-getestet (F6)
  • routes/public/submissions.ts — POST-Intake, public, IP-Hash (F4)
  • routes/admin/submissions.ts — list / approve→event / reject (F7)
  • Mount in index.ts, Manifest accepts-Eintrag
  • Unit-Tests + type-check grün

σ-submit-1 — Live schalten (braucht Deploy)

  • Modell-Fix (google/gemini-2.5-flash statt gpt-4o-mini) — kein Service-Key nötig (mana-llm Auth aus). Deploy steht noch aus: Submit- Code lebt auf feat/screenshot-submit, prod fährt alten Code.
  • Live-Vision-Smoke gegen llm.mana.how (nach Deploy) — incl. max_tokens hoch genug für Geminis Thinking + response_format-Verhalten verifizieren.
  • F8: transienter Bild-Speicher via mana-media + Purge-Cron (Bilder >7d pending → löschen)
  • Rate-Limit (Token-Bucket pro IP-Hash, analog Take-Down-TODO)

σ-submit-2 — UI (F9)

  • Web /melden-Seite (Upload + Post-Link + Notiz), server-seitiger POST
  • PWA Web-Share-Target (manifest.webmanifest share_target → /melden)
  • Moderations-UI (Admin) — /admin/moderation, Queue + Approve→Event / Reject. Client-seitig mit mana-auth-Bearer-Token (Admin-Routen sind JWT-gegated), noindex + robots-Disallow. Damit ist der Loop Submit→Moderation→Publish zu.
  • seepuls-native Share-Extension (σ-Native) → POST /submissions
  • Service-Worker für zuverlässige PWA-Installation (Share-POST klappt nach Installation auch ohne)

Datenmodell

event_submissions — eine eingereichte Meldung, vor Moderation.

id                sub_…
status            pending | approved | rejected | spam
channel           screenshot | share-target | federation | manual
note              optionale Nutzer-Notiz
postUrl           optionaler Link auf den Original-Post (§2-Attribution)
imageRef          transiente Bild-Referenz (mana-media-Key o. lokal)
imagePurgedAt     gesetzt, sobald Bild gelöscht
extracted         jsonb { title, starts_at, ends_at, venue_hint, … }
extractionStatus  pending | ok | failed | skipped
extractionError   Fehlertext, falls Vision scheiterte
matchedVenueId    → venues.id (Fuzzy-Treffer, von Mod bestätigt)
venueHint         roher Name/@-Handle aus dem Post
resultEventId     → events.id, gesetzt bei approve
reviewedByUserId  Moderator:in
reviewedAt        Zeitpunkt der Entscheidung
moderatorNote     freier Vermerk
submitterUserId   optional, wenn eingeloggt
clientIpHash      Anti-Spam (sha256(IP), kein Roh-IP) — wie Take-Down
createdAt / updatedAt

Approve → erzeugt eine events-Row (Dedupe via external_id_hash, source_url = postUrl, event_source_id = null = „gemeldet").

API

Public (kein Auth, IP-Hash + Rate-Limit)

  • POST /api/v1/submissions{ image (base64/multipart), post_url?, note?, venue_hint? }{ id, status: 'pending' }. Best-effort-Vision synchron oder als Job.

Admin (JWT, mana-auth)

  • GET /api/v1/admin/submissions?status=pending
  • POST /api/v1/admin/submissions/:id/approve{ venueId, title, starts_at, … (Korrekturen) } → erzeugt Event
  • POST /api/v1/admin/submissions/:id/reject{ reason }

Offene Punkte

  • F1 ist der eine echte Plattform-Blocker für die KI-Stufe — eine einmalige Ops-Aktion (Service-Key bei mana-auth). Bis dahin läuft die Pipeline im Manuell-Modus (graceful degrade).
  • Spam: Start anonym + Moderation + IP-Hash-Rate-Limit; Login (mana-auth) optional fürs „meine Meldungen"-Tracking. Local-First/Login-Optional-Linie.
  • Bild-Speicher: MVP hält imageRef; ob mana-media-Bucket oder kurzer lokaler Pfad entscheidet σ-submit-1. Nie öffentlich servieren.
  • Manifest-accepts: vorerst seepuls-lokaler /submissions-Endpoint. Ob „Screenshot→Event" ein geteilter Typ im @mana/shared-share-protocol wird (andere Apps könnten ihn anbieten), ist eine mana-Repo-Architektur- Frage → vor σ-submit-2 mit mana-architect klären.
  • Venue-Matching-Schwelle: Fuzzy-Match liefert Kandidaten + Score; die finale Zuordnung trifft die Moderator:in, nie der Automat.