# Architecture ## High-level flow ``` ┌──────────────┐ ┌──────────────┐ │ Ticketmaster │ │ SeatGeek │ │ Provider │ │ Provider │ └──────┬───────┘ └──────┬───────┘ │ │ └────────┬───────────┘ ▼ ┌───────────────┐ │ Normalize & │ │ Deduplicate │ └───────┬───────┘ ▼ ┌───────────────┐ │ Impact Score │ └───────┬───────┘ ▼ ┌───────────────┐ │ Filter by │ │ date window │ └───────┬───────┘ │ ┌───────┴───────┐ ▼ ▼ ┌──────────────┐ ┌──────────────┐ │ Telegram │ │ Airbnb │ │ Alert │ │ Calendar │ │ (primary) │ │ (optional) │ └──────────────┘ └──────────────┘ ``` ## Module map ``` src/ ├── main.py # CLI entrypoint, orchestration ├── config.py # Pydantic settings from env vars ├── models.py # NormalizedEvent dataclass ├── log.py # Structured logging configuration ├── dedup.py # Deduplication across providers ├── providers/ │ ├── base.py # EventProvider abstract base class │ ├── ticketmaster.py # Ticketmaster Discovery API │ └── seatgeek.py # SeatGeek API ├── scoring/ │ └── impact.py # Rule-based impact scoring ├── notifications/ │ └── telegram.py # Telegram bot message sender └── airbnb/ ├── auth.py # Playwright storage state management └── calendar.py # Calendar navigation and price updates ``` ## Key design decisions ### 1. Provider abstraction All event providers implement `EventProvider.fetch() -> list[NormalizedEvent]`. This makes it trivial to add new sources (Eventbrite, PredictHQ, scraping) without touching the orchestration layer. ### 2. Normalized event model A single `NormalizedEvent` dataclass acts as the shared contract between providers, deduplication, scoring, and output formatting. Fields: name, date, venue, source, url, raw metadata. ### 3. Airbnb automation is isolated and optional The `src/airbnb/` module is completely decoupled from event ingestion. If Playwright breaks (selectors change, login expires), the system degrades gracefully to Telegram-only alerts. The main runner catches all Airbnb errors and logs them without crashing. ### 4. Storage state for auth Airbnb authentication uses Playwright's `storage_state` API. A one-time manual login script saves cookies/localStorage to `state.json`. Subsequent headless runs load this state. No passwords are stored in code or env vars. ### 5. Configuration via environment All secrets and tunables live in env vars (loaded from `.env` in local dev). No config files to manage, easy to override in Docker/cron. ### 6. No database For weekly runs processing dozens of events, in-memory processing is sufficient. If persistence becomes necessary (e.g., tracking price change history), a simple JSON file or SQLite would be the first step. ## Error handling strategy - Each provider's `fetch()` is wrapped in try/except; one failing source does not block others. - Telegram send failures are logged but do not block Airbnb updates. - Airbnb automation failures are logged and reported via Telegram if possible. - The main runner returns a nonzero exit code if all providers fail. ## Future extension points - New providers: subclass `EventProvider` - New notification channels: add modules under `notifications/` - Smarter scoring: replace `scoring/impact.py` internals - Multi-listing: extend config and loop in `main.py`