Queue-Times keeps reporting yesterday's last wait with isOpen=true overnight,
so the per-ride open check wasn't enough — the sampler was recording phantom
"open" samples between close and the next morning's first refresh, padding
both wait-time averages and uptime% with stale data.
Add isWithinOperatingWindow gate (same check the /rides route uses) so the
sampler only runs during the park's actual hours plus the 1-hour closing
buffer. Includes a one-off wipe script for the accumulated bad data.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds a cron-driven sampler that snapshots Queue-Times waits and Six Flags
Fast Lane data every 5 minutes into a new ride_wait_samples table, and a
clickable per-ride detail page at /park/[id]/ride/[slug] with Today / 7d /
30d Recharts views plus a 30d uptime pill. Rides are keyed by Queue-Times'
stable qt_ride_id so renames don't fragment history. Samples store
pre-bucketed local_date and local_time in the park's IANA timezone so
aggregations are pure SQL and DST-safe.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Join Fast Lane waits from the Six Flags /wait-times endpoint onto
Queue-Times rides by name. A new toggle on the live ride panel swaps
the shown wait to the Fast Lane number; regular waits and open status
still come from Queue-Times.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Restores the startup scrape removed in deb8e41, gated on
getParkDayCount() < 50 so warm restarts don't hammer the API.
Cold containers (e.g. after the volume mount fix) populate
immediately instead of waiting up to 24h for tier-4 cron.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Update package names, Docker image tags, CI/CD workflow, and
documentation to reflect the public brand name. References to
the actual Six Flags theme park chain/API are intentionally kept.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
getTodayLocal() relied on system clock hours, which broke in the web
container (TZ defaulting to UTC) — the day flipped at 11 PM EDT (3 AM
UTC) instead of 3 AM Eastern. Now uses Intl.DateTimeFormat with an
explicit America/New_York timezone. Also replaced all toISOString()
date formatting with local-component helpers to avoid UTC conversion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fires scrapeToday() then scrapeFullYear() as a background task on
startup so fresh deploys have data immediately instead of waiting
for the first cron tick. Staleness check makes warm restarts a no-op.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Server components now fetch composed data from the backend instead of
directly querying SQLite and external APIs. Removes better-sqlite3
dependency from the frontend entirely.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Standalone Node.js backend that owns the SQLite database and serves
composed data via REST endpoints. Replaces the shell-scheduled scraper
with in-process node-cron tiered scheduling.
Backend structure:
- Hono HTTP server on port 3001 with CORS and request logging
- Singleton SQLite connection with WAL mode
- In-memory TTL cache for Queue-Times and fetchToday responses
- Comparison check on fetchToday (read-before-write, only upserts on change)
API endpoints:
- GET /api/calendar/week — week schedule + live ride counts for all parks
- GET /api/calendar/:parkId/month — month calendar for one park
- GET /api/parks — park list with metadata
- GET /api/parks/:id — single park detail
- GET /api/parks/:id/rides — live rides with Queue-Times/schedule fallback
- GET /api/status — health check, scrape stats
- POST /api/scrape/trigger — manual scrape (scope: today/month/upcoming/full)
Scheduler tiers:
- Tier 1: today — hourly (Mar-Dec)
- Tier 2: current month — every 6 hours
- Tier 3: upcoming — twice daily (3 AM + 3 PM)
- Tier 4: full year — daily at 3 AM
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>