← back to Watches
tasks/prd-real-api-integration.md
172 lines
# PRD: Real Market Data via Chrono24 (Free GitHub Library)
## Introduction
Upgrade the market data service to fetch real Omega watch prices from Chrono24 using the free [irahorecka/chrono24](https://github.com/irahorecka/chrono24) Python library. Since our app is Node.js, we'll create a Python scraper script that Node.js calls via child process.
## Goals
- Fetch real watch prices from Chrono24 using free GitHub library
- Create Python scraper script callable from Node.js
- Cache results aggressively (1 hour) to minimize requests
- Graceful fallback to mock data when scraping fails
- Zero cost solution
## User Stories
### US-010: Install Chrono24 Python Library
**Description:** As a developer, I need the chrono24 Python package installed.
**Acceptance Criteria:**
- [ ] Install chrono24 package: `pip install chrono24`
- [ ] Verify import works: `python -c "import chrono24; print('OK')"`
- [ ] Document Python version requirement in README
### US-011: Create Python Scraper Script
**Description:** As a developer, I need a Python script that fetches Omega watch prices from Chrono24.
**Acceptance Criteria:**
- [ ] Create `scripts/chrono24-scraper.py`
- [ ] Accept args: --brand --model --reference --limit
- [ ] Output JSON to stdout with: prices[], avgPrice, minPrice, maxPrice
- [ ] Handle errors gracefully (output empty JSON, not crash)
- [ ] Include rate limiting (sleep between requests)
- [ ] Script runs without errors: `python scripts/chrono24-scraper.py --brand "Omega" --model "Speedmaster"`
### US-012: Create Watch Reference Mapping
**Description:** As a developer, I need a mapping from our watch IDs to Chrono24 search terms.
**Acceptance Criteria:**
- [ ] Create `data/watch-chrono24-map.json`
- [ ] Map watch IDs to search queries
- [ ] Include: brand, model, reference for each watch
- [ ] Cover all 32 watches in omega-watches.json
- [ ] File is valid JSON
### US-013: Create Backend API Endpoint
**Description:** As a developer, I need a server endpoint that calls the Python scraper.
**Acceptance Criteria:**
- [ ] Create `GET /api/market-data/:watchId` endpoint in server.js
- [ ] Call Python script via child_process.spawn
- [ ] Parse JSON output from Python script
- [ ] Cache results in memory (1 hour TTL)
- [ ] Return mock data on Python script failure
- [ ] Server starts without errors
### US-014: Update Frontend Service
**Description:** As a developer, I need to update the frontend to call our backend endpoint.
**Acceptance Criteria:**
- [ ] Update `src/services/marketDataService.js`
- [ ] Call `/api/market-data/:watchId` instead of using mock data directly
- [ ] Keep mock data as fallback
- [ ] Maintain same return shape
- [ ] npm run build passes
### US-015: Add Cache Warmup Script
**Description:** As a developer, I need a script to pre-populate the cache with real data.
**Acceptance Criteria:**
- [ ] Create `scripts/warmup-market-cache.js`
- [ ] Fetch data for all watches sequentially (with delays)
- [ ] Log progress and any failures
- [ ] Can be run manually or via cron
- [ ] Script completes without errors
## Technical Approach
### Python Scraper (`scripts/chrono24-scraper.py`)
```python
#!/usr/bin/env python3
import chrono24
import json
import sys
import argparse
def scrape_prices(brand, model, reference=None, limit=20):
query_str = f"{brand} {model}"
if reference:
query_str += f" {reference}"
try:
results = []
for listing in chrono24.query(query_str).search(limit=limit):
results.append({
'price': listing.get('price'),
'condition': listing.get('condition'),
'location': listing.get('location'),
'url': listing.get('url')
})
prices = [r['price'] for r in results if r['price']]
return {
'listings': results,
'count': len(results),
'minPrice': min(prices) if prices else None,
'maxPrice': max(prices) if prices else None,
'avgPrice': sum(prices) / len(prices) if prices else None
}
except Exception as e:
return {'error': str(e), 'listings': []}
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--brand', required=True)
parser.add_argument('--model', required=True)
parser.add_argument('--reference', default='')
parser.add_argument('--limit', type=int, default=20)
args = parser.parse_args()
result = scrape_prices(args.brand, args.model, args.reference, args.limit)
print(json.dumps(result))
```
### Node.js Endpoint
```javascript
app.get('/api/market-data/:watchId', async (req, res) => {
const { watchId } = req.params;
// Check cache first
if (cache.has(watchId)) {
return res.json(cache.get(watchId));
}
// Get search params from mapping
const mapping = watchMap[watchId];
if (!mapping) {
return res.json(getMockData(watchId));
}
// Call Python scraper
const result = await callPythonScraper(mapping);
// Cache and return
cache.set(watchId, result, 3600000); // 1 hour
res.json(result);
});
```
## Non-Goals
- WatchCharts integration (paid)
- Real-time updates
- Database storage
- Paid APIs
## Success Metrics
- Real prices for 80%+ of watches
- Scraping success > 70%
- Cache hit rate > 90%
- Zero cost
---
## Checklist
- [x] User stories are small and specific
- [x] Free solution using GitHub library
- [x] Technical approach outlined
- [x] Saved to tasks/prd-real-api-integration.md