{"id":42332600,"url":"https://github.com/fwextensions/sf-pools","last_synced_at":"2026-01-27T13:51:38.237Z","repository":{"id":333049010,"uuid":"1060859844","full_name":"fwextensions/sf-pools","owner":"fwextensions","description":null,"archived":false,"fork":false,"pushed_at":"2026-01-17T03:09:01.000Z","size":345,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-17T14:36:45.190Z","etag":null,"topics":["ai","built-with-ai"],"latest_commit_sha":null,"homepage":"https://sf-pools.vercel.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fwextensions.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-20T18:33:06.000Z","updated_at":"2026-01-17T03:09:05.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/fwextensions/sf-pools","commit_stats":null,"previous_names":["fwextensions/sf-pools"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/fwextensions/sf-pools","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fwextensions%2Fsf-pools","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fwextensions%2Fsf-pools/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fwextensions%2Fsf-pools/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fwextensions%2Fsf-pools/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fwextensions","download_url":"https://codeload.github.com/fwextensions/sf-pools/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fwextensions%2Fsf-pools/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28814237,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-27T12:25:15.069Z","status":"ssl_error","status_checked_at":"2026-01-27T12:25:05.297Z","response_time":168,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","built-with-ai"],"created_at":"2026-01-27T13:51:37.608Z","updated_at":"2026-01-27T13:51:38.226Z","avatar_url":"https://github.com/fwextensions.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SF Pools Schedule Viewer\n\nCentralized, searchable schedules for San Francisco public swimming pools. This app scrapes official pool schedule PDFs from SF Rec \u0026 Park, uses an LLM to extract structured schedules, and provides a clean UI to browse by program, day, time, and pool.\n\n## Tech\n\n- Next.js (App Router), React 19, TypeScript\n- Tailwind CSS v4 (via `@tailwindcss/postcss` and `@import \"tailwindcss\"`)\n- Vercel AI SDK (`ai`) with Google Generative AI provider (`@ai-sdk/google`)\n- Zod for strict schema validation\n- GitHub Actions for automated weekly schedule updates\n\n## Prerequisites\n\n- Node.js 22+\n- npm\n- A Google Generative AI API key\n\n## Setup\n\n1. Install dependencies:\n\n\t```\n\tnpm install\n\t```\n\n2. Create `.env.local` in the project root and add:\n\n\t```\n\tGOOGLE_GENERATIVE_AI_API_KEY=your_key_here\n\t```\n\n3. Run the dev server:\n\n\t```\n\tnpm run dev\n\t```\n\n4. Generate schedules:\n\n\t```\n\tnpm run build-schedules\n\t```\n\nThis will scrape PDF URLs, download PDFs, extract schedules, and write `public/data/all_schedules.json`. View at `/schedules`.\n\n## Architecture\n\n### Data Files\n\n- **`data/pools.json`** — Source of truth for static pool metadata (id, name, shortName, address, pageUrl). Does not change frequently.\n- **`public/data/discovered_pool_schedules.json`** — Scraped PDF URLs (poolId → pdfUrl mapping). Regenerated on each scrape.\n- **`data/pdf-manifest.json`** — Tracks downloaded PDFs by hash to detect changes.\n- **`data/extracted/\u003cpoolId\u003e.json`** — Cached LLM extractions per PDF.\n- **`public/data/all_schedules.json`** — Aggregated schedule data for the UI.\n- **`data/changelog/`** — Change history between schedule updates.\n\n### Pipeline Flow\n\n```\nscrape →    download-pdfs →   process-all-pdfs\n   ↓             ↓                 ↓\nvalidates   checks hash       preserves data\npool count  downloads if      fails on large\n\u0026 URLs      content changed   changes\n```\n\n1. **Scrape**: Discovers pool pages and PDF URLs from SF Rec \u0026 Park. Validates against `pools.json` — fails if pool count or page URLs change unexpectedly.\n2. **Download**: Fetches PDFs, checks content hash against manifest. Only downloads if content actually changed (handles URL changes gracefully).\n3. **Process**: Extracts schedules via LLM, preserves unchanged pool schedules, detects large changes.\n\n### Change Detection\n\nThe pipeline computes a changelog comparing old vs new schedules:\n- **none/minor**: Normal updates, build succeeds\n- **major/wholesale**: Large changes detected, build fails in CI (requires manual review)\n\nSet `FAIL_ON_LARGE_CHANGES=false` locally to bypass this check during development.\n\n## Scripts\n\n- `npm run dev` — start Next.js dev server\n- `npm run build` — build for production\n- `npm run start` — start production build\n- `npm run lint` — run ESLint\n- `npm run test` — run tests\n- `npm run scrape` — scrape pool pages, validate against pools.json, discover PDF URLs\n- `npm run download-pdfs` — download changed PDFs into `data/pdfs/`\n- `npm run process-all-pdfs` — extract schedules from PDFs, preserve unchanged, write changelog\n- `npm run build-schedules` — full pipeline: scrape → download → process\n- `npm run scrape-alerts` — scrape pool alerts from SF Rec \u0026 Park\n- `npm run analyze-programs` — analyze raw vs canonical program names\n\n## How Extraction Works\n\n1. For each PDF, send content to the LLM with a strict Zod schema\n2. The model extracts:\n\t- Pool metadata (name, season, date range)\n\t- Program entries with day, time, lanes, notes\n3. Pipeline normalizes program names to canonical labels (e.g., \"LAP SWIM\" → \"Lap Swim\")\n4. Enriches with static metadata from `pools.json` (address, URLs)\n\n## Automated Updates\n\nGitHub Actions runs weekly to:\n1. Scrape and download new PDFs\n2. Extract schedules from changed PDFs\n3. Commit changes to `public/data/` and `data/changelog/`\n4. Send push notifications via Pushover\n\nIf large changes are detected, the build fails and a notification is sent for manual review.\n\n## Environment Variables\n\n```bash\n# Required: Google AI API key for schedule extraction\nGOOGLE_GENERATIVE_AI_API_KEY=your_key_here\n\n# Optional: Disable build failure on large changes (for local dev)\nFAIL_ON_LARGE_CHANGES=false\n\n# Optional: Force re-extraction even if cache exists\nREFRESH_EXTRACT=1\n\n# For CI notifications (GitHub Actions secrets)\nPUSHOVER_USER_KEY=...\nPUSHOVER_API_TOKEN=...\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffwextensions%2Fsf-pools","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffwextensions%2Fsf-pools","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffwextensions%2Fsf-pools/lists"}