| name | photo-resolver |
| description | Backfill missing event and venue photos in 206.events. Reads the photoGaps work queue from build-errors.json and, in bounded batches, fills venue photos via source-YAML PRs and event photos via the event-uncertainty-cache (imageUrl), or marks them unresolvable when no photo exists. |
206.events Photo Resolver
Backfill photos for events and venues that don't have one. The data model
stores image URLs only (never image bytes), in the imageUrl field.
There are two kinds of gap, fixed two different ways:
| Gap | Where it lives | How to fix |
|---|
| Venue photo | imageUrl: in the source YAML (ripper / external / recurring) | Open a PR adding imageUrl: (like geo-resolver edits venue coords) |
| Event photo | event-uncertainty-cache.json keyed source:eventId | Write an imageUrl resolution via uncertainty-cache.py resolve --image-url (committed file) |
Both are non-fatal todo queues, like geo and event-uncertainty. The queue
self-limits over time: once a photo is found (or confirmed unavailable and
marked unresolvable), the item drops off.
Workflow
1. Check the queue and baseline coverage
python3 skills/photo-resolver/scripts/photo-gaps.py stats
Note the coverage % — this is your baseline to beat. Then list the gaps:
python3 skills/photo-resolver/scripts/photo-gaps.py venues --limit 25
python3 skills/photo-resolver/scripts/photo-gaps.py events --limit 25
(Pass --url output/build-errors.json to inspect a local build, or
--url https://206.events/preview/<PR>/build-errors.json for a PR preview.)
2. Process a bounded batch
Do not try to drain the whole queue in one run. Pick a batch — e.g. the
first 25 items — and resolve those. The queue is drained across many runs,
exactly like the geo and uncertainty backlogs. Prefer venues first (one PR
covers many future events at that venue) and high-traffic sources.
3a. Resolve a VENUE photo (source YAML → PR)
- Find a representative, stable photo URL for the venue (the venue's own site,
an official social profile image, or a Wikimedia/again-stable host). Prefer
a direct
https://…/image.jpg URL. Link only — never download bytes.
- Add
imageUrl: "<url>" to the source's YAML next to its geo: block:
- ripper:
sources/<name>/ripper.yaml (ripper-level, or per-calendar for a
multi-branch source)
- external:
sources/external/<name>.yaml
- recurring:
sources/recurring/<name>.yaml
- Open a PR (these are content changes — auto-merge-eligible per AGENTS.md).
3b. Resolve an EVENT photo (uncertainty cache)
For each event gap, fetch the source page (url from the queue), find a
representative image link, and write it into the cache:
python3 skills/event-uncertainty-resolver/scripts/uncertainty-cache.py resolve \
--key "<source>:<eventId>" --image-url "https://example.com/event.jpg" \
--evidence "<source page url>"
If the source genuinely has no image, mark it unresolvable so it drops off
the queue and isn't re-investigated:
python3 skills/event-uncertainty-resolver/scripts/uncertainty-cache.py resolve \
--key "<source>:<eventId>" --unresolvable --reason "no image on source page"
The build's applyImageBackfill pass applies these imageUrl resolutions on
the next build (see docs/event-uncertainty.md and docs/photos.md).
3c. Retire a whole photo-less source (skipEventPhotos → PR)
When a source has a venue imageUrl but its event pages carry no
harvestable per-event image (only a generic site/category logo, e.g.
sources/seattle_makers), don't mark its events unresolvable one at a time —
every new event the venue publishes would re-enter the queue. Instead retire the
whole source's event-photo backfill in one PR:
- Confirm the pattern by fetching two or three distinct event pages for the
source and verifying none carries an event-specific image (just the venue's
logo/banner).
- Add
skipEventPhotos: true at the ripper level of sources/<name>/ripper.yaml
(alongside the venue imageUrl:), with a one-line comment noting what you
verified.
- Open a PR (content change — auto-merge-eligible per AGENTS.md).
The events keep displaying the venue photo; they just stop appearing in
eventGaps. Only use this when per-event photos genuinely don't exist — if the
ripper could be enhanced to extract a real per-event image, prefer that.
See the skipEventPhotos section in docs/photos.md.
4. Re-trigger the build and report
After venue PRs merge / event resolutions are written, re-trigger the build so
the changes take effect, then re-check coverage:
python3 skills/photo-resolver/scripts/photo-gaps.py stats
Summarize:
- Photo coverage (events + venues) before vs after
- How many venue PRs opened, how many event resolutions written
- How many marked unresolvable
- Remaining gap count
Notes
- Links only. Never base64/inline image data into the cache, YAML, or ICS.
- Stable URLs. Avoid signed/expiring CDN URLs where possible; prefer the
venue's canonical image.
- Event resolutions edit the committed
event-uncertainty-cache.json;
commit them in the same PR (CI reads the committed file — no S3). See
docs/github-native-caches.md.
- This skill is the handler invoked from the build-report skill's Photo
Coverage Check (step 5.6) and complements
source-from-event (poster images).