◄ Spécification chiffrage par lot / Intégration référentiel de prix
SPEC DEV

Intégration d'un référentiel de prix reconnu

Spécification technique pour adosser le chiffrage Conceptuo à une bibliothèque d'ouvrages du marché (Batiprix ou Batichiffrage), la répliquer en base, la mapper à notre base de prestations, en dériver les trois gammes de prix, et l'utiliser en repli quand une prestation demandée n'existe pas chez nous.

Batiprix ≠ Batichiffrage Ce sont deux produits concurrents de deux groupes différents. Batiprix est édité par Le Moniteur (groupe Infopro Digital) et expose une API d'intégration éditeur (« Data Connect »). Batichiffrage (anciennement « l'Annuel des Prix ») est édité par Batiactu Groupe, met en avant des prix régionalisés et un moteur de recherche assisté par IA. L'architecture ci-dessous est agnostique : elle vaut pour l'un comme pour l'autre. Le fournisseur retenu se branche derrière une couche d'abstraction (champ provider).

00Principe directeur

On ne consomme jamais l'API du fournisseur en direct au moment de générer un devis.

On réplique sa bibliothèque en local (PostgreSQL), on l'indexe, et le moteur de chiffrage ne parle qu'à notre base. Le fournisseur est une source amont synchronisée périodiquement, jamais une dépendance temps réel.

Raisons : latence, quotas d'API, disponibilité, et surtout le besoin de joindre, mapper et recalculer sur ces données (composition d'ouvrages, dérivation des gammes, indexation) — impossible à faire à la volée et de façon fiable.

À retenirSource amont → réplication locale versionnée → mapping → moteur de prix. Le devis ne lit que la base Conceptuo.

0Architecture d'ensemble

Éditeur Batiprix / Batichiffrage API partenaire (licence) Job de sync Laravel scheduler mensuel / millésime Miroir local referential_ouvrage PostgreSQL, versionné Mapping prestation ⇄ ouvrage Moteur de prix Éco / Std / Premium + indexation région/temps Base prestations Conceptuo (curée) Devis généré

Flux nominal : l'éditeur expose ses données → un job planifié les réplique dans le miroir local → la table de mapping relie chaque prestation Conceptuo aux ouvrages amont → le moteur de prix compose le Standard et en dérive Éco/Premium → le devis ne lit que cette sortie.

1·2Interfaçage API & réplication en base

Le mode partenaire de ces éditeurs est prévu pour ça : une clé d'API (licence éditeur) donne accès à un export des nomenclatures que le partenaire peut recopier en local. On implémente exactement ce pattern.

Job de synchronisation

  • Service ReferentialSyncService + job planifié (Laravel scheduler). Cadence : à chaque nouveau millésime, et/ou mensuel pour les mises à jour de prix.
  • Le job pagine l'API, mappe les champs amont → schéma Conceptuo, et fait un upsert dans la table miroir.
  • Versionnement par millésime : on n'écrase pas l'existant, on ajoute une version (audit + rollback possibles).
  • En fin de sync : contrôle d'intégrité (cf. §3, détection des ouvrages orphelins) et journal de la passe (nb d'ouvrages créés / mis à jour / disparus).

Schéma de la table miroir

Le sous-détail mo_ht / fourniture_ht est indispensable — c'est lui qui rend possible la dérivation des gammes (§5). Si l'API le fournit, on le stocke ; sinon, on le reconstitue via le temps de pose × taux horaire.

-- Réplique locale du référentiel, versionnée par millésime
TABLE referential_ouvrage (
  id               bigserial PK,
  provider         text,        -- 'batiprix' | 'batichiffrage'
  millesime        int,         -- 2026
  code_provider    text,        -- code natif de l'éditeur
  libelle          text,
  descriptif       text,        -- descriptif détaillé (conforme DTU)
  unite            text,        -- m2 | ml | u | forfait...
  prix_moyen_ht    numeric,     -- prix de référence (le pivot)
  mo_ht            numeric,     -- part main-d'oeuvre (pose)
  fourniture_ht    numeric,     -- part matériaux/fourniture
  temps_pose_h     numeric,     -- h / unité (si fourni)
  lot, famille     text,        -- nomenclature amont
  region           text,        -- si prix régionalisés
  raw_json         jsonb,       -- payload brut, on ne perd rien
  imported_at      timestamptz,
  UNIQUE (provider, millesime, code_provider, region)
);
Note d'implémentationLes noms d'endpoints et le schéma exact des champs viennent de la doc d'onboarding partenaire de l'éditeur retenu. On les adapte dans le mapper du ReferentialSyncService ; le reste du système ne voit que notre schéma normalisé ci-dessus.

3Mapping de notre base vers le référentiel

C'est le cœur fragile du système. À traiter comme un actif vivant, pas comme un import unique.

Pas de correspondance codée en dur : une table de mapping versionnée et administrable.

