# 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//...` - 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