1st place now gets N points where N is total tour participants,
not just those who submitted for that specific course. This makes
the leaderboard meaningful even when not everyone has played yet.
Courses without scraped layouts can now be used in tours by entering
a layout name and par manually. The layout is saved to the database
for reuse. All courses are shown in the dropdown, not just those with
existing layouts.
Players can create tours with selected courses/layouts and a date range.
Others join via a 6-character tour code, play the courses, and report
their total strokes. Live leaderboard with points and +/- par display.
Includes: database schema, model, service, routes, views, and styling.
- Switch from Alpine to Debian slim for correct Chromium architecture
(fixes ARM/Apple Silicon support)
- Upgrade Puppeteer 21 to 24, use system Chromium via PUPPETEER_EXECUTABLE_PATH
- Replace removed page.waitForTimeout() with setTimeout
- Set NODE_ENV=production in Dockerfile to prevent pino-pretty import
- Improve error logging with Pino's { err: error } pattern
- Add build: . to docker-compose for local development builds
Replace all console.log/error with Pino logger (info/warn/error/debug/fatal)
for structured JSON logging in production and pretty-print in development.
Remove redundant header dumps and consolidate rate-limit logging.
Add GitHub Actions workflow with release-please for automated semver releases
and Docker build/push to GHCR on new releases.
- Add sticky dark header with nav replacing inline text links
- Introduce CSS custom properties design system (colors, spacing, shadows)
- Use DM Sans + JetBrains Mono fonts replacing Arial
- Modernize tables with uppercase headers and subtle hover states
- Add gradient fill and rounded line to rating chart
- Unify card sections across players and courses pages
- Add backdrop blur to modals
- Clean up inline styles to use CSS variables
PDGA was blocking headless Chrome requests with ECONNRESET errors.
Using puppeteer-extra-plugin-stealth to mask headless browser
fingerprints (navigator.webdriver, chrome.runtime, plugins, etc).
- Add HTMX CDN to layout
- Replace client-side table rendering (displayRatings, displayCourses)
with server-rendered EJS partials via hx-get
- Add server-side course search with debounced hx-trigger
- Lazy-load player history and course layouts via htmx.ajax()
- Render rating chart via htmx:afterSwap with data attributes
- Add partial routes: ratings-table, course-table, player-history,
course-layouts
- Extract CSS into public/css/{shared,players,courses}.css
- Extract JS into public/js/{chart,tooltips,progress,players,courses}.js
- Consolidate 5 duplicated tooltip blocks into setupTooltip() helper
- Add EJS view engine with layout partial and nav partial
- Convert HTML pages to EJS templates (index.ejs, courses.ejs)
- Add /courses route with redirect from /courses.html
- Remove old monolithic HTML files (1478 + 612 lines)
- Calculate and store standard deviation during rating prediction
- Add std_dev column to players database table
- Display standard deviation tooltip on hover over predicted rating
- Show rating range (±std_dev) tooltip on hover over current rating
- Update tooltips dynamically when ratings are refreshed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Allow users to add themselves to the player database through a web form,
eliminating the need for manual pdga-numbers.txt updates. Implement 24-hour
rate limiting on prediction refreshes to prevent abuse while maintaining
reasonable update frequency.
Key changes:
- Add player self-registration with PDGA number lookup and confirmation
- Store predicted ratings in database for persistence across restarts
- Implement 24-hour rate limit on prediction refresh endpoint
- Make database the single source of truth (text file only for initial seed)
- Remove "Scrape All Layouts" bulk operation button
- Update "Load All" to refresh existing players instead of text file
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add request locking system to prevent concurrent scrapes of same course
- Extend HTTP timeouts (10-30 min) for long-running scraping operations
- Add comprehensive logging for layout parsing to debug silent failures
- Implement accordion UI to hide layouts not played within 365 days
- Return 409 status when scrape already in progress for a course
- Add visual indicators for active vs inactive layouts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Features added:
- Course directory scraping with pagination for Swedish courses
- Layout scraping from course detail pages (AJAX tabs)
- Event results scraping to calculate layout ratings
- Mean rating calculation based on players who shot par
- Last played date tracking for each layout (extracted from event pages)
- Multi-event aggregation for accurate ratings across tournaments
Database:
- Added courses table (name, link, city, last_updated)
- Added layouts table (name, par, mean_rating, rating_count, last_played)
- Added database migrations for new columns
- Foreign key relationship between courses and layouts
API endpoints:
- POST /api/scrape-courses - scrape course directory
- POST /api/scrape-layouts/:courseId - scrape layouts and events (combined)
- POST /api/scrape-all-layouts - bulk scrape all courses
- POST /api/scrape-event-results/:courseId - process event results
- GET /api/courses - fetch all courses
- GET /api/layouts/:courseId - fetch layouts for course
UI:
- New courses.html page for course/layout management
- Expandable course rows showing layouts
- Display layout par, mean rating, and last played date
- Layouts sorted by most recently played (newest first)
- Individual and bulk scraping controls
Technical details:
- Date extraction using regex pattern matching from event pages
- Proper detection of division results in details/table.results structure
- Round score and rating extraction from td.round/td.round-rating pairs
- Course location from td.views-field-field-course-location
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Modified calculatePredictedRating function to collect debug logs instead of console output
- Added debug modal UI with ? icon next to predicted ratings
- Updated API responses to include detailed calculation steps
- Fixed compatibility issue with competition property in round data
- Users can now see PDGA rules, filtering, outliers, and weighting details
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Document database-first architecture and automatic startup population
- Add Docker setup instructions and mobile-responsive UI features
- Include API endpoint documentation and technical architecture details
- Explain optimized PDGA scraping strategy and user-controlled refresh system
- Add performance, reliability, and Docker configuration sections
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Read pdga-numbers.txt at server startup and check for missing players
- Automatically scrape and populate any missing players into database
- Maintain respectful 2-second delays between PDGA requests
- Add comprehensive logging for population process
- Include new API endpoints for manual database population and status checking
- Ensure database is fully populated before server accepts requests
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Make database the single source of truth (no automatic cache expiration)
- Page loads now instant: read ratings + predictions directly from DB
- Remove automatic PDGA scraping on page load for performance
- Only scrape PDGA when user explicitly clicks refresh icons
- Add getPlayerDataFromDB() for fast DB-only player loading
- Separate scrapePDGARating() for explicit refresh operations only
- Remove delays from page load path (DB reads don't need rate limiting)
- Skip players not in DB rather than auto-scraping on page load
- User controls data freshness via refresh buttons
Performance: Page loads ~5+ minutes → instant DB reads
User experience: Predictable, fast, user-controlled data freshness
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Use /details page as baseline for official rating rounds (source of truth)
- Only scrape tournaments played AFTER latest official round from main page
- Dramatically reduce PDGA server load: ~50+ tournaments → ~2-5 tournaments
- Add getOptimizedPlayerRounds() for efficient round collection
- Add getNewTournamentRounds() for smart tournament filtering by date
- Reduce scraping delays: 2-3s → 0.5-1s (minimal tournaments to scrape)
- Improve prediction speed: ~5+ minutes → ~30-60 seconds
- Maintain accuracy with official PDGA rating calculation methodology
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Fix parseDate function to handle multi-day tournament formats (e.g., "2-Sep to 3-Sep-2023")
- Integrate PDGA update date simulation using 2nd Tuesday cutoffs for accurate predictions
- Calculate and display predictions automatically from database on page load
- Update rating calculation to use proper PDGA timing windows (12/24 months before update date)
- Improve date parsing regex to correctly extract start dates from tournament ranges
- Include updated player list in pdga-numbers.txt
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Mobile improvements:
- Responsive table layout with hidden columns on mobile
- Touch-friendly buttons and improved spacing
- Consolidated information display for small screens
- Mobile-specific CSS with media queries
PDGA rating simulation:
- Calculate next official PDGA update date (2nd Tuesday of each month)
- Filter tournaments to only include rounds before next update
- Simulate realistic rating predictions based on PDGA schedule
- Account for rolling 12-month window and round expiration
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add subtle "clear cache" link (gear icon) in top-right corner
- Implement cache clearing endpoint with user feedback
- Fix delay logic to skip delays for cached data
- Only apply rate limiting delays when actually scraping fresh data
- Add cache size reporting when clearing cache
- Improve performance for repeat visits with cached data
- Maintain server-friendly rate limiting for fresh scrapes
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Update predicted rating algorithm to match PDGA rating guide
- Focus on tournaments from last 12 months only (improved accuracy)
- Add proper outlier exclusion: rounds >2.5 std dev below average
- Implement double weighting for most recent 25% of rounds (9+ rounds)
- Apply PDGA minimum data requirements (7 rounds for outlier exclusion)
- Improve error handling and rate limiting for tournament scraping
- Add user-friendly error messages for failed calculations
- Reduce tournament scraping from 15 to 8 tournaments to avoid rate limits
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Cache predicted rating calculations (24 hour duration)
- Cache rating history data to avoid repeated scraping
- Implement separate cache keys for different data types
- Add cache hit logging for monitoring effectiveness
- Update PDGA numbers list with additional players
- Significantly improve performance for repeat visitors
- Reduce load on PDGA servers with intelligent caching
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Implement clickable player rows to expand/collapse rating history
- Add rating history scraping from PDGA history pages
- Create custom SVG line charts showing rating progression over time
- Add interactive tooltips with date and rating on hover
- Include visual highlights when hovering over data points
- Implement anti-flicker tooltip system with delayed hiding
- Add large hover areas (12px radius) for better user experience
- Show grid lines, axis labels, and responsive chart scaling
- Cache rating history data to avoid repeated API calls
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Implement Server-Sent Events for live progress updates
- Add animated progress bar with percentage display
- Show real-time status: current player being loaded
- Display player names as they complete loading
- Handle errors gracefully with progress continuation
- Replace HTTP-only approach for better reliability
- Enhanced user experience with visual feedback
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove unnecessary refresh button that only returned cached data
- Change button text from "Calculate Approx Rating" to "Predict Rating"
- Make player names clickable links to their PDGA profile pages
- Add link styling with hover effects
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add Dockerfile with optimized Puppeteer configuration for containers
- Add .dockerignore for efficient builds
- Fix rating change regex to match actual PDGA format (no brackets)
- Update Puppeteer launch args for Docker compatibility
- Use new headless mode to resolve deprecation warning
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Web scraping app for PDGA player ratings
- Current rating extraction from player pages
- Tournament round rating scraping for predictions
- Statistical rating prediction algorithm
- Interactive table with on-demand calculations
- Caching for performance optimization
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>