TABLE prestation_referential_map (
  prestation_id          text,    -- nos codes : PRE-001, SOL-008...
  referential_ouvrage_id bigint,  -- l'ouvrage amont
  millesime              int,
  coefficient            numeric, -- quantité d'ouvrage amont / unité de notre presta
  confidence             numeric, -- 0..1 : qualité du rapprochement
  validated_by           text,    -- humain ayant validé
  status                 text     -- mapped | review | unmapped
);

Deux contraintes à expliciter aux devs

Le mapping est rarement 1:1 → relation N..N avec composition

Notre base est au niveau « prestation de rénovation » et bundle (ex. WC suspendu fourni-posé = bâti + cuvette + plaque + raccordement). Le référentiel est souvent plus atomique. Une prestation Conceptuo peut donc valoir la somme de k ouvrages amont × coefficients. Le champ coefficient gère la composition.

Le mapping dérive à chaque millésime

Codes amont qui changent, ouvrages ajoutés/supprimés. À chaque sync, un contrôle doit détecter les referential_ouvrage_id orphelins (mappés mais disparus du nouveau millésime) et les passer en status = review. Sans ce garde-fou, le mapping pourrit silencieusement.

Amorçage du mapping

On réutilise le travail déjà fait (base de prestations propre + libellés connus). Rapprochement automatique par similarité (mots-clés métier + embeddings sur les libellés) qui propose des candidats avec un score de confiance ; un humain valide en back-office.

RègleAucun mapping auto-généré ne part en production sans revue humaine. La confidence sert à prioriser la file de validation, pas à valider automatiquement.

4Le prix récupéré devient le prix Standard

Une fois la prestation mappée :

prix_standard = Σ ( ouvrage.prix_moyen_ht × coefficient ) × coef_region

C'est le prix citable et opposable — celui qui porte la crédibilité. On stocke la provenance sur la ligne (provider + millésime + codes) pour pouvoir afficher « prix issu de Batiprix/Batichiffrage millésime 2026 » et tenir un audit.

Contrôle de cohérenceLà où on dispose des médianes Bernadette, on compare prix_standard à la médiane observée. Un écart fort = mapping à revoir ou positionnement à documenter.

5Définir Éco / Standard / Premium

À comprendre avant de coderLe référentiel donne UN prix moyen par ouvrage, pas trois gammes. Éco/Standard/Premium sont une construction Conceptuo. Un coefficient global plaqué (Éco = ×0,8, Premium = ×1,3) est arbitraire et contestable. On utilise la décomposition pose / fourniture.

Raisonnement métier

