1er
This commit is contained in:
420
consigne codex.md
Normal file
420
consigne codex.md
Normal file
@@ -0,0 +1,420 @@
|
||||
# Prompt Codex — Projet **suivi_produits** (Amazon.fr → extensible multi-stores)
|
||||
|
||||
## Rôle de Codex
|
||||
Tu es un **agent senior** en développement **Python web**, **Playwright**, **SQL/SQLite**, **architecture logicielle**, **UI frontend** (responsive).
|
||||
tu me parlera en francais dans la discussion et le code devra etre commenté regulieremnt en francais aussi.
|
||||
Tu dois :
|
||||
1) faire un **brainstorming** pragmatique, 2) proposer un **plan** en phases, 3) définir une **structure de projet** claire, 4) implémenter un **MVP** testable, 5) industrialiser (tests, logs, cron, docker-compose), 6) préparer l’**évolution multi-boutiques**.
|
||||
|
||||
Contraintes clés :
|
||||
- Scraping : **Python + Playwright** (robuste, peu de produits : 15–20/jour).
|
||||
- Stockage : **JSON** (raw scrap) + persistance en **SQLite** (historique prix + métriques).
|
||||
- Config :
|
||||
- `config_backend.json` (paramètres scraping + backend + catégories/types)
|
||||
- `config_frontend.json` (paramètres UI + colonnes + thème + bouton mode text/icon)
|
||||
- Logs : fichier de log des scrapes (rotation simple).
|
||||
- UI : **Gruvbox vintage dark**, moderne (ombres, arrondis, typo lisible), responsive. icon fa
|
||||
- Déploiement : test en mode .env puis deploiement final dans **docker-compose** (backend + frontend) + cron/worker pour scrapes périodiques.
|
||||
- Repo : sur mon serveur Gitea, nom : **suivi_produits** : https://gitea.maison43.duckdns.org/gilles/suivi_produit
|
||||
|
||||
---
|
||||
|
||||
## Objectif produit (fonctionnel)
|
||||
Application self-hosted pour suivre l’évolution de produits Amazon.fr (puis autres stores). L’utilisateur ajoute des URLs de produits, l’app :
|
||||
- scrape les données clés (prix, stock, note, badges, image, etc.)
|
||||
- stocke un **snapshot** à chaque scraping
|
||||
- affiche des **vignettes produit** + **graphique historique** clair (tendance, min/max, %)
|
||||
- propose actions : **Scrap**, **Edit**, **Delete**, **Détail**
|
||||
- lance un **scraping planifié** (cron) sur tous les produits à intervalle défini.
|
||||
|
||||
---
|
||||
|
||||
## Données à capturer (Amazon.fr)
|
||||
### Champs de base (toujours)
|
||||
- `url` (canonique) + `asin`
|
||||
- `title` (nom produit)
|
||||
- `image_main_url` (image principale)
|
||||
- `price_current` (valeur numérique + devise)
|
||||
- `stock_status` (texte) + `in_stock` (bool)
|
||||
- `rating_value` (float) + `rating_count` (int)
|
||||
|
||||
### Champs conditionnels (si présents)
|
||||
- `price_list` / prix conseillé / prix barré (si affiché)
|
||||
- `discount_percent` (si affiché ou calculé)
|
||||
- `lowest_30d_price` (si mention “prix le plus bas des 30 derniers jours”)
|
||||
- `amazon_choice` (badge)
|
||||
- `limited_time_deal` (offre à durée limitée)
|
||||
- `prime_eligible` (badge prime / livraison prime)
|
||||
- `amazon_exclusive` (mention exclusivité)
|
||||
|
||||
### Calculs à faire côté app (pas “inventer”)
|
||||
> Important : **ne pas “calculer une réduction” si le champ source n’existe pas**. on ne calcule rien sauf demande explicite de ma part ( peut etre tendance dans courbe historique)
|
||||
|
||||
---
|
||||
|
||||
## Méthode de scraping (sûre/efficace)
|
||||
### Stratégie Playwright
|
||||
- Navigateur Chromium.
|
||||
- Context :
|
||||
- locale `fr-FR`
|
||||
- timezone `Europe/Paris`
|
||||
- viewport réaliste (ex. 1366×768 ou 1920×1080)
|
||||
- user-agent récent
|
||||
- Rythme : faible (15–20 produits/jour) + **delays aléatoires** 1–3s entre pages.
|
||||
- Détection blocages :
|
||||
- si page contient captcha / robot-check → marquer scrap “blocked” + screenshot + html dump pour debug.
|
||||
- Résilience :
|
||||
- retry 1–2 fois max avec backoff, sinon échec contrôlé.
|
||||
|
||||
### Sélecteurs (approche robuste)
|
||||
- Priorité : **IDs stables** (ex : `#productTitle`, `#acrCustomerReviewText`, `#availability`)
|
||||
- Prix : gérer variantes (prix fractionné, promo, etc.)
|
||||
- Fallback : si sélecteur absent, log “missing field”, ne pas planter.
|
||||
|
||||
### Artifacts de debug
|
||||
À chaque scrap :
|
||||
- sauvegarder un JSON “raw” normalisé
|
||||
- en cas d’échec : `page.screenshot()` + `page.content()` dans un dossier `debug/` horodaté.
|
||||
|
||||
---
|
||||
|
||||
## Architecture cible
|
||||
### Backend
|
||||
- API HTTP (proposé : **FastAPI**) :
|
||||
- CRUD produits
|
||||
- déclenchement scrap (produit / tous)
|
||||
- lecture historique + agrégats (min/max/tendance)
|
||||
- lecture/écriture configs frontend/backend
|
||||
- Worker de scraping (Playwright) séparé en module “scraper”
|
||||
- Scheduler (cron interne ou cron container) qui appelle `scrape_all`
|
||||
|
||||
### Frontend
|
||||
- SPA simple (proposé : **Vite + React** ou **Svelte**) ou HTML server-side minimal (selon simplicité).
|
||||
- Thème **Gruvbox vintage dark** :
|
||||
- fond #282828, cartes #3c3836, texte #ebdbb2
|
||||
- accent orange #fe8019, jaune #fabd2f, vert #b8bb26
|
||||
- Responsive : nombre de colonnes configurable.
|
||||
- utilisation de popup lors de l'ajout de produit ou acces a setting
|
||||
### Stockage
|
||||
- le stockage se fais uniquement lors de l'enregistrement du produit
|
||||
- SQLite : tables normalisées (produits, snapshots, tags/catégories/types)
|
||||
- JSON “raw” : archivage optionnel (dossier `data/raw/YYYY-MM/...json`)
|
||||
|
||||
---
|
||||
|
||||
## Structure de projet (proposée et a adpater)
|
||||
```
|
||||
suivi_produits/
|
||||
README.md
|
||||
TODO.md
|
||||
CHANGELOG.md
|
||||
kanban.md
|
||||
docs/
|
||||
backend/
|
||||
app/
|
||||
main.py
|
||||
api/
|
||||
routes_products.py
|
||||
routes_scrape.py
|
||||
routes_config.py
|
||||
routes_stats.py
|
||||
core/
|
||||
config.py # charge config_backend.json
|
||||
logging.py
|
||||
scheduler.py # déclenche scrapes (optionnel)
|
||||
db/
|
||||
database.py # sqlite connection
|
||||
models.py # SQLAlchemy models
|
||||
schemas.py # pydantic
|
||||
crud.py
|
||||
migrations/ # si besoin plus tard
|
||||
scraper/
|
||||
amazon/
|
||||
parser.py # extraction DOM -> dict normalisé
|
||||
selectors.md # doc sélecteurs
|
||||
browser.py # création context Playwright
|
||||
runner.py # scrap 1 / scrap all
|
||||
normalize.py # parsing prix, notes, booléens
|
||||
services/
|
||||
pricing.py # calculs 30j si données
|
||||
images.py
|
||||
tests/
|
||||
test_normalize.py
|
||||
test_parser_samples.py
|
||||
samples/
|
||||
amazon_product.html # snapshots HTML (tests)
|
||||
config_backend.json
|
||||
logs/
|
||||
scrap.log
|
||||
data/
|
||||
raw/
|
||||
screenshots/
|
||||
|
||||
frontend/
|
||||
src/
|
||||
app/
|
||||
components/
|
||||
styles/
|
||||
api/
|
||||
public/
|
||||
config_frontend.json
|
||||
|
||||
docker/
|
||||
docker-compose.yml
|
||||
backend.Dockerfile
|
||||
frontend.Dockerfile
|
||||
nginx.conf (option)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schéma ASCII (UI globale)
|
||||
Objectif : reproduire l’esprit de la capture (vignette + section prix + graphe), en améliorant la lisibilité.
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────────────┐
|
||||
│ suivi_produits [Add Product] [Refresh] [Settings] FE vX BE vY │
|
||||
│ (header fixed) [debug] (⋯) │
|
||||
├──────────────────────────────────────────────────────────────────────────┤
|
||||
│ Grid (cols = config_frontend.json) │
|
||||
│ │
|
||||
│ ┌──────────────────────────── Card Produit ──────────────────────────┐ │
|
||||
│ │ Boutique + Titre (2 lignes) │ │
|
||||
│ │ Amazon │ │
|
||||
│ │ Samsung SSD Interne 9100 Pro… │ │
|
||||
│ │ │ │
|
||||
│ │ ┌───────────────┐ ┌──────────────────────────────────────────┐ │ │
|
||||
│ │ │ Image │ │ ACTUEL 249€99 │ │ │
|
||||
│ │ │ (non rognée) │ │ PRIX CONSEILLÉ 329€99 (si présent) │ │ │
|
||||
│ │ │ │ │ RÉDUCTION -24% (si présent) │ │ │
|
||||
│ │ └───────────────┘ │ STOCK Disponible │ │ │
|
||||
│ │ │ NOTE 4,7 (967) │ │ │
|
||||
│ │ │ CHOIX AMAZON Oui/Non │ │ │
|
||||
│ │ │ PRIME Oui/Non │ │ │
|
||||
│ │ │ DEAL Oui/Non │ │ │
|
||||
│ │ │ Ref: ASIN [Lien produit] │ │ │
|
||||
│ │ └──────────────────────────────────────────┘ │ │
|
||||
│ │ │ │
|
||||
│ │ ┌────────────────────── Graph 30j (clair) ─────────────────────┐ │ │
|
||||
│ │ │ ligne + points, axes lisibles, tooltip │ │ │
|
||||
│ │ └──────────────────────────────────────────────────────────────┘ │ │
|
||||
│ │ │ │
|
||||
│ │ Min 249€99 Max 249€99 Tendance → stable +0.0% Dernier: now │ │
|
||||
│ │ Catégorie: SSD Type: NVMe │ │
|
||||
│ │ │ │
|
||||
│ │ [Scrap] [Edit] [Delete] [Détail] │ │
|
||||
│ └────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
page de debug et log qui affiche le contenue des differentes tables sqlite dans des section distincte, une section log qui affiche les log json de scrap
|
||||
---
|
||||
|
||||
## Vignette produit : exigences UI
|
||||
- Image **non tronquée** : object-fit `contain`, fond neutre, padding.
|
||||
- Section prix alignée au niveau de l’image (descendue comme sur la capture).
|
||||
- “Boutique + titre” sur 2 lignes, pleine largeur, icône boutique.
|
||||
- Badges : Amazon Choice / Prime / Deal / Exclusive (chips).
|
||||
- Graph 30j :
|
||||
- axes + labels lisibles
|
||||
- points visibles
|
||||
- min/max/tendance affichés sous le graphe
|
||||
- couleurs + flèches :
|
||||
- baisse : vert + flèche ↓
|
||||
- stable : jaune/orange + →
|
||||
- hausse : rouge + ↑
|
||||
- Responsive :
|
||||
- `columns` paramétrable (desktop)
|
||||
- mobile : 1 colonne + layout empilé
|
||||
|
||||
---
|
||||
|
||||
## `config_backend.json` (spécification)
|
||||
Contenu attendu :
|
||||
- `app`: { `env`, `version`, `base_url`, `log_level` }
|
||||
- `scrape`:
|
||||
- `interval_minutes` (pour cron/worker)
|
||||
- `headless` (bool)
|
||||
- `timeout_ms`
|
||||
- `retries`
|
||||
- `delay_range_ms`: [min,max]
|
||||
- `user_agent`
|
||||
- `viewport`: {w,h}
|
||||
- `locale`, `timezone`
|
||||
- `proxy` (option) => non pas de proxy
|
||||
- `stores_enabled`: ["amazon_fr"]
|
||||
- `taxonomy`:
|
||||
- `categories`: ["SSD", "CPU", ...]
|
||||
- `types_by_category`: { "SSD": ["NVMe", "SATA"], ... }
|
||||
|
||||
---
|
||||
|
||||
## `config_frontend.json` (spécification)
|
||||
- `ui`:
|
||||
- `theme`: "gruvbox_vintage_dark"
|
||||
- 'button_mode': " text/icon"
|
||||
- `columns_desktop`: 3 (ex) => slider
|
||||
- `card_density`: "comfortable"|"compact"
|
||||
- `show_fields`: { flags }
|
||||
- `refresh_auto_seconds`
|
||||
- `versions`: { `frontend`, `backend_expected` }
|
||||
|
||||
---
|
||||
|
||||
## Schéma base de données (SQLite)
|
||||
Proposer un schéma minimal + extensible.
|
||||
|
||||
### Tables
|
||||
1) `products`
|
||||
- `id` (PK)
|
||||
- `store` (ex: amazon_fr)
|
||||
- `url`
|
||||
- `asin`
|
||||
- `title`
|
||||
- `image_url`
|
||||
- `category`
|
||||
- `type`
|
||||
- `is_active` (bool)
|
||||
- `created_at`, `updated_at`
|
||||
|
||||
2) `scrape_runs`
|
||||
- `id` (PK)
|
||||
- `started_at`, `ended_at`
|
||||
- `status` (success/partial/failed)
|
||||
- `items_total`, `items_ok`, `items_failed`
|
||||
- `log_path` (option)
|
||||
|
||||
3) `product_snapshots`
|
||||
- `id` (PK)
|
||||
- `product_id` (FK → products.id)
|
||||
- `scraped_at`
|
||||
- `price_current`
|
||||
- `price_list` (nullable)
|
||||
- `lowest_30d_price` (nullable)
|
||||
- `stock_text` (nullable)
|
||||
- `in_stock` (nullable)
|
||||
- `rating_value` (nullable)
|
||||
- `rating_count` (nullable)
|
||||
- `prime_eligible` (nullable)
|
||||
- `amazon_choice` (nullable)
|
||||
- `limited_time_deal` (nullable)
|
||||
- `amazon_exclusive` (nullable)
|
||||
- `raw_json_path` (nullable)
|
||||
- `scrape_status` (ok/blocked/missing_fields/error)
|
||||
- `error_message` (nullable)
|
||||
|
||||
4) (option) `tags`
|
||||
- `id`, `name`
|
||||
|
||||
5) (option) `product_tags`
|
||||
- `product_id`, `tag_id`
|
||||
|
||||
### Relations
|
||||
- `products (1) ─── (N) product_snapshots`
|
||||
- `scrape_runs (1) ─── (N) product_snapshots` (optionnel si on lie snapshots à run)
|
||||
|
||||
### Indices
|
||||
- index `(product_id, scraped_at)`
|
||||
- index `asin`
|
||||
|
||||
---
|
||||
page web : logs et debug
|
||||
|
||||
## Plan de développement (phases)
|
||||
### Phase 0 — Setup repo & conventions
|
||||
- initialiser repo `suivi_produits`
|
||||
- ajouter `README.md`, `TODO.md`, `CHANGELOG.md`, `kanban.md`
|
||||
- définir conventions : formatage, lint, tests
|
||||
|
||||
### Phase 1 — MVP Backend + DB
|
||||
- FastAPI + SQLite + SQLAlchemy
|
||||
- endpoints :
|
||||
- `GET /health`
|
||||
- `GET/POST/PUT/DELETE /products`
|
||||
- `POST /scrape/product/{id}`
|
||||
- `POST /scrape/all`
|
||||
- `GET /products/{id}/history?days=30`
|
||||
- `GET/PUT /config/backend`
|
||||
|
||||
### Phase 2 — Scraper Amazon (Playwright)
|
||||
- module `scraper/amazon/parser.py`
|
||||
- normalisation des prix (ex: "249,99 €" → 249.99)
|
||||
- gestion champs optionnels (pas d’invention)
|
||||
- logs + debug artifacts
|
||||
- tests unitaires sur HTML samples (fichiers dans `samples/`)
|
||||
|
||||
### Phase 3 — Frontend (vignettes + graph)
|
||||
- grille responsive, colonnes paramétrables
|
||||
- composant CardProduit
|
||||
- graphique 30j (lib : chart.js / echarts / recharts selon stack)
|
||||
- page Settings : frontend + backend config éditables
|
||||
|
||||
### Phase 4 — Scheduler / Cron
|
||||
- job toutes les X minutes/heures (config)
|
||||
- stockage des snapshots
|
||||
- endpoint stats (dernier scrap, erreurs, taux de succès)
|
||||
|
||||
### Phase 5 — Docker Compose
|
||||
- dockeriser backend + frontend
|
||||
- volume persistant SQLite + logs + raw json
|
||||
- doc d’installation
|
||||
|
||||
### Phase 6 — Évolution multi-stores
|
||||
- abstraction `StoreScraper` (interface)
|
||||
- un module par boutique : `scraper/<store_name>/...`
|
||||
- routing : store → scraper
|
||||
|
||||
---
|
||||
|
||||
## Tests (exigences)
|
||||
- Tests unitaires :
|
||||
- parsing prix FR
|
||||
- extraction rating/count
|
||||
- présence/absence champs optionnels
|
||||
- Tests d’intégration :
|
||||
- scrap d’un produit réel (option “manual”) avec variable `RUN_LIVE_SCRAPE=1`
|
||||
- sinon, replay HTML sample
|
||||
|
||||
---
|
||||
|
||||
## Livrables attendus
|
||||
- Code complet backend + frontend (MVP fonctionnel)
|
||||
- `docker-compose.yml`
|
||||
- docs :
|
||||
- `README.md` (install, run, config)
|
||||
- `TODO.md` (backlog)
|
||||
- `CHANGELOG.md` (semver simple)
|
||||
- `kanban.md` (colonnes : Backlog / Doing / Review / Done)
|
||||
- Schéma DB + migrations (si besoin)
|
||||
|
||||
---
|
||||
|
||||
## Questions à poser (bloquantes ou importantes)
|
||||
1) Frontend : tu préfères **React** (Vite) ou **Svelte** ? (sinon choisis la voie la plus simple et robuste)
|
||||
2) Déclenchement cron : tu veux un **cron linux** (container cron) ou un **scheduler interne** (APScheduler) ?
|
||||
3) Auth : l’app est-elle accessible uniquement en LAN (pas d’auth) ou tu veux un login simple ? => non
|
||||
4) Stockage raw JSON : tu veux conserver **tous** les raw ou uniquement les derniers N jours ? => oui
|
||||
5) Mode “captcha” : en cas de blocage, tu veux :
|
||||
- (a) abandon + log + retry plus tard => oui
|
||||
- (b) ouvrir navigateur headful pour résolution manuelle
|
||||
|
||||
|
||||
Si pas de réponse, prends des décisions raisonnables : React+Vite, scheduler interne APScheduler, pas d’auth (LAN), raw conservé 30 jours, stratégie (a).
|
||||
|
||||
---
|
||||
|
||||
## Notes de qualité (non négociables)
|
||||
- Ne jamais faire planter un scrap si un champ manque.
|
||||
- Ne pas calculer des métriques si aucune donnée n’est disponible sur la page.
|
||||
- UI : lisibilité d’abord (contraste, spacing, hiérarchie typographique).
|
||||
- Logs : chaque scrap doit laisser une trace claire (start/end, erreurs, champs manquants).
|
||||
|
||||
---
|
||||
|
||||
## Démarrage immédiat (premières tâches)
|
||||
1) Créer squelette repo + outils (poetry/pip-tools, pre-commit, ruff, mypy)
|
||||
2) Implémenter DB + CRUD produits
|
||||
3) Implémenter scrap d’un produit Amazon + snapshot
|
||||
4) Afficher vignettes + graph 30j
|
||||
5) Ajouter Settings + configs JSON éditables
|
||||
6) Dockeriser
|
||||
Reference in New Issue
Block a user