{"id":50554822,"url":"https://github.com/tukue/realtime-mobility","last_synced_at":"2026-06-04T06:02:02.757Z","repository":{"id":347315140,"uuid":"1193550363","full_name":"tukue/realtime-mobility","owner":"tukue","description":"Stockholm public travel planner app","archived":false,"fork":false,"pushed_at":"2026-05-24T13:49:24.000Z","size":407,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-24T14:12:50.823Z","etag":null,"topics":["api-integration","backend-development","fastapi","fullstack-development","react","typescript","vite","websocket"],"latest_commit_sha":null,"homepage":"https://realtime-mobility-planer.onrender.com/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tukue.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2026-03-27T10:49:03.000Z","updated_at":"2026-05-24T13:49:28.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/tukue/realtime-mobility","commit_stats":null,"previous_names":["tukue/realtime-mobility"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/tukue/realtime-mobility","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tukue%2Frealtime-mobility","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tukue%2Frealtime-mobility/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tukue%2Frealtime-mobility/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tukue%2Frealtime-mobility/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tukue","download_url":"https://codeload.github.com/tukue/realtime-mobility/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tukue%2Frealtime-mobility/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33891733,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-04T02:00:06.755Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["api-integration","backend-development","fastapi","fullstack-development","react","typescript","vite","websocket"],"created_at":"2026-06-04T06:02:01.919Z","updated_at":"2026-06-04T06:02:02.751Z","avatar_url":"https://github.com/tukue.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Stockholm Travel Planner\n\n**Real-time public transport information for Stockholm — live departure boards, nearby stops, service alerts, and journey planning across all SL transport modes.**\n\nA full-stack application connecting Stockholm's public transit data (SL Trafiklab APIs) to a responsive dark-theme dashboard. Built with React + TypeScript on the frontend and Python FastAPI on the backend, containerized with Docker, and deployed via CI/CD.\n\n---\n\n## Architecture\n\n```mermaid\ngraph TB\n    subgraph Development[\"Development\"]\n        DEV[\"Developer\u003cbr/\u003egit push\"]\n    end\n\n    subgraph GitHub[\"GitHub\"]\n        CI[\"CI/CD Pipeline\u003cbr/\u003eGitHub Actions\"]\n        BUILD[\"Build Frontend\u003cbr/\u003enpm ci + build\"]\n        TEST[\"Run Tests\u003cbr/\u003ePython unittest\"]\n        DOCKER[\"Build Docker\u003cbr/\u003eImage\"]\n        TRIVY[\"Trivy Security\u003cbr/\u003eScan\"]\n        CI --\u003e BUILD\n        CI --\u003e TEST\n        CI --\u003e DOCKER\n        DOCKER --\u003e TRIVY\n    end\n\n    subgraph Production[\"Production\"]\n        RENDER[\"Render Deploy\"]\n        TRIVY -- \"pass\" --\u003e RENDER\n    end\n\n    subgraph Browser[\"Browser\"]\n        FE[\"React SPA\u003cbr/\u003eVite + TypeScript\"]\n    end\n\n    subgraph Backend[\"FastAPI Backend (Python)\"]\n        API[\"REST API\u003cbr/\u003e/api/*\"]\n        WS[\"WebSocket\u003cbr/\u003e/api/alerts/ws/{id}\"]\n        POLLER[\"Background Poller\u003cbr/\u003easyncio\"]\n        API --\u003e POLLER\n    end\n\n    subgraph Storage[\"Storage\"]\n        LS[\"localStorage\u003cbr/\u003eFavorites · Recents\"]\n        SB[\"Supabase\u003cbr/\u003eCloud Favorites\"]\n    end\n\n    subgraph ExternalSL[\"SL Trafiklab Open Data APIs\"]\n        SL1[\"SL Typeahead\u003cbr/\u003eStop Search\"]\n        SL2[\"SL Realtidsinformation 4\u003cbr/\u003eLive Departures\"]\n        SL3[\"SL Deviations\u003cbr/\u003eService Alerts\"]\n        SL4[\"SL Journey Planner\u003cbr/\u003eTrip Planning\"]\n    end\n\n    DEV --\u003e GitHub\n    RENDER --\u003e Backend\n    RENDER -.-\u003e|\"serves static files\"| FE\n\n    FE -- \"HTTP REST\" --\u003e API\n    FE -- \"WebSocket\" --\u003e WS\n    FE --\u003e LS\n    FE -.-\u003e SB\n\n    API --\u003e SL1\n    API --\u003e SL2\n    API --\u003e SL3\n    API --\u003e SL4\n    POLLER --\u003e SL3\n    POLLER --\u003e WS\n```\n\nHigh-level flow: **Developer pushes to GitHub → CI/CD builds, tests, scans → deploys to Render → running app serves the React SPA (static) and FastAPI backend → backend proxies requests to SL Trafiklab Open APIs → live alerts pushed via WebSocket to the browser**.\n\n## Tech Stack\n## Tech Stack\n\n| Category | Technology | Purpose |\n|---|---|---|\n| **Frontend** | React 18 + TypeScript | Component-based UI with type safety |\n| **Build** | Vite 5 | Fast HMR dev server, optimized production builds |\n| **Backend** | Python 3.11+ / FastAPI | Async API server with automatic OpenAPI docs |\n| **API Client** | httpx | Async HTTP client for upstream SL API calls |\n| **Validation** | Pydantic | Request/response schema validation |\n| **Database** | Supabase (PostgreSQL) | Optional cloud favorites persistence |\n| **Container** | Docker (multi-stage) | Node build → Python runtime image |\n| **CI/CD** | GitHub Actions | Build, test, scan (Trivy), deploy (Render) |\n\n### Key Design Decisions\n\n- **No React Router** — The app uses state-driven conditional rendering in `App.tsx` rather than URL-based routing, keeping the bundle minimal for a single-view tool.\n- **No CSS framework** — All styles are inline `React.CSSProperties` objects. This avoids a CSS dependency and keeps the dark-theme styling self-contained.\n- **Graceful degradation** — Supabase is optional; the app works entirely offline-first with localStorage. WebSocket alerts fall back to REST polling with exponential backoff after 3 failed connection attempts.\n- **Dual SL API modes** — Supports both \"key\" (Trafiklab API key) and \"free\" (open endpoints) via `?source=free|key`, enabling development without paid credentials.\n\n---\n\n## Features\n\n### Live Departure Boards\nReal-time departures grouped by transport mode (Bus, Metro, Train, Tram, Ship). Each card shows line number, destination, scheduled/expected time, and deviation status. Auto-refreshes every 30 seconds with manual refresh always available.\n\n### Stop Search with Typeahead\nAsync autocomplete search against SL's stop database. Returns stops with type metadata and site IDs. Recent stops (last 4) persist in localStorage for one-tap re-access.\n\n### Nearby Stops with Live Previews\nBrowser geolocation or manual text input to find stops ranked by Haversine distance. Each nearby stop shows a live departure preview with mode filtering. Selection loads the full board.\n\n### Journey Planning\nPoint-to-point trip planner with stop autocomplete for both origin and destination. Returns up to 5 trip options sorted by departure time, with expandable leg details (mode, line, duration, transfers).\n\n### Live Disruption Alerts\nService alerts pushed via WebSocket with severity color-coding:\n- **Critical** (red) — Major disruptions\n- **Warning** (amber) — Significant delays\n- **Info** (blue) — Minor changes / planned work\n\nBackend runs a background asyncio task polling only for stops with active WebSocket subscribers.\n\n### Backend Health Monitoring\nReal-time status pill in the header polls `/api/health` every 30 seconds. Green/amber/red indicators let users know if the backend is reachable.\n\n---\n\n## API Reference\n\n| Method | Endpoint | Description |\n|---|---|---|\n| `GET` | `/api/health` | Backend health check |\n| `GET` | `/api/realtime/search?query={text}` | Search stops/stations |\n| `GET` | `/api/realtime/liveboard/{site_id}` | Raw departure data |\n| `GET` | `/api/liveboard/format/{site_id}` | Formatted departure board |\n| `GET` | `/api/nearby/stops?lat={}\u0026lon={}` | Nearby stops ranked by distance |\n| `GET` | `/api/nearby/boards?lat={}\u0026lon={}` | Nearby stops with departure previews |\n| `GET` | `/api/nearby/train-boards?lat={}\u0026lon={}` | Nearby train/metro stations with previews |\n| `GET` | `/api/situations/?site_id={}` | Service alerts (REST) |\n| `GET` | `/api/alerts/?site_id={}` | Alerts REST fallback |\n| `WS` | `/api/alerts/ws/{site_id}` | Live alert push |\n| `POST` | `/api/journey/plan` | Journey planning (JSON body) |\n\n---\n\n## Quick Start\n\n### Prerequisites\n\n- Node.js 18+\n- Python 3.11+\n- SL API key ([Trafiklab](https://www.trafiklab.se/)) — optional for free mode\n\n### Backend\n\n```bash\ncd backend\npython -m venv venv \u0026\u0026 source venv/bin/activate\npip install -r requirements.txt\n\n# Configure (optional — app runs in free mode without a key)\ncp .env.example .env\n# Edit .env with your SL_REALTIME_API_KEY\n\nuvicorn main:app --reload --port 8000\n```\n\n### Frontend\n\n```bash\nnpm install\nnpm run dev      # Starts on localhost with API proxy to backend\n\n### Docker\n\n```bash\ndocker build -t stockholm-travel-planner .\ndocker run -p 8000:8000 stockholm-travel-planner\n```\n\n---\n\n## CI/CD Pipeline\n\n```mermaid\ngraph LR\n    Push[Push to main] --\u003e CI[GitHub Actions]\n    CI --\u003e Build[Build Frontend\u003cbr/\u003enpm ci + npm run build]\n    CI --\u003e Test[Run Tests\u003cbr/\u003ePython unittest]\n    CI --\u003e Docker[Build Docker Image]\n    Docker --\u003e Scan[Trivy Vulnerability Scan]\n    Scan --\u003e Deploy[Render Deploy Hook]\n    Deploy --\u003e Live[Production]\n```\n\nThe pipeline in `.github/workflows/ci.yml`:\n1. Installs Node dependencies and builds the frontend\n2. Runs Python test suite (7 test files)\n3. Builds the multi-stage Docker image\n4. Scans with Trivy for vulnerabilities\n5. Triggers Render deploy hook (only on `main`, only if scan passes)\n\n---\n\n## Project Structure\n\n```\n├── backend/\n│   ├── main.py                 # FastAPI app, static file serving, health check\n│   ├── routers/\n│   │   ├── realtime.py         # Stop search + raw departures\n│   │   ├── liveboard.py        # Formatted departure boards\n│   │   ├── nearby.py           # Geospatial nearby queries\n│   │   ├── situations.py       # Service alerts REST\n│   │   ├── alerts.py           # Alerts REST + WebSocket endpoint\n│   │   └── journey.py          # Journey planning\n│   ├── services/\n│   │   ├── sl_api.py           # Core SL client, Haversine distance\n│   │   ├── sl_config.py        # Configurable API URLs\n│   │   ├── alerts_service.py   # Alert normalization\n│   │   ├── alerts_manager.py   # WebSocket manager + background poller\n│   │   └── journey_service.py  # Trip normalization\n│   └── tests/                  # 7 test files (unittest)\n├── src/\n│   ├── App.tsx                 # Application shell, state, layout\n│   ├── main.tsx                # React entry point\n│   ├── components/\n│   │   ├── SearchBar.tsx       # Stop autocomplete\n│   │   ├── stopBoard.tsx       # Live departure board + mode filters\n│   │   ├── LiveBoardCard.tsx   # Individual departure card\n│   │   ├── NearbyStops.tsx     # Nearby stops panel\n│   │   ├── FavoritesList.tsx   # Saved stops\n│   │   ├── DisruptionBanner.tsx# Live alerts\n│   │   └── JourneyPlanner.tsx  # Trip planner\n│   ├── hooks/\n│   │   ├── useLocalFavorites.ts# localStorage favorites\n│   │   ├── useAlerts.ts        # WebSocket + REST fallback\n│   │   └── useMediaQuery.ts    # Responsive breakpoints\n│   └── types/index.ts          # TypeScript interfaces\n├── Dockerfile                  # Multi-stage build\n├── .github/workflows/ci.yml    # CI/CD pipeline\n└── supabase/migrations/        # Database schema\n```\n\n---\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftukue%2Frealtime-mobility","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftukue%2Frealtime-mobility","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftukue%2Frealtime-mobility/lists"}