Documentation

Tableau de bord/Documentation

Documentation technique

Guide d'architecture, sources de données et automatisations qui font tourner la plateforme.

La plateforme consolide trois sources de données — l'API Exeve (catalogue de ventes), Airtable (demandes commerciales) et des CSV ponctuels — dans une base Supabase. Le dashboard Next.jsque vous utilisez lit cette base. Les imports tournent automatiquement chaque matin à 09h30 ou manuellement via l'onglet Admin.

1. Architecture

Le flux de données est unidirectionnel : les sources amont alimentent Supabase, et le dashboard lit Supabase. Aucun écran ne tape directement sur Exeve ou Airtable en lecture.


   ┌──────────────┐      ┌──────────────┐
   │  Exeve API   │      │   Airtable   │
   │  (ventes,    │      │  (demandes,  │
   │  contacts)   │      │  sources)    │
   └──────┬───────┘      └──────┬───────┘
          │                      │
          ▼                      ▼
   ┌────────────────────────────────────┐
   │  Scripts Python (Mac local, .venv)  │
   │  sync_contacts                      │
   │  sync_sales                         │
   │  sync_airtable_sources              │
   └────────────────┬───────────────────┘
                    │
                    ▼
            ┌───────────────┐       ┌───────────┐
            │   Supabase    │◄──────│  Make.com │
            │   PostgreSQL  │       │  (token)  │
            └───────┬───────┘       └───────────┘
                    │ lecture (anon key, RLS)
                    ▼
            ┌────────────────┐         ┌──────────────────────┐
            │ Next.js 16     │ ◄────── │ Vercel (Hobby plan)  │
            │ frontend/      │ deploy  │ build auto sur push  │
            │ React 19       │  push   │ → bi.leducq-encheres │
            └────────────────┘  main   │   .com               │
                                       └──────────────────────┘
      
Source de vérité: Supabase. Tout ce qui s'affiche dans l'app vient de là. Si une donnée semble incorrecte, le réflexe est de regarder dans Supabase Table Editor avant de remettre en cause le dashboard.

2. Bases Supabase

Supabase est notre base PostgreSQL hébergée. Elle contient deux types d'objets : des tables (alimentées par les scripts Python) et des vues SQL (calculs en temps réel sur les tables, consommées par le dashboard).

Tables principales

ventes

Une ligne par vente Exeve.

Clés : PK id_supabase · clé externe id_exeve

Champs : nom_vente, date_vente

Source : sync_sales.py

items

Une ligne par lot. Cœur de la base.

Clés : PK id_supabase · FK vente_id → ventes.id_exeve · FK vendeur_id / acheteur_id → contacts.id_supabase · FK expert_id → experts.id_supabase

Champs : description, adjudication, estimation_basse/haute, thumbnail_url, ref_vendeur, ref_acheteur

Source : sync_sales.py

contacts

Une ligne par personne (vendeur ou acheteur).

Clés : PK ref_contact · aussi id_exeve, id_supabase

Champs : nom, email, ville, telephone, source_acquisition

Source : sync_contacts.py + sync_airtable_sources.py

experts

Une ligne par expert (commissaires-priseurs externes, spécialistes).

Clés : PK id_supabase · UNIQUE ref_expert · UNIQUE id_exeve

Champs : prenom, nom, email, expertise, commission, ref_expert, id_exeve

Source : sync_experts_csv.py (import manuel CSV)

Vues BI (lecture seule, calculées par Supabase)

bi_dashboard_master

Superset des items avec jointures ventes + contacts. Lu par le dashboard, les filtres et l'onglet Ventes.

Clés : FK vente_id (= ventes.id_exeve)

Champs : tout items + nom_vente, date_vente, vendeur_source

Source : vue SQL

bi_ventes_stats

Une ligne par vente, agrégats prêts à afficher.

Clés : id_supabase, id_exeve

