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>
175 lines
9.2 KiB
Markdown
175 lines
9.2 KiB
Markdown
# 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-auth~~ — **falsche 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.
|