docs(04): phase 4 drag interaction (broken)
This commit is contained in:
189
.planning/codebase/INTEGRATIONS.md
Normal file
189
.planning/codebase/INTEGRATIONS.md
Normal file
@@ -0,0 +1,189 @@
|
||||
# External Integrations
|
||||
|
||||
**Analysis Date:** 2026-01-18
|
||||
|
||||
## APIs & External Services
|
||||
|
||||
**Apple CloudKit (Primary Data Source):**
|
||||
- Purpose: Sync game schedules, teams, stadiums, league structure to all users
|
||||
- Container: `iCloud.com.sportstime.app`
|
||||
- Database: Public (read-only for users, admin writes via Python uploader)
|
||||
- Client: `Core/Services/CloudKitService.swift`
|
||||
- Sync: `Core/Services/CanonicalSyncService.swift`
|
||||
- Record types: `Game`, `Team`, `Stadium`, `LeagueStructure`, `TeamAlias`, `StadiumAlias`, `Sport`
|
||||
- Auth: User iCloud account (automatic), server-to-server JWT (Python uploader)
|
||||
|
||||
**Apple MapKit:**
|
||||
- Purpose: Geocoding, routing, map snapshots, EV charger search, POI search
|
||||
- Services used:
|
||||
- `MKLocalSearch` - Address and POI search (`LocationService.swift`)
|
||||
- `MKDirections` - Driving routes and ETA (`LocationService.swift`)
|
||||
- `MKMapSnapshotter` - Static map images for PDF (`Export/Services/MapSnapshotService.swift`)
|
||||
- `MKLocalPointsOfInterestRequest` - EV chargers (`EVChargingService.swift`)
|
||||
- Client: `Core/Services/LocationService.swift`, `Core/Services/EVChargingService.swift`
|
||||
- Auth: Automatic via Apple frameworks
|
||||
- Rate limits: Apple's standard MapKit limits
|
||||
|
||||
**Apple StoreKit 2:**
|
||||
- Purpose: In-app subscriptions (Pro tier)
|
||||
- Products: `com.sportstime.pro.monthly`, `com.sportstime.pro.annual`
|
||||
- Client: `Core/Store/StoreManager.swift`
|
||||
- Features gated: Trip limit (1 free, unlimited Pro)
|
||||
|
||||
**Sports League APIs (Official):**
|
||||
|
||||
| API | URL | Sport | Reliability | Client |
|
||||
|-----|-----|-------|-------------|--------|
|
||||
| MLB Stats API | `https://statsapi.mlb.com/api/v1` | MLB | Official | `ScoreAPIProviders/MLBStatsProvider.swift` |
|
||||
| NHL Stats API | `https://api-web.nhle.com/v1` | NHL | Official | `ScoreAPIProviders/NHLStatsProvider.swift` |
|
||||
| NBA Stats API | `https://stats.nba.com/stats` | NBA | Unofficial | `ScoreAPIProviders/NBAStatsProvider.swift` |
|
||||
|
||||
**NBA Stats API Notes:**
|
||||
- Requires specific headers to avoid 403 (User-Agent, Referer, x-nba-stats-origin, x-nba-stats-token)
|
||||
- May break without notice (unofficial)
|
||||
|
||||
**Sports Reference Sites (Scraping Fallback):**
|
||||
|
||||
| Site | URL Pattern | Sport | Client |
|
||||
|------|-------------|-------|--------|
|
||||
| Baseball-Reference | `baseball-reference.com/boxes/?month=M&day=D&year=Y` | MLB | `HistoricalGameScraper.swift` |
|
||||
| Basketball-Reference | `basketball-reference.com/boxscores/?month=M&day=D&year=Y` | NBA | `HistoricalGameScraper.swift` |
|
||||
| Hockey-Reference | `hockey-reference.com/boxscores/?month=M&day=D&year=Y` | NHL | `HistoricalGameScraper.swift` |
|
||||
| Pro-Football-Reference | `pro-football-reference.com/boxscores/...` | NFL | `HistoricalGameScraper.swift` |
|
||||
|
||||
- Uses SwiftSoup for HTML parsing
|
||||
- On-device scraping (no server costs, unlimited scale)
|
||||
- Cached per-session to avoid redundant requests
|
||||
|
||||
## Data Storage
|
||||
|
||||
**SwiftData (Local):**
|
||||
- Purpose: Offline-first data storage, user trips, preferences
|
||||
- Location: App sandbox (automatic)
|
||||
- CloudKit sync: Disabled (`cloudKitDatabase: .none`)
|
||||
- Schema: See `SportsTimeApp.swift` ModelContainer configuration
|
||||
|
||||
**CloudKit Public Database (Remote):**
|
||||
- Purpose: Shared schedule data for all users
|
||||
- Container: `iCloud.com.sportstime.app`
|
||||
- Access: Read (all users), Write (admin via Python uploader)
|
||||
- Sync method: Date-based delta sync (modificationDate filtering)
|
||||
|
||||
**URLCache (Images):**
|
||||
- Purpose: Cache team logos and stadium photos
|
||||
- Memory: 50 MB
|
||||
- Disk: 100 MB
|
||||
- Path: `ImageCache`
|
||||
- Client: `Export/Services/RemoteImageService.swift`
|
||||
|
||||
**File Storage:**
|
||||
- Local filesystem only for PDF export (temporary)
|
||||
- Photo library access for visit photo imports
|
||||
|
||||
**Caching:**
|
||||
- In-memory game score cache: `ScoreResolutionCache.swift`
|
||||
- In-memory scraper cache: `HistoricalGameScraper.swift`
|
||||
- URLSession cache: Team logos, stadium photos
|
||||
|
||||
## Authentication & Identity
|
||||
|
||||
**iCloud (Automatic):**
|
||||
- Users authenticate via their iCloud account
|
||||
- Required for: CloudKit sync, poll voting identity
|
||||
- Optional: App works offline without iCloud
|
||||
|
||||
**CloudKit Server-to-Server (Python Uploader):**
|
||||
- JWT authentication with ECDSA P-256 key
|
||||
- Key ID and team ID from Apple Developer portal
|
||||
- Used by `Scripts/sportstime_parser/uploaders/cloudkit.py`
|
||||
|
||||
## Monitoring & Observability
|
||||
|
||||
**Error Tracking:**
|
||||
- None (no Sentry, Crashlytics, etc.)
|
||||
- Errors logged to console via `os.Logger`
|
||||
|
||||
**Logs:**
|
||||
- `os.Logger` subsystem: `com.sportstime.app`
|
||||
- Categories: `BackgroundSyncManager`, `NetworkMonitor`
|
||||
- Rich console output in Python scraper
|
||||
|
||||
## CI/CD & Deployment
|
||||
|
||||
**Hosting:**
|
||||
- iOS App Store (planned)
|
||||
- CloudKit public database (Apple infrastructure)
|
||||
|
||||
**CI Pipeline:**
|
||||
- None configured (no GitHub Actions, Xcode Cloud, etc.)
|
||||
|
||||
**Data Pipeline:**
|
||||
- Python CLI (`sportstime-parser`) scrapes schedules
|
||||
- Uploads to CloudKit via CloudKit Web Services API
|
||||
- iOS apps sync from CloudKit automatically
|
||||
|
||||
## Background Processing
|
||||
|
||||
**BGTaskScheduler:**
|
||||
- Refresh task: `com.sportstime.app.refresh` (periodic CloudKit sync)
|
||||
- Processing task: `com.sportstime.app.db-cleanup` (overnight heavy sync)
|
||||
- Manager: `Core/Services/BackgroundSyncManager.swift`
|
||||
|
||||
**Network Monitoring:**
|
||||
- `NWPathMonitor` triggers sync on connectivity restoration
|
||||
- Debounce: 2.5 seconds (handles WiFi/cellular handoffs)
|
||||
- Manager: `Core/Services/NetworkMonitor.swift`
|
||||
|
||||
**Push Notifications:**
|
||||
- CloudKit subscriptions for data changes
|
||||
- Silent notifications (`shouldSendContentAvailable`)
|
||||
- Subscription IDs: `game-updates`, `league-structure-updates`, `team-alias-updates`, `stadium-alias-updates`
|
||||
|
||||
## Environment Configuration
|
||||
|
||||
**Required Environment Variables:**
|
||||
|
||||
*iOS App:*
|
||||
- None required (CloudKit container ID hardcoded)
|
||||
|
||||
*Python Uploader:*
|
||||
- CloudKit key ID (or in config)
|
||||
- CloudKit team ID (or in config)
|
||||
- ECDSA private key path
|
||||
|
||||
**Secrets Location:**
|
||||
- iOS: Entitlements file (`SportsTime.entitlements`)
|
||||
- Python: Environment variables or local config file
|
||||
|
||||
## Webhooks & Callbacks
|
||||
|
||||
**Incoming:**
|
||||
- CloudKit silent push notifications (background sync trigger)
|
||||
- Deep links: `sportstime://poll/{shareCode}` (poll sharing)
|
||||
|
||||
**Outgoing:**
|
||||
- None
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
**Internal Rate Limiter:**
|
||||
- `Core/Services/RateLimiter.swift`
|
||||
- Per-provider keys: `mlb_stats`, `nba_stats`, `nhl_stats`
|
||||
- Prevents API abuse when resolving historical game scores
|
||||
|
||||
**Provider Auto-Disable:**
|
||||
- Official APIs: Never auto-disabled
|
||||
- Unofficial APIs: Disabled after 3 failures (24h cooldown)
|
||||
- Scraped sources: Disabled after 2 failures (24h cooldown)
|
||||
|
||||
## Polls (Group Trip Planning)
|
||||
|
||||
**CloudKit-Based Polling:**
|
||||
- Record type: `TripPoll`, `PollVote`
|
||||
- Share codes: 6-character uppercase alphanumeric
|
||||
- Deep link: `sportstime://poll/{shareCode}`
|
||||
- Service: `Core/Services/PollService.swift`
|
||||
|
||||
---
|
||||
|
||||
*Integration audit: 2026-01-18*
|
||||
128
.planning/codebase/STACK.md
Normal file
128
.planning/codebase/STACK.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Technology Stack
|
||||
|
||||
**Analysis Date:** 2026-01-18
|
||||
|
||||
## Languages
|
||||
|
||||
**Primary:**
|
||||
- Swift 5.0 - iOS app (`SportsTime/`)
|
||||
- Python 3.11+ - Data scraping and CloudKit uploading (`Scripts/sportstime_parser/`)
|
||||
|
||||
**Secondary:**
|
||||
- JSON - Data interchange format, bundled bootstrap data, configuration
|
||||
|
||||
## Runtime
|
||||
|
||||
**iOS Environment:**
|
||||
- iOS 26+ (iOS 26.2 targeted per Xcode 26.2)
|
||||
- Swift 6 concurrency model enabled (`SWIFT_APPROACHABLE_CONCURRENCY = YES`, `SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor`)
|
||||
- Deployment targets: iPhone and iPad (`TARGETED_DEVICE_FAMILY = "1,2"`)
|
||||
|
||||
**Python Environment:**
|
||||
- Python 3.11, 3.12, or 3.13
|
||||
- No lockfile (uses requirements.txt + pyproject.toml)
|
||||
|
||||
## Frameworks
|
||||
|
||||
**Core iOS Frameworks:**
|
||||
- SwiftUI - UI framework (entire app)
|
||||
- SwiftData - Local persistence (`Core/Models/Local/`)
|
||||
- CloudKit - Remote data sync (`Core/Services/CloudKitService.swift`)
|
||||
- StoreKit 2 - In-app purchases (`Core/Store/StoreManager.swift`)
|
||||
- MapKit - Geocoding, routing, map snapshots, EV chargers
|
||||
- CoreLocation - Location services
|
||||
- PDFKit - PDF generation (`Export/PDFGenerator.swift`)
|
||||
- BackgroundTasks - Background sync scheduling
|
||||
- Network - Network path monitoring (`NWPathMonitor`)
|
||||
- Photos/PhotosUI - Photo library access for visit imports
|
||||
- CryptoKit - Hash generation for canonical data
|
||||
|
||||
**Testing:**
|
||||
- XCTest - Unit and UI tests (`SportsTimeTests/`, `SportsTimeUITests/`)
|
||||
- pytest 8.0+ - Python tests (`Scripts/sportstime_parser/tests/`)
|
||||
|
||||
**Build/Dev:**
|
||||
- Xcode 26.2 - Build system
|
||||
- Swift Package Manager - Dependency management (single package)
|
||||
|
||||
## Key Dependencies
|
||||
|
||||
**iOS - Swift Package Manager:**
|
||||
- SwiftSoup (HTML parsing) - Used in `HistoricalGameScraper.swift` for Sports-Reference scraping
|
||||
|
||||
**Python - pip:**
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| requests | 2.31.0+ | HTTP client for API calls |
|
||||
| beautifulsoup4 | 4.12.0+ | HTML parsing |
|
||||
| lxml | 5.0.0+ | XML/HTML parser backend |
|
||||
| rapidfuzz | 3.5.0+ | Fuzzy string matching for team names |
|
||||
| python-dateutil | 2.8.0+ | Date parsing |
|
||||
| pytz | 2024.1+ | Timezone handling |
|
||||
| rich | 13.7.0+ | CLI output formatting |
|
||||
| pyjwt | 2.8.0+ | JWT token generation for CloudKit auth |
|
||||
| cryptography | 42.0.0+ | ECDSA signing for CloudKit requests |
|
||||
|
||||
**Dev Dependencies (Python):**
|
||||
- pytest 8.0+ - Test runner
|
||||
- pytest-cov 4.1+ - Coverage reporting
|
||||
- responses 0.25+ - HTTP mocking
|
||||
|
||||
## Configuration
|
||||
|
||||
**Environment Variables:**
|
||||
- CloudKit Web Services requires ECDSA private key for server-to-server auth
|
||||
- iOS app uses iCloud container identifier: `iCloud.com.sportstime.app`
|
||||
|
||||
**Build Settings:**
|
||||
| Setting | Value | Purpose |
|
||||
|---------|-------|---------|
|
||||
| `SWIFT_VERSION` | 5.0 | Swift language version |
|
||||
| `SWIFT_APPROACHABLE_CONCURRENCY` | YES | Modern concurrency |
|
||||
| `SWIFT_DEFAULT_ACTOR_ISOLATION` | MainActor | Default isolation |
|
||||
| `SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY` | YES | Swift 6 prep |
|
||||
|
||||
**Info.plist:**
|
||||
- Background modes: `remote-notification`, `fetch`, `processing`
|
||||
- Background task IDs: `com.sportstime.app.refresh`, `com.sportstime.app.db-cleanup`
|
||||
- Photo library usage description for visit imports
|
||||
|
||||
**Entitlements:**
|
||||
- CloudKit: `iCloud.com.sportstime.app`
|
||||
- Push notifications: `aps-environment` (development)
|
||||
|
||||
## Platform Requirements
|
||||
|
||||
**Development:**
|
||||
- macOS with Xcode 26.2
|
||||
- Python 3.11+ for data scraping
|
||||
- iCloud account for CloudKit testing
|
||||
|
||||
**Production:**
|
||||
- iOS 26+ (uses iOS 26 APIs, deprecated CLGeocoder annotations)
|
||||
- iCloud account for data sync (optional, works offline)
|
||||
- Network for CloudKit sync (offline-first architecture)
|
||||
|
||||
## Data Architecture
|
||||
|
||||
**Three-Tier Storage:**
|
||||
1. **Bundled JSON** (`SportsTime/Resources/`) - Bootstrap data for first launch
|
||||
2. **SwiftData** (local) - Runtime source of truth, offline-capable
|
||||
3. **CloudKit** (remote) - Public database for schedule updates
|
||||
|
||||
**Key Data Files:**
|
||||
- `games_canonical.json` - Pre-bundled game schedules
|
||||
- `teams_canonical.json` - Team definitions
|
||||
- `stadiums_canonical.json` - Stadium definitions
|
||||
- `league_structure.json` - League/conference/division hierarchy
|
||||
- `team_aliases.json` - Historical team name mappings
|
||||
- `stadium_aliases.json` - Historical stadium name mappings
|
||||
|
||||
**SwiftData Models:**
|
||||
- Canonical: `CanonicalStadium`, `CanonicalTeam`, `CanonicalGame`, `CanonicalSport`
|
||||
- User: `SavedTrip`, `StadiumVisit`, `UserPreferences`, `Achievement`
|
||||
- Sync: `SyncState`, `LeagueStructureModel`, `TeamAlias`, `StadiumAlias`
|
||||
|
||||
---
|
||||
|
||||
*Stack analysis: 2026-01-18*
|
||||
Reference in New Issue
Block a user