Champs : nom_vente, date_vente, nb_lots_total, nb_lots_vendus, total_adjudication, ca_total, taux_de_vente

Source : vue SQL

bi_stats_mensuelles

Une ligne par mois. Alimente le graph « Performance mensuelle ».

Clés : mois (date)

Champs : adjudication_totale, ca_total, nb_lots_total, nb_lots_vendus, adjudication_moyenne, taux_invendu_pourcentage

Source : vue SQL

core_contact_stats

Vue CRM Contacts : profil + KPIs vendeur/acheteur pré-agrégés. Lue par la modal de détail contact pour un affichage instantané (1 ligne SQL au lieu de jusqu'à 500 lignes côté JS).

Clés : ref_contact (= contacts.ref_contact)

Champs : profil (nom, prenom, email, telephone, ville, code_postal, pays, source_acquisition, civilite) · flags (is_vendeur_real, is_acheteur_real) · stats vendeur (nb_lots_mis_en_vente, nb_lots_vendus, nb_invendus, taux_invendu, ca_total_vendeur) · stats acheteur (nb_lots_achetes, total_adjudication_achete)

Source : vue SQL (refactor CTE — un seul scan d'items au lieu de subqueries corrélées)

Table technique

app_secrets

Stocke les secrets qui rotatent souvent. Évite d'éditer .env tous les jours.

Clés : PK key (text)

Champs : value (text), updated_at

Source : Make.com (upsert quotidien du EXEVE_TOKEN)

Les colonnes des vues BI sont définies par des fichiers SQL côté Supabase (pas dans ce repo). Si une colonne manque ou change de nom, c'est dans le SQL editor Supabase qu'il faut la rajouter — ensuite le frontend la consomme automatiquement.

3. Scripts Python

Trois scripts vivent à la racine du projet et sont exécutés avec l'interpréteur local .venv/bin/python. Ils peuvent être lancés manuellement (terminal ou onglet Admin) ou automatiquement par le cron.

sync_contacts.py

Importer / mettre à jour tous les contacts depuis l'API Exeve (paginé).

Inputs
Aucun. Lit .env (Supabase) + table app_secrets (token Exeve).
Sortie
Table contacts à jour. Log : nb ajoutés, nb mis à jour, nb total.
Déclencheur
Bouton « Synchroniser les contacts » dans Admin · cron 09h30
sync_sales.py

Importer une vente Exeve avec ses lots, en croisant un CSV pour récupérer les références vendeur, acheteur et expert.

Inputs
--sale-id (ID Exeve de la vente, ex. 4744) · --csv-file (export CSV Exeve correspondant, doit contenir Ref. Vendeur, Ref. Acheteur et Ref. Expert)
Sortie
Lignes ajoutées/upsertées dans ventes et items. Les FK vendeur_id / acheteur_id / expert_id sont résolues par lookup des refs CSV dans contacts.ref_contact et experts.ref_expert — importez donc contacts et experts d'abord.
Déclencheur
Bouton « Ajouter une vente » dans Admin uniquement. Manuel, une vente à la fois.
sync_experts_csv.py

Importer une liste d'experts depuis un CSV (Nom, Prénom, Email — ref_expert / expertise / commission optionnels).

Inputs
--csv-file (CSV experts). Colonnes mappées : Nomnom, Prénom prenom, Emailemail.
Sortie
Lignes insérées dans experts. Dédoublonnage idempotent sur le triplet (nom, prenom, email) en lower-case.
Déclencheur
Bouton « Importer un CSV » dans Admin uniquement. Manuel, à la demande.
sync_airtable_sources.py

Pour chaque vendeur réel, identifier la première demande Airtable et écrire son champ Source dans contacts.source_acquisition.

Inputs
Aucun. Lit .env + app_secrets pour Supabase et AIRTABLE_TOKEN / AIRTABLE_BASE_ID pour Airtable.
Sortie
Colonne source_acquisition des contacts vendeurs renseignée.
Déclencheur
Bouton « Synchroniser les sources » dans Admin · cron 09h30 (après sync_contacts)

4. Automatisations

Cron local — Mac

Chaque jour à 09h30, une tâche programmée enchaîne :

  1. sync_contacts.py
  2. sync_airtable_sources.py (uniquement si l'étape 1 a réussi)

Sortie + erreurs dans /tmp/leducq-cron.log. Pour vérifier que la tâche est bien planifiée :

crontab -l
Limite macOS: si le MacBook est en veille à 09h30, le cron est skippé silencieusement. Si vous voyez que les chiffres n'ont pas bougé après une nuit, c'est la cause la plus probable. Solution : relancer manuellement depuis l'Admin, ou laisser le Mac réveillé la nuit.

Make.com — rotation du token Exeve

Le token Exeve expire toutes les ~24h. Un scénario Make capture la nouvelle valeur chaque matin et fait un upsert dans la table app_secrets avec la clé EXEVE_TOKEN. Les scripts Python lisent cette table à chaque exécution — plus besoin de toucher au .env chaque jour.

Si tous les scripts échouent simultanément avec une erreur 403 d'Exeve, commencer par vérifier que Make a bien tourné et que app_secrets.EXEVE_TOKEN contient une valeur fraîche (colonne updated_at du jour).

5. Sources externes

API Exeve

  • Base URL : https://api.exeve.fr/v1/public/
  • Authentification : Bearer JWT, rotation ~quotidienne (cf. Make ci-dessus)
  • Endpoints utilisés : /contacts (paginé), /sale/{id}, /sale/{id}/items

Airtable

  • Table Demandeurs — un record par personne ayant fait une demande. Champ email est la clé de jointure avec Supabase.
  • Table Demande — un record par demande commerciale. Le champ Demandeur est un linked record vers Demandeurs. Le champ Sourcecontient l'origine (Artvaleur - Formulaire, Leducq - Inventaire, etc.).
  • Auth : Personal Access Token (PAT) avec scope data.records:read sur la base.

6. Frontend

Application Next.js 16 (App Router) + React 19, charts par Tremor, thème Tailwind v4 « Midnight Luxury ».

Pages

/

Dashboard : KPI banner, filtres (période avec DateRangePicker Tremor, vente, expert, source), courbe d'évolution, histogramme mensuel, donut sources/experts.

Clés : lit bi_dashboard_master + bi_stats_mensuelles

/ventes

Catalog : liste de toutes les ventes avec thumbnails + adjudication totale.

Clés : lit bi_ventes_stats + bi_dashboard_master

/ventes/[saleKey]

Détail d'une vente avec 2 tabs (KPIs / Lots) côte à côte du titre. Tab KPIs : podiums Lots/Vendeurs/Acheteurs (bouton « Voir plus » → modal liste complète) + donuts Source/Expert (expert capé à 6 slices, « Voir plus de détails » pour déplier). Tab Lots : tableau triable. Le bandeau KPI haut reste visible sur les 2 tabs. Click sur un contact d'un podium ou d'une liste → modal détails contact (stats pré-agrégées de core_contact_stats, lots en lazy-load).

Clés : lit bi_dashboard_master.eq('vente_id', …) + core_contact_stats pour la modal contact

/admin

Boutons pour déclencher les scripts Python. Local-only — sur Vercel, les routes /api/sync-* renvoient 503.

Clés : POST sur /api/sync-*

/documentation

Cette page.

Clés :

Routes API (spawn de scripts Python)

POST /api/sync-sale

Reçoit saleId + CSV en multipart, spawne sync_sales.py, renvoie stdout/stderr.

POST /api/sync-contacts

Spawne sync_contacts.py, sans input.

POST /api/sync-airtable-sources

Spawne sync_airtable_sources.py.

POST /api/sync-experts-csv

Reçoit un CSV en multipart, spawne sync_experts_csv.py.

Les routes API /api/sync-*spawnent du Python local et ne marchent qu'en dev (localhost:3000). En production sur Vercel, elles renvoient un 503 via un check process.env.VERCELau début du handler — pour éviter qu'un appel accidentel essaie d'exécuter Python qui n'existe pas. Voir la section Déploiement pour plus de détails.

7. Déploiement

L'app est en production depuis le 20 mai 2026. Le code vit sur GitHub, l'hébergement et le build tournent chez Vercel. Aucune commande manuelle à lancer pour déployer : un git push sur main suffit.

URLs

https://bi.leducq-encheres.com

URL publique de production. Sous-domaine pointé vers Vercel via un CNAME dans la zone DNS OVH de leducq-encheres.com.

https://leducq-bi.vercel.app

Alias Vercel auto-généré. Toujours fonctionnel, redirige vers la prod. Utile pour debug ou si OVH a un souci DNS.

https://github.com/gregoire-lq/leducq-bi

Repo source — compte GitHub pro (gregoire-lq). L'ancien repo perso gregoiregagnaux/leducq-bi n'est plus utilisé.

Workflow Git → Vercel

  1. git pullavant de commencer (récupère ce que l'autre dev a poussé).
  2. Coder, tester en local avec npm run dev.
  3. git add -A && git commit -m "…"pour sauvegarder l'étape.
  4. git push → Vercel détecte le commit et redéploie automatiquement (~1 min).
  5. Branche autre que main : Vercel crée une URL de preview dédiée (leducq-bi-git-<branche>-leducq-s-projects.vercel.app) — utile pour tester sans toucher à la prod.

Configuration Vercel à connaître

Root Directory

frontend (le monorepo a un dossier frontend/ pour Next.js et le code Python à la racine).

Framework Preset

Next.js — ⚠️ ne JAMAIS passer à « Other », sinon Vercel sert le projet en static générique et toutes les routes renvoient 404.

Build Command

npm run build (auto-detected) → exécute next build --webpack dans package.json. Le flag --webpack est nécessaire car l'output Turbopack par défaut de Next 16 a un bug d'intégration avec l'adapter serverless Vercel (résultat : déploiement Ready mais 404 sur toutes les routes).

frontend/.npmrc

legacy-peer-deps=true — nécessaire car Tremor v3.18.7 déclare encore react@^18 comme peer dependency, alors qu'on tourne en React 19. npm strict refuse l'install sans ce flag. Le jour où Tremor passe à React 19 officiellement, on pourra retirer ce fichier.

Environment Variables

NEXT_PUBLIC_SUPABASE_URL + NEXT_PUBLIC_SUPABASE_ANON_KEY (clé sb_publishable_…). PAS la clé sb_secret_… (service_role) — celle-ci ne va que dans le .env racine pour les scripts Python.

Identité Git

Les commits doivent être signés par un membre de la team Vercel (gregoire@leducq-encheres.com). Si tu fais un commit avec une autre identité, Vercel rejette en « Blocked ». Vérifier avec git config user.email dans le repo.

Routes admin (/admin et /api/sync-*) sur Vercel

Ces routes spawn du Python local — impossible sur Vercel (pas de Python, pas de .venv). Les routes API embarquent un check process.env.VERCEL qui renvoie 503 en production. Pour synchroniser depuis chez vous, lancer npm run dev localement et utiliser /admin sur localhost:3000, ou exécuter les scripts Python en CLI directement.

Speed Insightsest activé : Vercel mesure les Core Web Vitals (LCP, CLS, INP) sur les vrais users. Visible dans l'onglet Speed Insights du projet Vercel après quelques visites.

8. En cas de pépin

Quelques scénarios fréquents et le bon réflexe associé.

Les chiffres du dashboard n'ont pas bougé depuis hier.

Causes probables

Mac en veille pendant le cron de 09h30 · Make n'a pas rafraîchi le token Exeve · Une vue Supabase a été cassée.

Quoi faire

1. Ouvrir /adminet cliquer « Synchroniser les contacts ». Regarder le log — si erreur 403, c'est le token.
2. Vérifier dans Supabase Table Editor que app_secrets a bien une ligne EXEVE_TOKEN récente.
3. Sinon, vérifier le scénario Make.

Une vente affiche zéro lot alors qu'elle existe dans Supabase.

Causes probables

La colonne vente_id de bi_dashboard_master ne matche pas l'id_exeve attendu · Ou le lot n'a pas été importé.

Quoi faire

1. Dans SQL Editor : select count(*) from bi_dashboard_master where vente_id = '<id_exeve>';
2. Si zéro, l'import n'a pas tourné. Relancer sync_sales.py avec le bon sale_id et le CSV correspondant via /admin.

Le filtre Source du dashboard ne montre aucune option.

Causes probables

La colonne vendeur_source de bi_dashboard_master est vide pour tous les rows.

Quoi faire

La source vient de contacts.source_acquisitionjoint via le vendeur. Si vide partout, c'est que sync_airtable_sources.py n'a pas tourné. Le relancer via /admin.

Un lot a expert_id NULL alors qu'un expert est censé être assigné.

Causes probables

L'expert n'est pas (encore) importé dans la table experts · La Ref. Expert du CSV ne matche aucun ref_expert en base · Le CSV n'a pas de colonne Ref. Expert.

Quoi faire

1. Vérifier que l'expert existe : SQL select * from experts where ref_expert = '<ref>';
2. Si absent, importer/mettre à jour la liste experts via /admin → « Importer un CSV ».
3. Relancer ensuite sync_sales.py pour cette vente — le lookup résoudra expert_id au prochain upsert.

Un lot a un mauvais thumbnail / pas de thumbnail.

Causes probables

Le lot n'a pas de thumbnail_url côté Exeve · Ou son adjudication est NULL et la vente n'a aucun lot adjugé.

Quoi faire

Le thumbnail d'une vente sur la page Ventes est celui du lot avec la plus haute adjudication non-NULL. Si la vente n'a aucun lot adjugé (vente future), pas de thumbnail. Comportement attendu.

Erreur sur localhost:3000 (« Site inaccessible »).

Causes probables

Le serveur dev Next.js s'est arrêté.

Quoi faire

Relancer depuis le terminal :
cd ~/Documents/leducq-bi/frontend && npm run dev

L'app live (bi.leducq-encheres.com) renvoie 404 NOT_FOUND sur toutes les URLs, alors que Vercel dit que le déploiement est « Ready ».

Causes probables

Le Framework Preset Vercel est passé à « Other » au lieu de « Next.js » (typiquement après un re-import du projet où Vercel n'a pas auto-détecté Next.js). L'adapter serverless n'est plus activé, donc les routes ne sont pas servies.

Quoi faire

1. Aller dans Settings → Build & Development Settings du projet Vercel.
2. Vérifier « Framework Preset » — doit être Next.js. Si c'est « Other », le repasser à Next.js et Save.
3. Pour forcer Vercel à réécrire le cache de détection : sélectionner « Other » → Save → re-sélectionner « Next.js » → Save.
4. Redéployer (Deployments → dernier deploy → ... → Redeploy, sans cache).

Un déploiement Vercel reste « Blocked » avec un email « not a member of the team ».

Causes probables

L'identité Git locale ne correspond pas à un membre de la team Vercel. Typiquement, git config user.email est encore l'email perso au lieu du compte pro.

Quoi faire

1. Vérifier : git config user.email doit retourner gregoire@leducq-encheres.com.
2. Sinon, le corriger :
git config user.email "gregoire@leducq-encheres.com"
git config user.name "gregoire-lq"
3. Faire un commit (au pire empty : git commit --allow-empty -m "retrigger") puis git push. Vercel acceptera le nouveau build.