seepuls/.claude/skills/seepuls-validate-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

168 lines
5.2 KiB
Markdown

---
name: seepuls-validate-venue
description: Validiert ein seepuls-curate-venue-Draft (slug-unique, country-enum, region-enum, coords-plausibel, description-länge, robots-allow, source-coverage) bevor er ins curated/venues/ landet. Read-only, produziert validate/report.md.
argument-hint: '<venue-slug>'
allowed-tools:
- Read
- Write
- Edit
- Bash(ls *)
- Bash(grep *)
- Bash(cat *)
- Bash(curl *)
- Bash(jq *)
- Bash(docker exec *)
---
# `/seepuls-validate-venue` — QA für ein Draft-Venue
Vorletzte Stufe der `/seepuls-curate-venue`-Pipeline. Liest
`~/Documents/seepuls-drafts/<slug>/design/venue.yml` und produziert
`<slug>/validate/report.md` mit allen Findings.
Schwester-Skill von `/cards-validate-deck` (Cardecky-Spaced-Repetition)
und der inline-Validate-Stage in `/zitare-curate-quote`.
## Wann benutzen
- Direkt nach Stage 3 von `/seepuls-curate-venue` (venue.yml steht).
- Vor dem Publish (Stage 5). Bei rotem Report: **nicht eigenmächtig
fixen**, sondern dem User die Findings melden + welche Stufe nochmal.
## Inputs
1. **Draft-Slug** (Pflicht) — z.B. `museum-rosenegg`.
## Checks
### 1. slug-unique
Slug ist neu im Korpus:
```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>'"
```
Treffer → **rot**. Vorschlag: `<slug>-<stadt>` oder `<slug>-2`.
**Exception:** Wenn der User explizit ergänzen will (`--slug
<existing>` in curate-venue), nur Warning, kein rot.
### 2. country-enum
`countryCode ∈ {DE, CH, AT, LI}`. Treffer-Miss → **rot**.
### 3. region-enum
`regionSlug ∈ {bodensee, hegau, thurgau-west, oberschwaben}` ODER
fehlt komplett (null). Andere Werte → **rot** (Region erst seed-en).
### 4. coords-plausibel
`coordinates.lat ∈ [47.4, 47.9]` UND `coordinates.lng ∈ [8.5, 9.7]`
(Bodensee-Bounding-Box). Außerhalb → **rot**, vermutlich falscher
Geocode oder falsche Region.
Plus: Cross-Check `countryCode`:
- DE-Venue mit lat < 47.55 (Schweizer Seeufer) Warning.
- CH-Venue mit lat > 47.66 (deutsches Ufer) → Warning.
### 5. description-länge
`description.length` ∈ [10, 220]:
- < 10 **rot**, zu kurz für sinnvolle Anzeige.
- > 220 → **rot**, AGGREGATOR_POLICY §2 verlangt ≤ 200 (mit kleinem
Puffer auf 220 für UTF-8-Zähl-Diff).
### 6. robots-allow
`source.url`-Domain erlaubt das Crawlen seepuls-Bot:
```bash
curl -sf -H "User-Agent: seepuls/0.0.1 (+https://seepuls.mana.how; kontakt@mana.how)" \
--max-time 5 "https://<domain>/robots.txt" | head -40
```
Manuelles Checken: gibt es `Disallow: /` für `*` oder für `seepuls`?
**rot**, Eintrag nicht anlegen. Domain in `blocked_domains` tragen.
Wenn `robots.txt` 404 oder timeout: **warnung** (default-allow), aber
flaggen.
### 7. source-coverage
`source.refs[]` enthält ≥ 1 Eintrag, jeder ≥ 3 Zeichen. Leerer Array
**rot**, Curator hat keine Quelle dokumentiert.
`source.url` und `source.domain` müssen übereinstimmen:
```bash
echo '<source.url>' | sed -E 's|^https?://([^/]+).*|\1|' | tr 'A-Z' 'a-z'
```
Sollte gleich `source.domain` sein (modulo www-Stripping). Sonst **rot**.
### 8. enum-fit (`crawlStatus`)
`crawlStatus ∈ {manual-entry, pending-review, claimed}`. Andere Werte
**rot**, sonst greift der Importer (oder spätere Constraints).
## Output
`~/Documents/seepuls-drafts/<slug>/validate/report.md`:
```
# Validate-Report — <slug>
Draft-Path: <…>
Geprüft: 2026-MM-DD HH:MM
## Findings
✓ slug-unique: neu (kein YAML, keine DB-Row)
✓ country-enum: CH
✓ region-enum: bodensee
✓ coords-plausibel: 47.6442, 9.1720 — im Bodensee-BBOX
✓ description-länge: 137 Zeichen
✓ robots-allow: robots.txt erlaubt /
✓ source-coverage: 2 refs
✓ enum-fit: crawlStatus=manual-entry ok
## Empfehlung
Grün — bereit für Publish (Stage 5).
```
Bei roten Findings:
```
✘ coords-plausibel: 47.123, 9.456 — Lat zu südlich, außerhalb Bodensee-BBOX
✘ description-länge: 245 Zeichen (Limit 220)
## Empfehlung
Stop vor Publish. Nochmal Stufe 3:
- Adresse re-geocoden — vielleicht falsche Stadt erwischt.
- Description am Satz-Ende kürzen, nicht umformulieren.
```
## Was dieser Skill NICHT macht
- **Eigenmächtig fixen.** Findings → User entscheidet.
- **YAML schreiben oder editieren.** Read-only.
- **DB-State ändern.** Read-only.
- **Robots-Tiefen-Parse.** Heuristik: `Disallow: /` für `*` oder
`seepuls`. Komplexe Path-Patterns → User soll manuell checken.
## Offene Punkte
- **Robots-Lookup könnte in `src/crawl/robots.ts` der API wiederverwendet
werden.** Aktuell ist der Validator-Skill bash-only — der echte Parser
(mit Cache + Path-Match) lebt in Bun-Code. Bei systematischen
False-Positives: Validator als `bun script` mit Import aus
`apps/api/src/crawl/robots.ts` umbauen.
- **Geographische Region-Inference.** Bei klar falschem Region-Slug
(z.B. „Friedrichshafen" mit `regionSlug: thurgau-west`) wäre eine
Vorschlag-Heuristik nett. Heute: Warning, kein Auto-Fix.
## Referenzen
- Schwester-Skill: `/seepuls-curate-venue`
- Cross-Repo-Pattern: `/cards-validate-deck`
- Aggregator-Policy: `../mana/docs/AGGREGATOR_POLICY.md`