# 02 — Prix non typés + surfaces machine-readable contradictoires

> **Spec** : Structured data for agents — tier **recommended**
> **Verdict** : 🟠 **PARTIAL** (le reste du JSON-LD est bon — le prix est le maillon faible) · **Priorité : P1**
> **Effort estimé** : 0,5-1 jour (source de prix unique + templates)
> **Lié** : [SEO issue 09](../../audit-seo/issues/09-structured-data.md) (`unitText` détourné, offres sans prix — même chantier)

## Constat 1 — Le prix n'est jamais typé

La donnée n°1 qu'un agent cherche pour une prestation locale (« combien ça coûte ? ») n'existe **nulle part** sous forme de champ :

| Page | JSON-LD | Prix typé ? |
|---|---|---|
| `/` (accueil) | LocalBusiness + OfferCatalog 4 offres | 1 offre sur 4 (80 €, avec `unitText: "à partir de"` détourné) |
| `/tarifs` — **la page dont c'est l'objet** | LocalBusiness + OfferCatalog 4 offres | **0 offre sur 4** avec un `price` — vérifié programmatiquement. Seul `priceRange: "€€"` (symbole vague) |
| `/nid-guepes-frelons-yerres-91330` (template ×622) | LocalBusiness localisé + FAQPage | **0** — le prix n'existe qu'en **prose** dans une réponse FAQ : « varie entre 90€ et 150€ selon l'accessibilité » |
| `/presentation` (service) | LocalBusiness nu | 0 (pas même de nœud Service dédié) |

Un agent doit faire du NLU sur une phrase française pour extraire un nombre qui devrait être un champ.

## Constat 2 — Les sources se contredisent (problème de confiance)

| Surface | Prix annoncé (destruction nid de guêpes) |
|---|---|
| `/llms.txt` (site-wide) | « Tarifs **à partir de 80 euros** » |
| Page Yerres, prose FAQ | « varie **entre 90€ et 150€** » |
| JSON-LD accueil | `price: "80"` |

Un agent qui lit `/llms.txt` puis la page ville — le workflow le plus plausible — rapporte **deux prix différents** à l'utilisateur. C'est exactement le problème de confiance que la donnée typée mono-source doit prévenir. (Et si le « à partir de 80 € » n'est plus vrai en 2026, c'est aussi un problème client/juridique d'affichage de prix.)

## Ce qui est déjà bon (à préserver)

Le smoke test agent sur la page Yerres réussit 3/4 :
- ✅ `telephone: "+33631266384"` typé + 4 liens `tel:`
- ✅ Zone : `addressLocality: Yerres` + `areaServed` en **GeoCircle 20 km centré sur la ville** (localisation par page — très bien fait)
- ✅ Chemin de contact (`/contact`, `tel:`)
- ❌ Prix (ce document)

## Correctif

### 1. Une source de vérité unique pour les prix

Créer une table de prix unique dans les données du site (ex. `src/data/pricing.js`) : service × (minPrice, maxPrice, conditions). **Tout** en dérive : JSON-LD, prose des FAQ, page `/tarifs`, `/llms.txt`, `/llms-full.txt`.

### 2. Typer les offres partout (accueil, `/tarifs`, pages villes/départements)

```json
{
  "@type": "Offer",
  "itemOffered": { "@type": "Service", "name": "Destruction nid de guêpes" },
  "priceSpecification": {
    "@type": "PriceSpecification",
    "priceCurrency": "EUR",
    "minPrice": 90,
    "maxPrice": 150
  },
  "areaServed": { "@type": "City", "name": "Yerres" }
}
```

- `minPrice`/`maxPrice` remplacent le hack `unitText: "à partir de"` ([SEO issue 09](../../audit-seo/issues/09-structured-data.md)).
- Si les prix varient par zone, la table par service×zone alimente le template ville — la prose FAQ et le JSON-LD de chaque page disent alors **la même chose par construction**.

### 3. Réconcilier `/llms.txt` et `/llms-full.txt`

Régénérer les deux fichiers depuis la même table à chaque build (ils sont déjà versionnés/datés — bonne base). Ne plus jamais y écrire un prix à la main.

## Vérification

- Script de cohérence au build : extraire tous les montants (JSON-LD `minPrice`/`maxPrice`, regex `\d+\s*€` dans les pages, llms*.txt) → l'ensemble des valeurs par service doit être identique. Échec de build sinon.
- Rejouer le smoke test : depuis le HTML brut de la page Yerres seule, un agent doit extraire prix min/max **sans parser de prose**.
- validator.schema.org : `PriceSpecification` valide sur `/`, `/tarifs`, une page ville.
