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

10 KiB

name description argument-hint allowed-tools
seepuls-curate-venue 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. <venue-name-oder-url> [--slug <existing-slug>] [--country DE|CH|AT|LI] [--region <slug>]
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 und /seepuls-curate-events. Cross-Repo- Best-Practices: ../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:
    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:
    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:
    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:
    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-venuesapps/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