Files
scrap/PHASE_2_PROGRESS.md
Gilles Soulier d0b73b9319 codex2
2026-01-14 21:54:55 +01:00

508 lines
14 KiB
Markdown

# 🚀 Phase 2 Infrastructure - EN COURS
**Date de démarrage**: 2026-01-14
**Version cible**: 0.4.0
**Objectif**: Ajouter PostgreSQL + Redis/RQ worker pour persistence et scraping asynchrone
---
## 📊 Vue d'Ensemble
### Mises a jour recentes
- Migration Alembic corrigee (down_revision sur 20260114_02)
- Extraction images Amazon amelioree (data-a-dynamic-image + filtre logos)
- Nouveau scraping de validation (URL Amazon ASUS A16)
### Prochaines actions
- Verifier l'affichage des images, description, specs, msrp et reduction dans le Web UI
- Confirmer que le popup ajout produit affiche toutes les donnees du preview
### Objectifs Phase 2
- ✅ Configuration centralisée (database, Redis, app)
- ✅ Modèles SQLAlchemy ORM (5 tables)
- ✅ Connexion base de données (init_db, get_session)
- ✅ Migrations Alembic
- ✅ Repository pattern (CRUD)
- ✅ Worker RQ pour scraping asynchrone
- ✅ Scheduler pour jobs récurrents
- ✅ CLI étendu (commandes DB + worker)
- ✅ Docker Compose (PostgreSQL + Redis)
- ✅ Gestion erreurs Redis
- ✅ Logs d'observabilité jobs
- ⏳ Tests end-to-end (Semaine 4)
---
## ✅ Semaine 1: Database Foundation (TERMINÉE)
### Tâches Complétées
#### 1. Configuration Centralisée ✅
**Fichier**: `pricewatch/app/core/config.py` (187 lignes)
**Contenu**:
- `DatabaseConfig`: Configuration PostgreSQL
- Host, port, database, user, password
- Propriété `url`: SQLAlchemy connection string
- Propriété `url_async`: AsyncPG connection string (futur)
- Prefix env vars: `PW_DB_*` (PW_DB_HOST, PW_DB_PORT, etc.)
- `RedisConfig`: Configuration Redis pour RQ
- Host, port, db, password (optional)
- Propriété `url`: Redis connection string
- Prefix env vars: `PW_REDIS_*`
- `AppConfig`: Configuration globale application
- Debug mode
- Worker timeout (300s par défaut)
- Worker concurrency (2 par défaut)
- Feature flags: `enable_db`, `enable_worker`
- Defaults Playwright: timeout, use_playwright
- Nested configs: `db`, `redis`
- Prefix env vars: `PW_*`
- **Pattern Singleton**: `get_config()`, `set_config()`, `reset_config()`
**Justifications**:
- 12-factor app: configuration via env vars
- Pydantic validation garantit config valide au démarrage
- Valeurs par défaut pour développement local
- Support `.env` file pour faciliter le setup
- Feature flags permettent de désactiver DB/worker pour tests
#### 2. Dépendances Phase 2 ✅
**Fichier**: `pyproject.toml` (lignes 48-60)
**Ajouts**:
```toml
# Database (Phase 2)
"sqlalchemy>=2.0.0",
"psycopg2-binary>=2.9.0",
"alembic>=1.13.0",
# Configuration (Phase 2)
"python-dotenv>=1.0.0",
# Worker/Queue (Phase 2)
"redis>=5.0.0",
"rq>=1.15.0",
"rq-scheduler>=0.13.0",
```
#### 3. Modèles SQLAlchemy ORM ✅
**Fichier**: `pricewatch/app/db/models.py` (322 lignes)
**Tables créées**:
1. **`products`** - Catalogue produits
- PK: `id` (Integer, autoincrement)
- Natural key: `(source, reference)` - Unique constraint
- Colonnes: `url`, `title`, `category`, `currency`
- Timestamps: `first_seen_at`, `last_updated_at`
- Relations: `price_history`, `images`, `specs`, `logs`
- Indexes: source, reference, last_updated_at
2. **`price_history`** - Historique prix (time-series)
- PK: `id` (Integer, autoincrement)
- FK: `product_id` → products(id) CASCADE
- Unique: `(product_id, fetched_at)` - Évite doublons
- Colonnes: `price` (Numeric 10,2), `shipping_cost`, `stock_status`
- Fetch metadata: `fetch_method`, `fetch_status`, `fetched_at`
- Check constraints: stock_status, fetch_method, fetch_status
- Indexes: product_id, fetched_at
3. **`product_images`** - Images produit
- PK: `id` (Integer, autoincrement)
- FK: `product_id` → products(id) CASCADE
- Unique: `(product_id, image_url)` - Évite doublons
- Colonnes: `image_url` (Text), `position` (Integer, 0=main)
- Index: product_id
4. **`product_specs`** - Caractéristiques produit (key-value)
- PK: `id` (Integer, autoincrement)
- FK: `product_id` → products(id) CASCADE
- Unique: `(product_id, spec_key)` - Évite doublons
- Colonnes: `spec_key` (String 200), `spec_value` (Text)
- Indexes: product_id, spec_key
5. **`scraping_logs`** - Logs observabilité
- PK: `id` (Integer, autoincrement)
- FK optionnelle: `product_id` → products(id) SET NULL
- Colonnes: `url`, `source`, `reference`, `fetched_at`
- Métriques: `duration_ms`, `html_size_bytes`
- Fetch metadata: `fetch_method`, `fetch_status`
- Debug data (JSONB): `errors`, `notes`
- Indexes: product_id, source, fetched_at, fetch_status
**Justifications schéma**:
- Normalisation: products séparée de price_history (catalogue vs time-series)
- Clé naturelle (source, reference) vs UUID arbitraire
- Tables séparées pour images/specs: évite JSONB non structuré
- JSONB uniquement pour données variables: errors, notes dans logs
- Cascade DELETE: suppression produit → suppression historique
- SET NULL pour logs: garde trace même si produit supprimé
---
### Tâches Complétées (suite)
#### 4. Connexion Base de Données ✅
**Fichier**: `pricewatch/app/db/connection.py`
**Contenu**:
- `get_engine(config)`: Engine SQLAlchemy (pooling)
- `get_session_factory(config)`: Session factory
- `get_session(config)`: Context manager
- `init_db(config)`: Création tables
- `check_db_connection(config)`: Health check
- `reset_engine()`: Reset pour tests
**Justifications**:
- Singleton engine pour éviter les pools multiples
- `pool_pre_ping` pour robustesse
- Context manager pour rollback/close automatiques
---
#### 5. Setup Alembic ✅
**Fichiers**:
- `alembic.ini`
- `pricewatch/app/db/migrations/env.py`
- `pricewatch/app/db/migrations/script.py.mako`
**Justifications**:
- URL DB injectée depuis `AppConfig`
- `compare_type=True` pour cohérence des migrations
#### 6. Migration Initiale ✅
**Fichier**: `pricewatch/app/db/migrations/versions/20260114_01_initial_schema.py`
**Contenu**:
- 5 tables + indexes + contraintes
- JSONB pour `errors` et `notes`
#### 7. Commandes CLI Database ✅
**Fichier**: `pricewatch/app/cli/main.py`
**Commandes**:
```bash
pricewatch init-db # Créer tables
pricewatch migrate "message" # Générer migration Alembic
pricewatch upgrade # Appliquer migrations
pricewatch downgrade # Rollback migration
```
#### 8. Docker Compose ✅
**Fichier**: `docker-compose.yml`
**Services**:
- PostgreSQL 16 (port 5432)
- Redis 7 (port 6379)
- Volumes pour persistence
#### 9. Fichier .env Exemple ✅
**Fichier**: `.env.example`
**Variables**:
```bash
# Database
PW_DB_HOST=localhost
PW_DB_PORT=5432
PW_DB_DATABASE=pricewatch
PW_DB_USER=pricewatch
PW_DB_PASSWORD=pricewatch
# Redis
PW_REDIS_HOST=localhost
PW_REDIS_PORT=6379
PW_REDIS_DB=0
# App
PW_DEBUG=false
PW_WORKER_TIMEOUT=300
PW_WORKER_CONCURRENCY=2
PW_ENABLE_DB=true
PW_ENABLE_WORKER=true
```
#### 10. Tests Database ✅
**Fichiers**:
- `tests/db/test_models.py`: Tests des modèles SQLAlchemy
- `tests/db/test_connection.py`: Tests connexion et session
**Stratégie tests**:
- SQLite in-memory pour tests unitaires
- Fixtures pytest pour setup/teardown
- Tests relationships, constraints, indexes
---
## 📦 Semaine 2: Repository & Pipeline (TERMINEE)
### Tâches Prévues
#### Repository Pattern
**Fichier**: `pricewatch/app/db/repository.py`
**Classe**: `ProductRepository`
- `get_or_create(source, reference)`: Trouver ou créer produit
- `save_snapshot(snapshot)`: Persist ProductSnapshot to DB
- `update_product_metadata(product, snapshot)`: Update title, url, etc.
- `add_price_history(product, snapshot)`: Ajouter entrée prix
- `sync_images(product, images)`: Sync images (add new, keep existing)
- `sync_specs(product, specs)`: Sync specs (upsert)
- `add_scraping_log(snapshot, product_id)`: Log scraping
**Statut**: ✅ Terminé
#### Scraping Pipeline
**Fichier**: `pricewatch/app/scraping/pipeline.py`
**Classe**: `ScrapingPipeline`
- `process_snapshot(snapshot, save_to_db)`: Orchestration
- Non-blocking: échec DB ne crash pas pipeline
- Retour: `product_id` ou `None`
**Statut**: ✅ Terminé
#### CLI Modification
**Fichier**: `pricewatch/app/cli/main.py`
**Modification commande `run`**:
- Ajouter flag `--save-db / --no-db`
- Intégrer `ScrapingPipeline` si `save_db=True`
- Compatibilité backward: JSON output toujours créé
**Statut**: ✅ Terminé
#### Tests Repository + Pipeline ✅
**Fichiers**:
- `tests/db/test_repository.py`
- `tests/scraping/test_pipeline.py`
**Statut**: ✅ Terminé
#### Tests end-to-end CLI + DB ✅
**Fichier**:
- `tests/cli/test_run_db.py`
**Statut**: ✅ Terminé
---
## 📦 Semaine 3: Worker Infrastructure (TERMINEE)
### Tâches Prévues
#### RQ Task
**Fichier**: `pricewatch/app/tasks/scrape.py`
**Fonction**: `scrape_product(url, use_playwright=True)`
- Réutilise 100% code Phase 1 (detect → fetch → parse)
- Save to DB via ScrapingPipeline
- Retour: `{success, product_id, snapshot, error}`
**Statut**: ✅ Terminé
#### Scheduler
**Fichier**: `pricewatch/app/tasks/scheduler.py`
**Classe**: `ScrapingScheduler`
- `schedule_product(url, interval_hours=24)`: Job récurrent
- `enqueue_immediate(url)`: Job unique
- Basé sur `rq-scheduler`
**Statut**: ✅ Terminé
#### CLI Worker
**Nouvelles commandes**:
```bash
pricewatch worker # Lancer worker RQ
pricewatch enqueue <url> # Enqueue scrape immédiat
pricewatch schedule <url> --interval 24 # Scrape quotidien
```
**Statut**: ✅ Terminé
#### Tests worker + scheduler ✅
**Fichiers**:
- `tests/tasks/test_scrape_task.py`
- `tests/tasks/test_scheduler.py`
**Statut**: ✅ Terminé
#### Gestion erreurs Redis ✅
**Fichiers modifiés**:
- `pricewatch/app/tasks/scheduler.py`:
- Ajout `RedisUnavailableError` exception
- Ajout `check_redis_connection()` helper
- Connexion lazy avec ping de vérification
- `pricewatch/app/cli/main.py`:
- Commandes `worker`, `enqueue`, `schedule` gèrent Redis down
- Messages d'erreur clairs avec instructions
**Tests ajoutés** (7 tests):
- `test_scheduler_redis_connection_error`
- `test_scheduler_lazy_connection`
- `test_check_redis_connection_success`
- `test_check_redis_connection_failure`
- `test_scheduler_schedule_redis_error`
**Statut**: ✅ Terminé
#### Logs d'observabilité jobs ✅
**Fichier modifié**: `pricewatch/app/tasks/scrape.py`
**Logs ajoutés**:
- `[JOB START]` - Début du job avec URL
- `[STORE]` - Store détecté
- `[FETCH]` - Résultat fetch HTTP/Playwright (durée, taille)
- `[PARSE]` - Résultat parsing (titre, prix)
- `[JOB OK]` / `[JOB FAILED]` - Résultat final avec durée totale
**Note**: Les logs sont aussi persistés en DB via `ScrapingLog` (déjà implémenté).
**Statut**: ✅ Terminé
---
## 📦 Semaine 4: Tests & Documentation (EN COURS)
### Tâches Prévues
#### Tests
- ✅ Tests end-to-end (CLI → DB → Worker)
- ✅ Tests erreurs (DB down, Redis down)
- ✅ Tests backward compatibility (`--no-db`)
- ✅ Performance tests (100+ produits)
**Fichiers tests ajoutes**:
- `tests/cli/test_worker_cli.py`
- `tests/cli/test_enqueue_schedule_cli.py`
- `tests/scraping/test_pipeline.py` (erreurs DB)
- `tests/tasks/test_redis_errors.py`
- `tests/cli/test_run_no_db.py`
- `tests/db/test_bulk_persistence.py`
- `tests/tasks/test_worker_end_to_end.py`
- `tests/cli/test_cli_worker_end_to_end.py`
- **Resultat**: OK avec Redis actif
#### Documentation
- ✅ Update README.md (setup Phase 2)
- ✅ Update CHANGELOG.md
- ✅ Migration guide (JSON → DB)
---
## 📈 Métriques d'Avancement
| Catégorie | Complétées | Totales | % |
|-----------|------------|---------|---|
| **Semaine 1** | 10 | 10 | 100% |
| **Semaine 2** | 5 | 5 | 100% |
| **Semaine 3** | 6 | 6 | 100% |
| **Semaine 4** | 7 | 7 | 100% |
| **TOTAL Phase 2** | 28 | 28 | **100%** |
---
## 🎯 Prochaine Étape Immédiate
**Prochaine étape immédiate**
- Phase 2 terminee, bascule vers Phase 3 (API REST)
- API v1 avancee: filtres, export CSV/JSON, webhooks + tests associes
**Après (prévu)**
- Documentation Phase 2 (resume final)
- Retry policy (optionnel)
- Phase 4 Web UI (dashboard + graphiques)
---
## 🔧 Vérifications
### Vérification Semaine 1 (objectif)
```bash
# Setup infrastructure
docker-compose up -d
pricewatch init-db
# Vérifier tables créées
psql -h localhost -U pricewatch pricewatch
\dt
# → 5 tables: products, price_history, product_images, product_specs, scraping_logs
```
### Vérification Semaine 2 (objectif)
```bash
# Test pipeline avec DB
pricewatch run --yaml scrap_url.yaml --save-db
# Vérifier données en DB
psql -h localhost -U pricewatch pricewatch
SELECT * FROM products LIMIT 5;
SELECT * FROM price_history ORDER BY fetched_at DESC LIMIT 10;
```
### Vérification Semaine 3 (objectif)
```bash
# Enqueue job
pricewatch enqueue "https://www.amazon.fr/dp/B08N5WRWNW"
# Lancer worker
pricewatch worker
# Vérifier job traité
psql -h localhost -U pricewatch pricewatch
SELECT * FROM scraping_logs ORDER BY fetched_at DESC LIMIT 5;
```
---
## 📝 Notes Importantes
### Backward Compatibility
- ✅ CLI Phase 1 fonctionne sans changement
- ✅ Format JSON identique
- ✅ Database optionnelle (`--no-db` flag)
- ✅ ProductSnapshot inchangé
- ✅ Tests Phase 1 continuent à passer (295 tests)
### Architecture Décisions
**Normalisation vs Performance**:
- Choix: Normalisation stricte (5 tables)
- Justification: Catalogue change rarement, prix changent quotidiennement
- Alternative rejetée: Tout dans products + JSONB (moins queryable)
**Clé Naturelle vs UUID**:
- Choix: `(source, reference)` comme unique constraint
- Justification: ASIN Amazon déjà unique globalement
- Alternative rejetée: UUID artificiel (complexifie déduplication)
**Synchrone vs Asynchrone**:
- Choix: RQ synchrone (pas d'async/await)
- Justification: Code Phase 1 réutilisable à 100%, simplicité
- Alternative rejetée: Asyncio + asyncpg (refactoring massif)
---
**Dernière mise à jour**: 2026-01-15
### Recap avancement recent (Phase 3 API)
- Filtres avances + exports CSV/JSON + webhooks (CRUD + test)
- Tests API avances ajoutes
- Nettoyage warnings Pydantic/datetime/selectors
- Suite pytest complete: 339 passed, 4 skipped
### Validation locale (Semaine 1)
```bash
docker compose up -d
./venv/bin/alembic -c alembic.ini upgrade head
psql -h localhost -U pricewatch pricewatch
\\dt
```
**Resultat**: 6 tables visibles (products, price_history, product_images, product_specs, scraping_logs, alembic_version).
**Statut**: ✅ Semaine 1 terminee (100%).