Automation roadmap¶
Superseded (2026-07-07): this 2026-05-31 spec was implemented and then replaced by the living improvement plan — see its wave history for what actually shipped. Kept for context; don't work from it.
Goal: make report production more systematic — push the mechanical work into scripts so the LLM is used only for judgment, and approval/tool-call churn drops. Target outcome: one command does the data-gathering 80%, the LLM writes the narrative 20% and reviews.
What's mechanical vs. judgment¶
From the Crestolita + Broken Hand session, the work split roughly:
| Mechanical (scriptable — no LLM) | Judgment (needs LLM) |
|---|---|
| Login-check all 3 sources, confirm usernames | Deciding a combo is a real pairing vs. coincidence |
| Resolve cross-site IDs (peak_db ↔ 14ers ↔ LoJ ↔ peakbagger) | Catching route errors (south ridge ≠ standard route) |
| Sweep TR lists from all 3 sources | Synthesizing route narrative from multiple TRs |
| Download every GPX (LoJ + 14ers lib + peakbagger) | Picking the recommended route + caveats |
| Compute combo distance/gain from exact-combo GPX | Cluster/combo framing, "what's left for this climber" |
| Query 14ers cell DB | Hazard/season judgment calls |
| Get drive time from home → TH (Maps) | Final prose + TL;DR |
| Build waypoint GPX, upload CalTopo, render PNG | Review of the auto-assembled facts |
| Emit a filled-in report skeleton |
Most tool-call/approval volume is in the mechanical column.
Proposed: scripts/research_peak.py¶
One command that does the mechanical 80% and emits a data-filled skeleton.
scripts/research_peak.py --peaks <id|slug>[,<id>...] [--climber kyle] [--flavor single|day|multi]
Steps it automates¶
- Login verification — hit each of the 3 sources, confirm the climber's username appears (wait past peakbagger Cloudflare). Hard-fail if any source is logged-out (per source requirements).
- ID resolution — from peak_db, get summit coords/elev/class/range/ranked/climbed; follow LoJ → peakbagger cross-links; resolve 14ers peak IDs.
- Cluster analysis — nearby unclimbed ranked 13ers+ within 8 mi, flagged same-drainage vs. different-drive.
- TR sweep — list every TR per source with author/date/"additional peaks" (combo signal).
- GPX sweep — download all tracks from all 3 sources to
gpx/<slug>/, source-suffixed filenames. - Stats — compute distance/gain from the exact-combo (or standard-route) GPX tracks.
- Cell DB — query 14ers community reception for each peak + primary TH.
- Drive time — Maps directions from the climber's home address to the primary TH.
- Map build — generate waypoint GPX, upload to CalTopo (
--no-dedupe, source colors), capture map ID; render the PNG. - Emit skeleton — write
docs/peaks/<slug>.mdpre-filled with the report template: Quick Stats, drive row, weather link, map embed, TR tables (all 3 sources), GPX count, cell section, sources-checked footer — leaving narrative sections (recommended route, why-combined, conditions judgment, TL;DR) as clearly-marked<!-- TODO: narrative -->blocks.
Then the LLM¶
- Reads the skeleton + the TR text it flags as route-relevant
- Writes/edits only the narrative + judgment sections
- Verifies the auto-assembled facts (catches route errors)
- Commits + pushes
Output contract¶
- Idempotent: re-running refreshes data without clobbering hand-written narrative (narrative lives between sentinel markers the script won't overwrite).
- Prints a summary: sources confirmed, TR counts, GPX counts, map ID, PNG path, any gaps.
Companion scripts (smaller, independently useful)¶
| Script | Does | Removes |
|---|---|---|
scripts/check_sources_login.py |
Confirms logged-in on all 3 sites; prints usernames | The manual login-check dance every session |
scripts/sweep_gpx.py <peakids> |
Downloads all GPX from all 3 sources for given peaks | The bespoke per-session GPX fetch code |
scripts/combo_stats.py <gpx...> |
Distance/gain/elev from track(s) | Inline python each time |
scripts/drive_time.py <lat,lon> [--climber] |
Maps drive time + directions URL from home | Manual Maps navigation |
scripts/narrow_down.py --criteria ... |
Runs a saved narrow-down query → docs/lists/ |
LLM doing peak_db filtering by hand |
scripts/scrape_14ers_checklist.py <url> |
Friend's climbed list from a 14ers checklist URL | (enables multi-climber) |
research_peak.py orchestrates the first four.
Auth approach (decided 2026-06-02)¶
The headless scrapers (check_sources_login, sweep_gpx, research_peak) need the 3 sources logged in. Do not require a second, separate login. check_sources_login.py currently uses its own Playwright profile (a one-time --login), but the intended direction is to reuse an already-authenticated session — e.g. point the scripts at Kyle's existing Chrome/Playwright profile, or load an exported storage-state file — so there's zero extra login. Claude never enters credentials; the human establishes the session once in their normal browser and the scripts borrow it. Design sweep_gpx/research_peak against that before leaning on the separate-profile path.
Map QA tests (also reduces redo loops)¶
Pre-commit / CI checks on generated PNGs so distorted maps never publish (the recurring "redo the map" problem):
- Aspect/extent sanity — objective peak bbox occupies a reasonable fraction of the frame (not a dot, not clipped)
- Track layer present — at least one rendered LineString (catches the "only dots" failure)
- Non-blank tiles — basemap actually fetched (not the beige fallback)
- Objective centered — all peak markers within frame
- Optional: visual diff vs. a stored baseline per peak
Pair with the pending multi-color PNG tracks (color by source like the CalTopo map) — both live in make_overview_map.py.
Sequencing (when we build it)¶
check_sources_login.py+combo_stats.py+drive_time.py— small, high-frequency, low-risksweep_gpx.py— consolidates the most repetitive scrapingresearch_peak.py— orchestrate the above + skeleton emit- Map QA tests + multi-color PNG
narrow_down.py, thenscrape_14ers_checklist.py(multi-climber)
Each step independently cuts tool-call volume; research_peak.py is where the big LLM-usage/approval savings land.
Last updated: 2026-05-31