Pour une tâche donnée, la pose (main-d'œuvre) varie peu ; ce qui fait l'Éco vs Premium, c'est surtout le produit. On fige donc la pose et on fait varier la fourniture — ce qui s'aligne exactement avec l'architecture existante (typage POSE / F+P / FOURNITURE + base produits scrapée taguée Éco/Standard/Premium).

Règle par type de prestation

pricing_typeExemplesComment varient les gammes
avec produitcarrelage, parquet, vasque, robinetterie, portePose constante ; fourniture remplacée par le prix produit réel scrapé au tag de gamme (Éco/Std/Premium) de la linked_product_category
pose seuledépose, ragréage, démolitionPas de produit → coefficient de finition par famille (pas global), assumé comme un vrai écart de niveau de service

Formules

Prestation avec produit — on éclate le Standard amont en pose + fourniture, la pose reste constante :

prix_eco = mo_ht + fourniture_eco // produit tagué Éco
prix_standard = mo_ht + fourniture_standard // ≈ prix amont (calage)
prix_premium = mo_ht + fourniture_premium // produit tagué Premium

Prestation de pure main-d'œuvre — coefficient de finition par lot, paramétrable et documenté :

prix_eco = mo_ht × k_eco // ex. 0,85
prix_standard = mo_ht
prix_premium = mo_ht × k_premium // ex. 1,25

Exemple chiffré — SOL-008 « Pose carrelage sol » (avec produit)

ComposantÉcoStandardPremium
Pose (mo_ht, constante)45 €45 €45 €
Fourniture (produit scrapé selon gamme)30 €45 €75 €
Prix /m² HT75 €90 €120 €

Avantage : Éco et Premium ne sont plus inventés, ils sont adossés à des prix produits réels et sourcés (IKEA / Castorama / Leroy Merlin). Le Standard reste calé sur le référentiel, ce qui permet le contrôle mo_ht + fourniture_standard ≈ prix_moyen amont.

Communication / juridiqueOn ne revendique « prix Batiprix/Batichiffrage » que pour le Standard. Éco et Premium sont des dérivés Conceptuo (pose constante + produit réel, ou coefficient de finition) : à étiqueter comme tels dans le devis et l'UI. Assumé = argument de méthode ; caché = flanc d'attaque.

6Fallback : prestation absente de notre base

Quand le client demande une prestation hors de la base curée, on cherche dans le miroir local du référentiel (recherche plein-texte / sémantique sur les 28 000–80 000 ouvrages).

  • Jamais de substitution silencieuse. On présente les 3–5 ouvrages les plus proches avec leur score ; le pro (ou le client) choisit.
  • La ligne créée est marquée source = referential, is_assumption = true, et tarifée selon la même logique qu'au §5 (le prix amont devient son Standard, Éco/Premium dérivés).
  • Ces lignes ad hoc alimentent une file de revue en back-office. Si une prestation revient souvent, un admin la promeut dans la base canonique.
EffetCouverture quasi totale sans diluer la base curée : la base reste propre, le référentiel comble les trous à la demande.

7Indexation régionale & temporelle

Régional : si Batichiffrage est retenu, les prix sont déjà régionalisés (champ region). Sinon, on applique un coef_region Conceptuo documenté.

Temporel : on indexe entre la date du millésime et la date du devis via les indices officiels INSEE BT01 (national tous corps d'état) et BT50 (rénovation – entretien). Gratuits, publiés au Journal officiel — donc opposables.

coef_temps = indice_BT( date_devis ) / indice_BT( date_millesime )

8Hiérarchie des sources de prix

Trois sources vont coexister. La précédence doit être fixée une fois pour toutes :

PrioritéSourceRôle
1 (override)Prix custom TCEChaque entreprise sur sa propre base : écrase tout pour ses devis
2 (défaut générique)Référentiel (Batiprix/Batichiffrage)Prix par défaut Agences / Particuliers — l'ancre opposable
3 (calage interne)Médianes BernadetteContrôle de réalisme et calibrage des coefficients — pas affiché
ObservationLes médianes Bernadette ressortaient souvent au-dessus du Standard marché : c'est précisément l'écart que ce calage doit objectiver et documenter.

9Contrats d'API internes (Conceptuo)

Ce que notre backend expose au front et au microservice de génération de devis :

// Prix d'une prestation, toutes gammes, avec provenance
GET /api/prestations/{id}/pricing?region=IDF&date=2026-06-15
→ {
  "prestation_id": "SOL-008",
  "unit": "m2",
  "eco": 75, "standard": 90, "premium": 120,
  "source": "batiprix",
  "millesime": 2026,
  "provider_codes": ["..."],
  "derived": { "eco": true, "premium": true }  // gammes dérivées, pas sourcées
}

// Recherche fallback dans le miroir référentiel
POST /api/referential/search
{ "query": "pose receveur extra-plat 90x90", "limit": 5 }
→ [ { "ouvrage_id": ..., "libelle": "...", "unite": "u",
      "prix_moyen_ht": ..., "confidence": 0.82 }, ... ]

// État du mapping (back-office)
GET /api/admin/mapping?status=review&millesime=2026

10Points de vigilance

1 — Licence : préalable bloquantCes API/exports sont conçus pour qu'un pro licencié chiffre ses propres travaux. Le cas « Agences » et surtout « Particuliers » implique d'afficher les prix amont à un tiers final et de les stocker durablement. Faire confirmer par écrit que la licence éditeur autorise cette rediffusion B2B2C et le stockage local — avant toute ligne de code. (Avis non juridique : valider avec l'éditeur et un conseil.)
2 — « Incontestable » ne couvre que le StandardLe référentiel ne donne ni les gammes, ni (selon le produit) la régionalisation fine, ni nos bundles. Standard = sourcé ; Éco/Premium/régional = dérivés Conceptuo. À assumer dans l'UX comme une méthode explicite.
3 — Le mapping est vivantBudgétiser le re-rapprochement à chaque millésime, le score de confiance, la détection d'orphelins et une vraie UI de validation. C'est là que ça casse silencieusement si personne ne maintient.
4 — Granularité 1:NConcevoir le mapping en composition (many-to-many) dès le départ. Un refactor a posteriori pour passer de 1:1 à N..N est coûteux.

11Prérequis & séquencement

Ne pas lancer l'intégration API tant que :

  • (1) la licence autorisant l'affichage B2B2C + stockage local n'est pas confirmée par écrit ;
  • (2) le contrat de mapping (composition N..N, coefficients, gestion du millésime) n'est pas tranché.

Ces deux décisions conditionnent tout le schéma de données. Une fois actées : sync miroir → amorçage + revue du mapping → moteur de prix Standard → dérivation gammes (lot pilote : Sols, où pose/fourniture se séparent proprement) → fallback → indexation.

Lot pilote conseilléValider toute la mécanique Éco/Standard/Premium sur le lot Sols avant de généraliser : décomposition pose/fourniture nette, produits scrapés disponibles, médianes Bernadette pour contrôle.

Conceptuo — spécification de travail. Contenu à valider avant implémentation (notamment le préalable licence).