seepuls/.claude/skills/seepuls-curate-venue.md
Till JS 6125df9bdf
Some checks are pending
CI / validate (push) Waiting to run
α-6: Curator-Pipeline für Venues (YAML-as-SOT)
Drei Skills + Importer + Migration der 5 existierenden Venues.

Skills (alle repo-lokal in .claude/skills/):
- seepuls-curate-venue: 5-Stufen-Pipeline (Plan → Recherche → Design
  → Validate → Publish), Reviewer-Stops Pflicht, Nominatim-Geocoding
  mit Politeness, YAML als Output. Bulk-Mode für >10 Venues.
- seepuls-validate-venue: read-only, 8 Checks (slug-unique,
  country-enum, region-enum, coords-plausibel, description-länge,
  robots-allow, source-coverage, enum-fit).
- seepuls-curate-events: zwei Modi (Crawl via admin-API mit
  Service-Key, oder Manual aus User-Input). DB-as-SOT (nicht YAML),
  weil Events kurzlebig sind.

Importer:
- apps/api/src/jobs/import-venues.ts: liest curated/venues/*.yml,
  upsert in seepuls.venues, idempotent (zweiter Run = 0 inserted,
  N updated). Zod-Validation der YAML-Form.
- pnpm db:import-venues script.
- Pattern an zitare-Importer orientiert.

SOT-Migration der 5 bestehenden Venues:
- apps/api/src/data/curated/venues/{museum-rosenegg,
  seemuseum-kreuzlingen, bodensee-planetarium-kreuzlingen,
  rosgartenmuseum, archaeologisches-landesmuseum-konstanz}.yml
- README.md mit Format-Doku.

Doku:
- seepuls/docs/CURATOR.md: Workflow-Übersicht, verweist auf
  mana/docs/CONTENT_PIPELINES.md als Cross-Repo-Pattern.

Lokal validiert (gegen Postgres :5441):
- 1. Lauf: 5 inserted ✓
- 2. Lauf: 0 inserted, 5 updated ✓ (idempotent)
- type-check + 43/43 Tests grün.

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

257 lines
10 KiB
Markdown

---
name: seepuls-curate-venue
description: Legt eine Venue im Seepuls-Korpus an oder reichert sie an — Recherche → strukturierte Felder → Geocoding → Validate → YAML in apps/api/src/data/curated/venues/<slug>.yml → Import. Mit Reviewer-Stops, Quellen-Permalinks, Compliance-Veto.
argument-hint: '<venue-name-oder-url> [--slug <existing-slug>] [--country DE|CH|AT|LI] [--region <slug>]'
allowed-tools:
- Read
- Write
- Edit
- WebSearch
- WebFetch
- Bash(ls *)
- Bash(mkdir *)
- Bash(grep *)
- Bash(cat *)
- Bash(curl *)
- Bash(jq *)
- Bash(docker exec *)
- Bash(pnpm *)
- AskUserQuestion
---
# `/seepuls-curate-venue` — Curator-Workflow für eine Venue
Du legst eine Venue (Veranstaltungsort) im Seepuls-Korpus an oder
reicherst sie an. **YAML in `apps/api/src/data/curated/venues/<slug>.yml`
ist die SOT** — DB-Eintrag entsteht durch `pnpm db:import-venues`.
Schwester-Skill von [`/seepuls-validate-venue`](./seepuls-validate-venue.md)
und [`/seepuls-curate-events`](./seepuls-curate-events.md). Cross-Repo-
Best-Practices: [`../mana/docs/CONTENT_PIPELINES.md`](../../mana/docs/CONTENT_PIPELINES.md).
## Wann benutzen
- User will eine **neue Venue** anlegen (Museum, Club, Kulturhaus,
Bar, Verein, Festival-Spielstätte) für die Bodensee-Region (DE+CH+AT+LI).
- User will eine **bestehende Venue** korrigieren (Adresse, Beschreibung,
Logo, Social).
- User hat eine **konkrete URL** und will daraus einen Eintrag.
Für die **automatische Crawl-Pipeline** (mana-research → mana-llm →
Reviewer-Stop) ist `POST /api/v1/admin/venues/from-url` der Pfad — er
schreibt direkt in DB mit `crawl_status='pending-review'`. Dieser Skill
hier ist der **manuelle Pfad** mit YAML-as-SOT, idempotent re-importierbar.
## Pflicht-Lektüre vor erstem Run
- `seepuls/STATUS.md` — Phasenstand der App.
- `seepuls/apps/api/src/data/curated/venues/README.md` — YAML-Format.
- `seepuls/docs/CURATOR.md` — Workflow-Übersicht.
- `mana/docs/AGGREGATOR_POLICY.md` — Hard-Rules (robots.txt, Attribution,
≤ 200 Zeichen Description).
- `mana/docs/CONTENT_PIPELINES.md` — generelles Pipeline-Pattern.
## Inputs
1. **Venue-Name oder Website-URL** (Pflicht) — z.B. `"Museum Rosenegg
Kreuzlingen"` oder `"https://museumrosenegg.ch"`.
2. **`--slug <existing>`** (optional) — bestehenden Eintrag anreichern.
3. **`--country DE|CH|AT|LI`** (optional) — wenn aus dem Namen nicht
eindeutig. AskUserQuestion bei mehrdeutigen Treffern (z.B. „Stadthaus" —
welches?).
4. **`--region <slug>`** (optional, default `bodensee`) — eine der
curated Regions (`bodensee`/`hegau`/`thurgau-west`/`oberschwaben`).
5. **`--no-import`** (optional) — Stage-5 überspringen, nur YAML schreiben
(für dry-run / Review).
## Workspace
```
~/Documents/seepuls-drafts/<slug>/
├─ plan.md Stufe-1-Output: Name, Country/Region, Source-Kandidaten
├─ research/
│ ├─ sources.md Quellen mit Permalinks + Zugriffsdatum
│ └─ notes.md Roh-Notizen aus WebFetch
├─ design/
│ └─ venue.yml Stufe-3-Vorschlag (gleiche Form wie curated/venues/*.yml)
├─ validate/
│ └─ report.md Output von /seepuls-validate-venue
└─ publish/
└─ import.log Output von `pnpm db:import-venues`
```
`<slug>` = kebab-case aus Name. Drafts bleiben liegen als Audit-Trail.
## Pipeline (5 Stufen)
### Stufe 1 — Plan
1. Workspace anlegen:
`mkdir -p ~/Documents/seepuls-drafts/<slug>/{research,design,validate,publish}`.
2. **Slug bestimmen**: kebab-case aus Name, ohne Stop-Wörter
(„Museum X" → `museum-x`). Bei Konflikt mit existing: `<slug>-2` oder
ortsspezifischer (`museum-x-konstanz`).
3. **Existenz-Check**:
```bash
ls seepuls/apps/api/src/data/curated/venues/<slug>.yml 2>/dev/null
docker exec seepuls-postgres psql -U seepuls -d seepuls -tAc \
"SELECT slug FROM seepuls.venues WHERE slug='<slug>'"
```
Wenn YAML existiert: User fragen ob ergänzen oder neuer Slug.
4. `plan.md` mit:
- Venue-Name (1 Zeile)
- Country + Region (Begründung warum diese Region)
- Source-URL-Kandidaten (offizielle Website, ggf. Wikipedia)
- Streitfälle (z.B. „mehrere gleichnamige Orte" — welcher?)
5. **Reviewer-Stop**: User sieht Plan, sagt go oder korrigiert Slug/Region.
### Stufe 2 — Recherche
1. **WebFetch der offiziellen Website** — Name, Adresse, Beschreibung,
Logo-URL, Social-URLs, Öffnungszeiten-Hinweis.
2. **Wikipedia-Cross-Check** (optional) — bei historischen oder
prominenten Orten. Permalink mit `oldid`.
3. **Adresse präzise erfassen** — Straße + Hausnummer + PLZ + Ort.
4. `research/sources.md`: nummerierte Quelle, jede mit:
- Titel + URL
- Zugriffsdatum (ISO `YYYY-MM-DD`)
- Permalink wenn möglich (Wikipedia `oldid`)
5. `research/notes.md`: Roh-Texte, Adress-Varianten, evtl. abweichende
Schreibweisen. Streitfälle (z.B. „Website sagt Strasse, Wikipedia
sagt Straße") markieren.
### Stufe 3 — Design + Geocoding
1. **Nominatim Forward-Geocode** für die Adresse:
```bash
UA='seepuls/0.0.1 (+https://seepuls.mana.how; kontakt@mana.how)'
curl -sf -H "User-Agent: $UA" --max-time 10 \
--data-urlencode 'q=Straße Nr, PLZ Ort' \
--data 'format=json&limit=2&countrycodes=de,ch,at,li' \
-G 'https://nominatim.openstreetmap.org/search' \
| jq -r '.[] | "\(.lat),\(.lon) \(.display_name)"'
```
**Politeness Pflicht**: User-Agent mit Kontakt-Mail, ≥ 1.1 s zwischen
Requests. Bei mehreren Treffern: AskUserQuestion mit beiden Optionen.
2. **Plausibilität prüfen**: lat ∈ [47.4, 47.9], lng ∈ [8.5, 9.7]
(Bodensee-Bounding-Box). Außerhalb → Plan-Stop, möglich falsche
Region.
3. `design/venue.yml` schreiben in der Form aus
`curated/venues/README.md`. Felder:
- `slug` (aus Plan)
- `name` (offizielle Schreibweise)
- `countryCode` (ISO DE/CH/AT/LI)
- `regionSlug` (curated; default `bodensee`)
- `city`, `address`
- `coordinates: {lat, lng}` aus Nominatim
- `description` (**≤ 200 Zeichen**, in Originalsprache,
**kein LLM-Re-Phrasing** — direkte Übernahme oder leichte Kürzung)
- `websiteUrl`, `logoUrl` (oder null), `socialUrls` (Map)
- `source`: url + domain + accessedOn + refs
- `crawlStatus: manual-entry`
- `attributionRequired: true`
4. **Reviewer-Stop**: User sieht die YAML, sagt go oder korrigiert.
### Stufe 4 — Validate
Skill `/seepuls-validate-venue <slug>` aufrufen. Read-only, schreibt
`validate/report.md` mit 7 Checks (siehe Schwester-Skill).
Bei roten Items: **nicht eigenmächtig fixen** — User entscheidet welche
Stufe nochmal durch.
### Stufe 5 — Publish (YAML in SOT + Import)
1. **YAML kopieren** nach `seepuls/apps/api/src/data/curated/venues/<slug>.yml`.
2. **Compliance-Veto** via Sub-Agent:
```
Agent(subagent_type="mana-compliance", prompt="<diff der venue.yml>")
```
- ✅ → weiter.
- ⚠️ → User informieren.
- 🛑 → **abbrechen**. YAML aus curated/ entfernen.
3. **Import**:
```bash
cd seepuls && pnpm --filter ./apps/api db:import-venues
```
Output in `publish/import.log`. Idempotent (zweiter Run = 0 inserted,
1 updated).
4. **Cloudflare-Tunnel-Pause** unnötig — die public Read-API zeigt die
Venue sofort (keine Build-Cache-Invalidation nötig, Astro-Web ist SSR).
5. **Smoke**:
```bash
curl --resolve seepuls-api.mana.how:443:188.114.96.12 -s \
'https://seepuls-api.mana.how/api/v1/venues/<slug>' | jq
```
## Anti-Halluzinations-Regeln
- **Niemals Adresse oder Koordinaten raten.** Wenn Nominatim nichts
findet: stoppen, User fragen, Adresse manuell verifizieren.
- **Niemals Description erfinden oder LLM-paraphrasieren.** Direktes
Zitat aus offizieller Quelle, ≤ 200 Zeichen. Wenn länger: kürzen
am Satz-Ende, nicht umformulieren.
- **Niemals geratene Social-URLs.** Wenn die Website keinen Insta-Link
hat: `socialUrls: {}`, nicht raten.
- **Niemals `logoUrl` mit Wikipedia-Logo befüllen** — nur das offizielle
Logo von der Venue-Website (Hot-Link gemäß AGGREGATOR_POLICY §3),
oder `null`.
## Bulk-Mode (>10 Venues auf einmal)
Eine Curated-Liste (z.B. „alle Konstanzer Museen", „alle Konzert-Lokale
Kreuzlingen") als Master-Quelle. Dann:
1. **Curated-Listings-Site als Quelle** (z.B. konstanz.de/tourismus/museen,
thurgau-bodensee.ch/freizeit/museen).
2. **Sampling-Reviewer-Stop**: 5 zufällige Venues + Streitfall-Liste.
3. **Skript-getrieben**: Python-Heredoc liest CSV (slug, name, address,
website) und produziert N YAMLs. Geocoding-Loop mit Politeness-Delay.
4. **Atomic-Import**: `pnpm db:import-venues` macht alle in einer
Transaction.
## Reviewer-Stops sind Pflicht
Nach Stufe 1 (Plan) und Stufe 3 (Design + Geocoding), **vor** Stufe 5
(Publish). Auch wenn der User „mach einfach" gesagt hat. Stops sind kurz
(Markdown-Preview + ja/nein), retten den Korpus vor falschen Adressen.
## Was dieser Skill NICHT macht
- **Events anlegen** — `/seepuls-curate-events` ist Schwester-Skill.
- **Crawl-Pipeline triggern** — der admin-Endpoint
`/api/v1/admin/venues/from-url` ist der automatische Pfad
(Service-Key Pflicht).
- **Live-DB direkt schreiben** — YAML ist SOT, Import macht den Rest.
- **DSGVO-Sicht** — Venue-Daten sind public-aggregierte Infos, keine
PII. Personen-Daten (Veranstalter:innen-Mails etc.) gehören nicht
ins YAML.
## Dependencies
- `pnpm db:import-venues` — `apps/api/src/jobs/import-venues.ts`
- `docker exec seepuls-postgres psql` — Slug-Existenz-Checks
- Nominatim-API (OSM, gratis, ≥ 1 req/s Politeness)
- Sub-Agent `mana-compliance` für Veto
## Offene Punkte
- **Logo-Hot-Link-Verifikation.** Manche Websites haben
Hot-Link-Schutz; Logo lädt im Browser, aber nicht im img-Tag eines
anderen Hosts. Beim Smoke-Check (Stufe 5d) im Browser prüfen oder
fallback `logoUrl: null`.
- **Slug-Konsistenz mit ECOSYSTEM-City-Tags.** Wenn eine Stadt mehrere
Venues hat (Konstanz: Rosgarten + ALM): keine Konvention für
`<venue>-<stadt>` vs nur `<venue>`. Aktuell: Eindeutigkeit
reicht.
- **Wikidata-Anbindung.** Zitare nutzt Wikidata für Author-Enrich; bei
Venues gibt es Wikidata-Properties für viele Museen. Optional zur
V2 dazu.
## Referenzen
- Format: `apps/api/src/data/curated/venues/README.md`
- Workflow-Übersicht: `seepuls/docs/CURATOR.md`
- Schwester-Skill: `/seepuls-validate-venue`, `/seepuls-curate-events`
- Cross-Repo-Best-Practices: `../mana/docs/CONTENT_PIPELINES.md`
- Aggregator-Policy: `../mana/docs/AGGREGATOR_POLICY.md`