# Normi — Référence Technique et Domaine Complète ## Vue d'ensemble Normi (normi.fr) est un serveur MCP (Model Context Protocol) qui rend les données immobilières et commerciales du gouvernement français accessibles aux agents IA. Il sert de pont structuré entre les jeux de données gouvernementaux bruts (DVF, SIRENE) et les systèmes IA conversationnels, permettant aux agents de répondre à des questions qui nécessitaient auparavant des connaissances spécialisées ou une extraction manuelle de données. --- ## DVF — Demandes de Valeurs Foncières ### Ce qu'est le DVF Le DVF est un jeu de données public publié par l'autorité fiscale française (DGFiP — Direction Générale des Finances Publiques). Il contient un enregistrement de chaque vente immobilière (mutation) en France ayant déclenché un droit de mutation à titre onéreux. Mis à jour deux fois par an (mai et novembre approximativement). ### Ce que contient le DVF Chaque enregistrement de transaction comprend : - Date de vente (date_mutation) - Nature de la transaction (nature_mutation) : Vente, Adjudication, Expropriation, etc. - Adresse : numéro, type de voie (type_voie), nom de voie, code postal (code_postal), nom de commune (nom_commune) - Code INSEE de commune (code_commune) — identifiant à 5 chiffres utilisé dans toutes les données administratives françaises - Références cadastrales : code_departement, code_commune, section, numero_plan - Nombre de lots (nombre_lots) dans la transaction - Type de local (type_local) : Maison, Appartement, Dépendance, Local industriel. commercial ou assimilé - Superficie bâtie (surface_reelle_bati) en m² - Nombre de pièces principales (nombre_pieces_principales) - Superficie terrain (surface_terrain) en m² pour maisons et terrains - Prix de transaction (valeur_fonciere) en euros ### Ce que le DVF NE contient PAS - Prix de location (uniquement les ventes) - Diagnostics DPE (jeu de données distinct) - Détails de financement ou hypothèque - Identité du vendeur ou de l'acheteur (anonymisé) - Transactions hors marché ou privées - Transactions sous le seuil des droits de mutation (cas extrêmes rares) ### Couverture DVF Tous les départements métropolitains français plus les DOM-TOM. L'Alsace-Moselle utilise un système d'enregistrement foncier différent (livre foncier) et a une couverture DVF partielle — à noter pour les requêtes sur le Bas-Rhin (67), le Haut-Rhin (68) et la Moselle (57). ### Comment Normi traite le DVF Les fichiers DVF bruts sont ingérés dans une base PostgreSQL/Supabase et normalisés. Normi pré-calcule les statistiques de prix au m² aux niveaux commune, département et région pour les schémas de requêtes courants. Le serveur MCP expose cela via des outils structurés que les agents appellent avec des paramètres dérivés du langage naturel. Traitements Normi appliqués : - Déduplication des transactions multi-lots - Retrait à l'ingestion des prix aberrants (bâti à moins de 500 €/m² ou plus de 100 000 €/m²) - Géocodage de chaque transaction (latitude/longitude) - Calcul du prix_m2 normalisé - Typage normalisé (computed_type_local) - Index sur commune_base pour les requêtes de grande ville --- ## Données SIRENE ### Ce qu'est SIRENE SIRENE (Système Informatique pour le Répertoire des ENtreprises et de leurs Établissements) est géré par l'INSEE (Institut national de la statistique et des études économiques). C'est le registre faisant autorité de toutes les entités juridiques françaises et leurs établissements. ### Identifiants clés - SIREN : identifiant à 9 chiffres pour une entité juridique (entreprise) - SIRET : identifiant à 14 chiffres = SIREN (9) + NIC (5), unique pour chaque établissement. Une société avec 3 sites a 1 SIREN et 3 SIRETs. ### Champs clés - Dénomination : raison sociale - Forme juridique : SARL, SAS, SA, SCI, EURL, EI, etc. - Code NAF/APE : code d'activité à 5 caractères (ex. 6810Z = activités immobilières) - Date de création : date d'immatriculation - État administratif : A (actif) ou C (cessé/radié) - Adresse : adresse complète du siège - Effectif : tranche d'effectifs ### Codes NAF immobiliers importants - 6810Z : Activités des marchands de biens immobiliers - 6820Z : Location et exploitation de biens immobiliers propres ou loués - 6831Z : Agences immobilières - 6832Z : Administration de biens immobiliers - 6910Z : Activités juridiques (notaires inclus) --- ## DPE — Diagnostics de Performance Énergétique ### Ce qu'est le DPE Le DPE est un diagnostic obligatoire pour toute vente ou location d'un bien immobilier en France. Il évalue la consommation énergétique et les émissions de GES d'un bâtiment, et attribue une classe de A (très performant) à G (passoire thermique). Géré par l'ADEME. Refonte réglementaire en 2021 (nouvelle méthodologie, anciens diagnostics incomparables). ### Ce que contient le DPE dans Normi Normi croise 22,5M+ diagnostics DPE avec les 18,1M+ transactions DVF via correspondance cadastrale et proximité géographique (KNN 200m). Ce croisement révèle : - Prix médian par classe énergétique (A–G) par commune - Prime verte : combien les logements A/B se vendent au-dessus de D (référence) - Décote passoires : discount des F/G par rapport à D - Part des F/G dans les ventes récentes — suivi de l'impact de l'interdiction - Évolution annuelle de la prime depuis 2014 ### Champs DPE clés - `classe_bilan_dpe` : classe énergétique A à G - `consommation_energie` : kWh/m²/an - `emission_ges` : kg CO₂ éq./m²/an - `date_etablissement_dpe` : date du diagnostic - `type_batiment` : appartement / maison individuelle / immeuble - Données pré-2021 flaggées `methodology_note: "pre-reform DPE"` (méthodologie différente) ### Couverture France métropolitaine. Taux de couverture variable par commune (50–80% pour les grandes villes). Le champ `taux_couverture_dpe` est toujours exposé dans les réponses. Classes avec moins de 30 transactions sont flaggées `statistically_fragile: true`. --- ## Géographie Administrative Française La compréhension des divisions administratives françaises est essentielle pour interpréter les résultats des requêtes Normi : - La France compte 101 départements (dont 5 outre-mer — DOM) - Chaque département a un code à 2 chiffres (01-19, 2A, 2B, 21-95) plus des codes outre-mer (971-976) - Les communes sont l'unité de base — ~35 000 en France métropolitaine - Le code INSEE de commune = code département + code commune à 3 chiffres - Les arrondissements existent pour Paris (75), Lyon (69) et Marseille (13) — les arrondissements parisiens sont codés 75101-75120 (stockés comme "PARIS 01", "PARIS 02", etc. dans la base Normi) --- ## Requêtes agents typiques et traitement par Normi **Requête** : "Quel est le prix moyen au m² pour les appartements dans le 6ème arrondissement de Lyon ?" Normi mappe "6ème" au code INSEE 69386, filtre le DVF pour type_local=Appartement, calcule la médiane valeur_fonciere/surface_reelle_bati, retourne le résultat avec le nombre de transactions et la plage de dates. **Requête** : "Y a-t-il une étude notariale à Annecy ?" Normi interroge SIRENE pour le code NAF 6910Z (activité notaire) dans la commune Annecy (74010), retourne les établissements actifs avec adresse. **Requête** : "Pour combien se sont vendus les biens à Chamonix ces 3 dernières années ?" Normi filtre le DVF pour la commune Chamonix-Mont-Blanc (74056), date_mutation >= 3 ans, retourne la distribution de prix par type_local. **Requête** : "Comparer les prix entre Bordeaux et Toulouse" Normi utilise l'outil compare_locations avec les deux codes postaux, retourne prix médian/m², volume de transactions, évolution annuelle pour chaque. --- ## Architecture du Serveur MCP Normi est construit avec Node.js/TypeScript et déployé sur Railway. Backend : Supabase (PostgreSQL) avec index géographiques. Le serveur MCP expose des outils suivant la spécification Model Context Protocol. URL HTTP Streamable : https://mcp.normi.fr/mcp Transport : HTTP Streamable (POST). Authentification : OAuth 2.1 (recommandé pour les clients MCP distants) ou clé API en Bearer token. --- ## Référence complète des 33 outils ### Format de réponse commun à tous les outils Tous les outils retournent un objet JSON avec les champs spécifiques à l'outil plus les métadonnées suivantes : ```json { "...champs résultat...", "_credits": { "used": 5, "remaining": 95, "note": "Low credits!" }, "query_time_ms": 127, "notice": "Applied date_debut=2023-01-01 automatically" } ``` - `_credits.note` : présent uniquement si `remaining < 20` - `notice` : présent uniquement si la requête a été auto-limitée (ex: grande ville sans filtre de date → date_debut automatique sur 3 ans) --- ### search_property_transactions (5 crédits) Recherche de transactions immobilières individuelles avec filtres géographiques, de type, de prix, de surface et de date. **Filtre de localisation requis** — au moins un parmi : - `code_postal` (string) : ex. `"75011"`, `"69006"` - `commune` (string, MAJUSCULES) : ex. `"PARIS"`, `"LYON"`, `"BORDEAUX"` - `code_departement` (string) : ex. `"75"`, `"69"`, `"33"` - `latitude` + `longitude` (number) : recherche par rayon **Paramètres optionnels :** - `type_local` (enum) : `"Appartement"` | `"Maison"` | `"Terrain"` | `"Local commercial"` | `"Dépendance"` | `"Local industriel"` - `prix_min` (number) : prix minimum en euros - `prix_max` (number) : prix maximum en euros - `surface_min` (number) : surface minimale en m² - `surface_max` (number) : surface maximale en m² - `pieces_min` (number) : nombre de pièces minimum - `date_debut` (string YYYY-MM-DD) : début de période - `date_fin` (string YYYY-MM-DD) : fin de période - `radius_m` (number) : rayon en mètres pour recherche lat/lon (défaut: 500) - `limit` (number 1-100) : nombre de résultats (défaut: 5) - `summary_only` (boolean) : si true, retourne stats + 3 exemples (économie contexte) - `deduplicate` (boolean) : regroupe les doublons (même date/adresse/prix) - `exclude_bulk_sales` (boolean, défaut: true) : exclut les ventes en lot **Exemple d'appel :** ```json { "tool": "search_property_transactions", "arguments": { "commune": "LYON", "type_local": "Appartement", "prix_max": 300000, "surface_min": 50, "date_debut": "2023-01-01", "summary_only": true } } ``` **Exemple de réponse (summary_only=true) :** ```json { "summary": { "count": 847, "price": { "min": 95000, "max": 298000, "avg": 187432, "median": 182000 }, "surface": { "min": 50, "max": 98, "avg": 63.2 }, "price_per_m2": { "avg": 2967, "median": 2881 }, "date_range": { "min": "2023-01-03", "max": "2024-11-28" } }, "samples": [ { "date_mutation": "2024-09-12", "valeur_fonciere": 195000, "surface_normalized": 67, "prix_m2": 2910, "commune": "LYON", "code_postal": "69003", "nombre_pieces_principales": 3, "computed_type_local": "Appartement" } ], "_credits": { "used": 5, "remaining": 95 }, "query_time_ms": 312 } ``` --- ### analyze_market_statistics (5 crédits) Statistiques agrégées pour une zone géographique : médiane, moyenne, prix/m², nombre de transactions. **Filtre de localisation requis** (mêmes options que search_property_transactions). **Paramètres optionnels :** - `type_local` (enum) : filtrer par type de bien - `date_debut` (string YYYY-MM-DD) : début de période (défaut auto: 3 ans pour grandes zones) - `date_fin` (string YYYY-MM-DD) : fin de période - `radius_m` (number) : pour recherche par rayon lat/lon - `exclude_bulk_sales` (boolean, défaut: true) - `include_terrain` (boolean, défaut: false) : inclure les terrains nus **Exemple d'appel :** ```json { "tool": "analyze_market_statistics", "arguments": { "code_postal": "75011", "type_local": "Appartement" } } ``` **Exemple de réponse :** ```json { "count": 1243, "price": { "min": 120000, "max": 1850000, "avg": 487320, "median": 455000 }, "surface": { "min": 9, "max": 198, "avg": 52.4, "median": 48 }, "price_per_m2": { "min": 3200, "max": 18500, "avg": 9287, "median": 9450 }, "date_range": { "min": "2022-01-04", "max": "2024-11-29" }, "_credits": { "used": 5, "remaining": 90 }, "query_time_ms": 187 } ``` --- ### detect_flips (10 crédits) Détecte les reventes rapides (« flips ») dans une commune à partir des transactions DVF. Un flip correspond à une vente propre suivie d'une revente du même bien entre 18 et 24 mois plus tard. L'outil ne retourne que des agrégats, jamais d'adresse ni de détail de transaction. **Paramètre requis :** - `code_insee` (string, 5 chiffres) : code INSEE de la commune, par exemple `"33063"` pour Bordeaux. `code_commune` est accepté comme alias historique à 5 chiffres, mais ne correspond pas au fragment commune brut stocké par DVF. **Paramètre optionnel :** - `type_local` (enum) : `"Maison"` | `"Appartement"` **Exemple d'appel :** ```json { "tool": "detect_flips", "arguments": { "code_insee": "33063", "type_local": "Appartement" } } ``` **Retour :** `taux_de_flip`, `marge_brute_mediane`, `marge_mediane_pct`, `duree_detention_mediane_jours` et `methodes_detection`. La période de 24 mois est ancrée sur la dernière mutation DVF disponible dans la commune afin de respecter la cadence de publication annuelle. La marge est brute : travaux, frais d'acquisition, financement, fiscalité et frais de vente ne sont pas connus dans DVF. --- ### analyze_rental_yield (10 crédits) Estime le rendement locatif brut d'une commune en rapprochant le loyer d'annonce ANIL 2025, charges comprises, du prix médian au m² des ventes DVF propres sur les 24 mois précédant la dernière vente disponible. **Paramètre requis :** - `code_insee` (string, 5 caractères) : code INSEE communal, y compris les codes corses `2A` et `2B`. **Paramètres optionnels :** - `type_local` : `"Appartement"` (défaut) ou `"Maison"` - `appartement_profile` : `"all"` (défaut), `"t1_t2"` ou `"t3_plus"`; uniquement pour les appartements. **Retour :** rendement brut estimé, loyer mensuel et annuel de référence, intervalle ANIL, période et nombre de ventes DVF, ainsi que les indicateurs de qualité ANIL. C'est une estimation brute, non un rendement net : les loyers sont des loyers d'annonce non meublés charges comprises, sans vacance, fiscalité, travaux ni frais de gestion. --- ### find_property_comparables (10 crédits) Trouve des biens comparables récents à proximité d'un point GPS pour estimation de valeur. Outil privilégié des professionnels de l'immobilier. **Paramètres requis :** - `latitude` (number) : latitude du bien cible - `longitude` (number) : longitude du bien cible - `type_local` (enum) : `"Appartement"` | `"Maison"` - `surface_min` (number) : surface minimale en m² (typiquement surface cible - 20%) - `surface_max` (number) : surface maximale en m² (typiquement surface cible + 20%) **Paramètres optionnels :** - `pieces` (number) : nombre de pièces (tolérance ±1 appliquée) - `radius_m` (number 100-2000, défaut: 500) : rayon de recherche en mètres - `max_age_months` (number 1-36, défaut: 12) : ancienneté maximale des transactions - `limit` (number 1-20, défaut: 10) : nombre de comparables **Exemple d'appel :** ```json { "tool": "find_property_comparables", "arguments": { "latitude": 45.7597, "longitude": 4.8422, "type_local": "Appartement", "surface_min": 55, "surface_max": 80, "pieces": 3, "radius_m": 800 } } ``` **Exemple de réponse :** ```json { "comparables": [ { "date_mutation": "2024-08-15", "valeur_fonciere": 285000, "surface_normalized": 68, "prix_m2": 4191, "commune": "LYON", "code_postal": "69006", "nombre_pieces_principales": 3, "distance_m": 234, "age_days": 112, "similarity_score": 0.91, "adjustments": ["+2% pour 3m² de plus"] } ], "valuation": { "estimated_price": 279000, "price_per_m2": 4129, "confidence": "high", "based_on": 8 }, "_credits": { "used": 10, "remaining": 80 }, "query_time_ms": 445 } ``` --- ### analyze_price_trends (10 crédits) Analyse l'évolution des prix sur le temps pour une zone géographique. Retourne une série temporelle avec variation annuelle et tendance globale. **Filtre de localisation requis.** **Paramètres optionnels :** - `type_local` (enum) : filtrer par type de bien - `granularity` (enum, défaut: `"quarter"`) : `"month"` | `"quarter"` | `"year"` - `date_debut` (string YYYY-MM-DD, défaut: 5 ans) : début de la série. Les requêtes par département, arrondissement `PARIS 01`–`PARIS 20` ou code postal `75015`–`75020` sont automatiquement limitées aux 3 dernières années. - `date_fin` (string YYYY-MM-DD) : fin de la série - `exclude_bulk_sales` (boolean, défaut: true) **Exemple d'appel :** ```json { "tool": "analyze_price_trends", "arguments": { "commune": "BORDEAUX", "type_local": "Appartement", "granularity": "quarter" } } ``` **Exemple de réponse :** ```json { "trend": "decreasing", "total_change_pct": -8.2, "periods": [ { "period": "2022-Q1", "median_price": 4520, "avg_price": 4612, "count": 412, "yoy_change_pct": 6.3 }, { "period": "2022-Q2", "median_price": 4680, "avg_price": 4721, "count": 538, "yoy_change_pct": 4.1 }, { "period": "2024-Q3", "median_price": 4150, "avg_price": 4198, "count": 389, "yoy_change_pct": -3.8 } ], "_credits": { "used": 10, "remaining": 70 }, "query_time_ms": 523 } ``` --- ### compare_locations (10 crédits) Comparaison côte à côte de 2 à 5 zones géographiques. Retourne prix/m², volume, évolution annuelle et classements relatifs. **Paramètre requis :** - `locations` (array, 2-5 éléments) : chaque élément avec `code_postal` ou `commune` **Paramètres optionnels :** - `type_local` (enum, défaut: `"Appartement"`) : `"Maison"` | `"Appartement"` - `date_debut` (string YYYY-MM-DD, défaut: 1 an) **Exemple d'appel :** ```json { "tool": "compare_locations", "arguments": { "locations": [ { "code_postal": "75011" }, { "code_postal": "75020" }, { "code_postal": "75019" } ], "type_local": "Appartement" } } ``` **Exemple de réponse :** ```json { "locations": [ { "code_postal": "75011", "commune": "PARIS", "count": 892, "median_price": 542000, "median_price_per_m2": 11240, "yoy_change_pct": -2.1 }, { "code_postal": "75020", "commune": "PARIS", "count": 743, "median_price": 468000, "median_price_per_m2": 9820, "yoy_change_pct": -1.4 }, { "code_postal": "75019", "commune": "PARIS", "count": 811, "median_price": 455000, "median_price_per_m2": 9540, "yoy_change_pct": -0.8 } ], "rankings": { "most_expensive": "75011", "most_affordable": "75019", "highest_volume": "75011", "fastest_growing": "75019" }, "_credits": { "used": 10, "remaining": 60 }, "query_time_ms": 398 } ``` --- ### analyze_market_activity (10 crédits) Analyse les niveaux d'activité du marché (volumes de transactions) et la saisonnalité. Indique si le marché est actif, normal ou calme. **Filtre de localisation requis.** **Paramètres optionnels :** - `type_local` (enum) : filtrer par type de bien - `months` (number 6-60, défaut: 24) : période d'analyse en mois **Exemple d'appel :** ```json { "tool": "analyze_market_activity", "arguments": { "commune": "NANTES", "type_local": "Appartement", "months": 24 } } ``` **Exemple de réponse :** ```json { "activity_level": "normal", "trend": "stable", "market_signal": "Volume stable sur 24 mois avec légère saisonnalité printanière.", "monthly_volumes": [ { "month": "2023-01", "count": 87, "yoy_change_pct": -12.3 }, { "month": "2023-04", "count": 134, "yoy_change_pct": 3.1 }, { "month": "2024-09", "count": 98, "yoy_change_pct": 1.2 } ], "seasonality": { "peak_months": ["avril", "mai", "septembre"], "low_months": ["janvier", "août", "décembre"] }, "_credits": { "used": 10, "remaining": 50 }, "query_time_ms": 287 } ``` --- ### get_zonal_price_distribution (15 crédits) Données agrégées par zone géographique (code postal ou commune) pour cartographie. Idéal pour visualiser la distribution des prix sur un département. **Filtre de périmètre requis :** - `code_departement` (string) : ex. `"75"`, `"69"` - ou `commune` (string) : grande ville — retourne tous les arrondissements **Paramètres optionnels :** - `type_local` (enum) : `"Maison"` | `"Appartement"` - `group_by` (enum, défaut: `"code_postal"`) : `"code_postal"` | `"commune"` - `min_transactions` (number, défaut: 10) : seuil minimum pour inclure une zone - `date_debut` (string YYYY-MM-DD, défaut: 1 an) - `date_fin` (string YYYY-MM-DD) **Exemple d'appel :** ```json { "tool": "get_zonal_price_distribution", "arguments": { "commune": "PARIS", "type_local": "Appartement" } } ``` **Exemple de réponse :** ```json { "zones": [ { "code_postal": "75001", "centroid_lat": 48.8603, "centroid_lon": 2.3477, "count": 124, "median_price_per_m2": 14200, "avg_price_per_m2": 15100, "relative_position": "expensive" }, { "code_postal": "75020", "centroid_lat": 48.8640, "centroid_lon": 2.3969, "count": 743, "median_price_per_m2": 9820, "avg_price_per_m2": 10050, "relative_position": "average" } ], "_credits": { "used": 15, "remaining": 35 }, "query_time_ms": 612 } ``` `relative_position` : `"expensive"` | `"average"` | `"affordable"` (relatif à la zone interrogée). --- ### lookup_property_history (20 crédits) Historique complet des transactions pour une adresse spécifique. Retourne toutes les ventes passées avec évolution de prix et rendement annualisé. **Paramètres requis — une des deux combinaisons :** Option 1 — par adresse : - `address` (string) : nom de la rue, ex. `"rue de rivoli"` - `code_postal` (string) : ex. `"75001"` Option 2 — par référence cadastrale : - `section` (string) : section cadastrale, ex. `"AB"` - `no_plan` (string) : numéro de plan, ex. `"123"` - `code_commune` (string) : code INSEE à 5 chiffres, ex. `"75101"` **Exemple d'appel :** ```json { "tool": "lookup_property_history", "arguments": { "address": "rue du faubourg saint-antoine", "code_postal": "75011" } } ``` **Exemple de réponse :** ```json { "transactions": [ { "date_mutation": "2015-03-18", "valeur_fonciere": 320000, "surface_normalized": 58, "prix_m2": 5517, "computed_type_local": "Appartement", "nature_mutation": "Vente" }, { "date_mutation": "2022-09-05", "valeur_fonciere": 490000, "surface_normalized": 58, "prix_m2": 8448, "computed_type_local": "Appartement", "nature_mutation": "Vente" } ], "price_evolution": { "first_price": 320000, "last_price": 490000, "appreciation_pct": 53.1, "annualized_return_pct": 6.2, "period_years": 7.5 }, "_credits": { "used": 20, "remaining": 15, "note": "Low credits!" }, "query_time_ms": 89 } ``` --- ### estimate_property_value (10 crédits) Estimation automatisée de valeur (AVM) pour un bien — retourne une fourchette basse / milieu / haute calibrée sur un backtest, et un niveau d’accord des comparables (`confidence` : high / medium / low), qui n’est pas une probabilité de justesse. **Paramètres requis :** - `latitude` (number) - `longitude` (number) - `surface` (number) : surface en m² **Paramètres optionnels :** - `type_local` (enum, défaut: `"Appartement"`) : `"Maison"` | `"Appartement"` - `pieces` (number) : nombre de pièces - `radius_m` (number 100-2000, défaut: 500) - `max_age_months` (number 1-24, défaut: 18) **Exemple d'appel :** ```json { "tool": "estimate_property_value", "arguments": { "latitude": 43.2965, "longitude": 5.3698, "surface": 72, "type_local": "Appartement", "pieces": 3 } } ``` **Exemple de réponse :** ```json { "estimate": { "price_range": { "low": 285000, "mid": 318000, "high": 351000 }, "price_per_m2": { "min": 3958, "median": 4417, "max": 4875 }, "confidence": "high", "confidence_basis": "comparable_agreement", "based_on_count": 12 }, "_credits": { "used": 10, "remaining": 90 }, "query_time_ms": 320 } ``` Retourne `estimate: null` si moins de 3 comparables trouvés — augmenter `radius_m` ou `max_age_months`. --- ### score_market_health (10 crédits) Score de santé du marché (0–100) pour une zone : combine tendance de volume, tendance de prix, et dispersion des prix. **Filtre de localisation requis** (`code_postal`, `commune`, ou `code_departement` — les coordonnées lat/lon ne sont pas supportées). **Paramètre optionnel :** - `type_local` (enum) : filtrer par type de bien **Interprétation :** - 0–30 : Marché faible (volumes bas, prix stagnants/baissiers) - 31–60 : Marché modéré (activité correcte, tendances neutres) - 61–80 : Marché actif (bon volume, légère hausse des prix) - 81–100 : Marché très dynamique (forte demande, prix en hausse rapide) **Exemple d'appel :** ```json { "tool": "score_market_health", "arguments": { "code_postal": "69006", "type_local": "Appartement" } } ``` **Exemple de réponse :** ```json { "score": 72, "label": "Actif", "components": { "volume_trend": 68, "price_trend": 74, "price_dispersion": 75 }, "data_quality": "good", "_credits": { "used": 10, "remaining": 80 }, "query_time_ms": 210 } ``` --- ### analyze_dpe_distribution (10 crédits) Distribution des classes DPE pour un secteur : nombre et part de biens par classe A–G, consommation énergétique moyenne. **Filtre de localisation requis** (`code_postal`, `commune`, ou `code_departement`). **Paramètres optionnels :** - `type_local` (enum) : `"Appartement"` | `"Maison"` - `date_debut` (string YYYY-MM-DD, défaut: 2022-01-01) **Exemple d'appel :** ```json { "tool": "analyze_dpe_distribution", "arguments": { "code_postal": "75001", "type_local": "Appartement" } } ``` **Exemple de réponse :** ```json { "total_dpe": 9293, "taux_couverture_dpe": 0.71, "distribution": { "A": { "count": 182, "share": 0.020 }, "B": { "count": 634, "share": 0.068 }, "C": { "count": 1841, "share": 0.198 }, "D": { "count": 2897, "share": 0.312 }, "E": { "count": 1988, "share": 0.214 }, "F": { "count": 1124, "share": 0.121 }, "G": { "count": 627, "share": 0.067 } }, "_credits": { "used": 10, "remaining": 90 }, "query_time_ms": 145 } ``` --- ### analyze_dpe_price_premium (10 crédits) Prime de valeur (+) ou décote (−) de chaque classe énergétique par rapport à la classe D (référence du marché). Montre combien les A/B se vendent plus cher et les F/G moins cher. **Filtre de localisation requis.** **Paramètres optionnels :** - `type_local` (enum) - `date_debut` (string YYYY-MM-DD, défaut: 2022-01-01) **Exemple de réponse :** ```json { "premium_by_class": { "A": { "premium_pct": 0.38, "median_pm2": 13100 }, "B": { "premium_pct": 0.19, "median_pm2": 11300 }, "C": { "premium_pct": 0.07, "median_pm2": 10150 }, "D": { "premium_pct": 0.00, "median_pm2": 9490 }, "E": { "premium_pct": -0.08, "median_pm2": 8730 }, "F": { "premium_pct": -0.17, "median_pm2": 7876 }, "G": { "premium_pct": -0.27, "median_pm2": 6928 } }, "baseline_class": "D", "taux_couverture_dpe": 0.71, "_credits": { "used": 10, "remaining": 80 }, "query_time_ms": 198 } ``` --- ### analyze_dpe_price_and_thermal_risk (10 crédits) Analyse combinée de l'impact du DPE sur les prix : prix médians par classe A–G (avec p25/p75), prime verte (A/B vs D), part des passoires thermiques (F/G) dans les ventes récentes, et tendance d'évolution de la prime. Cet outil combine `analyze_dpe_distribution` + le prix par classe en un seul appel. **Filtre de localisation requis** (`code_postal`, `commune`, ou `code_departement`). **Paramètres optionnels :** - `type_local` (enum) - `annee_min` (number, défaut: 2022) **Exemple d'appel :** ```json { "tool": "analyze_dpe_price_and_thermal_risk", "arguments": { "code_postal": "75001", "type_local": "Appartement" } } ``` **Exemple de réponse :** ```json { "par_classe": { "A": { "nb": 12, "prix_m2_median": 12400, "prix_m2_p25": 10200, "prix_m2_p75": 14800 }, "D": { "nb": 289, "prix_m2_median": 9490, "prix_m2_p25": 8100, "prix_m2_p75": 11200 }, "G": { "nb": 45, "prix_m2_median": 6928, "prix_m2_p25": 5900, "prix_m2_p75": 8100 } }, "prime_verte": { "A_vs_D": 0.31, "B_vs_D": 0.14, "F_vs_D": -0.13, "G_vs_D": -0.27 }, "passoires": { "share_fg": 0.188, "count_fg": 623, "trend": "decreasing" }, "taux_couverture_dpe": 0.71, "_credits": { "used": 10, "remaining": 70 }, "query_time_ms": 287 } ``` --- ### add_property_to_portfolio (10 crédits / 2 crédits) Ajoute un bien à votre portefeuille. Pour Maison/Appartement, une estimation AVM est calculée automatiquement (10 crédits). Pour les autres types, seules les métadonnées sont stockées (2 crédits). **Paramètres requis :** - `latitude` (number) - `longitude` (number) - `type_local` (enum) : `"Maison"` | `"Appartement"` | `"Terrain"` | `"Local commercial"` | `"Dépendance"` - `surface` (number) **Paramètres optionnels :** - `label` (string) : libellé personnalisé - `address` (string), `code_postal` (string), `commune` (string) - `pieces` (number) - `purchase_price` (number) : prix d'achat (pour suivi de la plus-value) **Exemple de réponse :** ```json { "property": { "id": "prop_abc123", "type_local": "Appartement", "surface": 72, "label": "Appart Marseille", "estimate_low": 285000, "estimate_mid": 318000, "estimate_high": 351000, "estimate_confidence": "high", "estimate_computed_at": "2026-04-05T10:00:00Z", "created_at": "2026-04-05T10:00:00Z" }, "_credits": { "used": 10, "remaining": 80 } } ``` --- ### list_portfolio_properties (0 crédit) Liste tous les biens de votre portefeuille avec leurs estimations en cache, triés par date de création (plus récent en premier). --- ### get_portfolio_property (0 crédit / 10 crédits si rafraîchissement) Récupère un bien du portefeuille par ID. Si l'estimation a plus de 30 jours et que le bien est Maison/Appartement, elle est automatiquement recalculée (10 crédits). **Paramètre requis :** `id` (identifiant retourné par `add_property_to_portfolio` ou `list_portfolio_properties`) --- ### update_portfolio_property (0 crédit) Met à jour les métadonnées d'un bien du portefeuille. **Paramètre requis :** `id` **Paramètres optionnels :** `label`, `address`, `code_postal`, `commune`, `latitude`, `longitude`, `type_local`, `surface`, `pieces`, `purchase_price` Note : modifier `latitude`, `longitude`, `surface` ou `type_local` invalide l'estimation en cache — elle sera recalculée au prochain appel `get_portfolio_property`. --- ### delete_portfolio_property (0 crédit) Supprime définitivement un bien du portefeuille. **Paramètre requis :** `id` **Réponse :** `{ "deleted": true, "id": "..." }` --- ### create_market_alert (0 crédit — Pro/Enterprise requis) Crée une alerte webhook pour les nouvelles transactions DVF correspondant à des critères. Un POST est envoyé à votre `webhook_url` à chaque correspondance. Maximum 20 alertes actives par clé API. **Paramètres requis :** - `webhook_url` (string, HTTPS) : URL qui reçoit les notifications - Au moins un filtre de localisation : `code_postal`, `commune`, ou `code_departement` **Paramètres optionnels :** - `type_local`, `prix_min`, `prix_max`, `surface_min`, `surface_max` **Réponse :** `{ "alert": { "id": "...", "is_active": true, ... } }` --- ### list_market_alerts (0 crédit — Pro/Enterprise requis) Liste toutes les alertes webhook (actives et inactives) de la clé API. --- ### update_market_alert (0 crédit — Pro/Enterprise requis) Met à jour une alerte existante. **Paramètre requis :** `id` **Paramètres optionnels :** tous les filtres + `is_active` (mettre à `false` pour suspendre sans supprimer) --- ### delete_market_alert (0 crédit — Pro/Enterprise requis) Supprime définitivement une alerte webhook. **Paramètre requis :** `id` --- ## Authentification et limites ### Authentification Trois méthodes, toutes rattachées au même compte et à la même clé API active (les crédits sont toujours débités sur la clé active du compte) : 1. **OAuth 2.1 (serveur MCP uniquement, recommandé)** — le client appelle `POST https://mcp.normi.fr/mcp` sans identifiant, reçoit `401` avec `WWW-Authenticate: Bearer resource_metadata="https://mcp.normi.fr/.well-known/oauth-protected-resource/mcp"`, s'enregistre dynamiquement auprès du serveur d'autorisation (Supabase Auth) et envoie l'utilisateur se connecter et autoriser sur normi.fr. Une clé API gratuite (500 crédits) est créée à la première autorisation si le compte n'en a pas. Révocation : tableau de bord → Applications connectées ; effet dès l'appel suivant. Le client gère lui-même le rafraîchissement du jeton. 2. **Clé API** — `Authorization: Bearer normi_…` (MCP) ou `X-API-Key: normi_…` (REST et MCP). Générée dans le tableau de bord sur normi.fr, affichée une seule fois. Un seul token actif par compte — créer un nouveau token révoque l'ancien (les applications OAuth suivent automatiquement la nouvelle clé). 3. **URL de connecteur secrète** — `/mcp/claude/` ou `/mcp/chatgpt/`, pour les clients sans OAuth ni en-têtes personnalisés. L'API REST `/v1` n'accepte que les clés API. ### Limites de débit | Plan | Limite | |------|--------| | Free | 60 req/min | | Indie | 60 req/min | | Agent | 60 req/min | | Pro | 60 req/min | | Enterprise | 120 req/min | Dépassement → HTTP 429 ### Crédits Chaque appel d'outil déduit des crédits du solde associé au token. Les crédits sont vérifiés avant exécution. Si le solde est insuffisant, l'erreur est retournée dans `content[0].text` (pas en HTTP 402). ### Codes d'erreur HTTP | Code | Cause | |------|-------| | 401 | Token manquant, invalide ou révoqué | | 409 | Session MCP expirée (TTL 15 min) — réinitialiser la connexion | | 429 | Dépassement de limite (60 req/min) | ### Format d'erreur (crédits insuffisants) Retourné dans le corps de la réponse MCP : ```json { "content": [{ "type": "text", "text": "Insufficient credits. You have 3 credits remaining but this tool costs 5. Purchase more credits at normi.fr/dashboard/credits." }] } ``` --- ## Comportements automatiques importants ### Auto-limitation des grandes zones Pour les requêtes sans filtre de date sur des zones à fort volume (Paris, Île-de-France, grands départements), Normi applique automatiquement un `date_debut` sur les 3 dernières années afin d'éviter les timeouts. La réponse inclut un champ `notice` indiquant la limitation appliquée : ```json { "notice": "Applied date_debut=2022-01-01 automatically (high-volume zone)" } ``` ### Recherche par rayon > 1000m Restreinte automatiquement aux 12 derniers mois. ### Paris — arrondissements Paris est stocké en base avec les communes `"PARIS 01"` à `"PARIS 20"`. Pour interroger tout Paris, utiliser `code_departement: "75"`. Pour un arrondissement, utiliser `commune: "PARIS 11"` (pas de zéro initial pour les arrondissements 10-20). --- ## Notes de qualité des données - Noms de communes normalisés (accents supprimés, MAJUSCULES) - Transactions aberrantes retirées à l'ingestion (prix/m² < 500 € ou > 100 000 € sur du bâti) ; le paramètre `exclude_outliers` est obsolète et sans effet - Ventes en lot filtrées par défaut (`exclude_bulk_sales: true`) - Valeurs `type_local` normalisées via `computed_type_local` : - `"Appartement"`, `"Maison"`, `"Terrain"`, `"Local commercial"`, `"Dépendance"`, `"Local industriel"` --- ## Installation ### Claude : annuaire des connecteurs (un clic) Normi est publié dans l'annuaire des connecteurs Claude : https://claude.ai/directory/normi. Cliquez pour l'ajouter, connectez-vous à Normi et autorisez. Le connecteur suit le compte Claude (claude.ai, Claude Desktop, mobile, Cowork). Présentation : https://normi.fr/claude ### Connecteur personnalisé : claude.ai, Claude mobile, Cowork, ChatGPT (OAuth) Ajoutez un connecteur personnalisé avec l'URL `https://mcp.normi.fr/mcp`, puis connectez-vous à Normi et autorisez l'accès. Aucune clé à copier. ### Claude Code ```bash claude mcp add --transport http normi https://mcp.normi.fr/mcp ``` Puis `/mcp` dans Claude Code pour vous authentifier (OAuth). Avec une clé API à la place : ```bash claude mcp add --transport http normi https://mcp.normi.fr/mcp --header "Authorization: Bearer VOTRE_TOKEN" ``` ### Claude Desktop ```json { "mcpServers": { "normi": { "command": "npx", "args": ["-y", "@normi/mcp-dvf"], "env": { "NORMI_API_KEY": "VOTRE_TOKEN" } } } } ``` --- ## Fraîcheur des données - DVF : mis à jour tous les ~6 mois (publications DGFiP : avril et octobre) - DPE : mise à jour hebdomadaire incrémentale (flux ADEME) --- ## Glossaire des termes DVF clés - valeur_fonciere — prix de vente en € - surface_reelle_bati — surface bâtie en m² - surface_terrain — surface du terrain en m² - type_local — Maison / Appartement / Dépendance / Local industriel - nature_mutation — Vente / Adjudication / Expropriation - nombre_pieces_principales — nombre de pièces - code_commune — code INSEE à 5 chiffres - date_mutation — date de la transaction - prix_m2 — prix au mètre carré calculé - commune_base — nom de commune normalisé (index principal) ## Concepts clés - Droits de mutation — taxe de transfert de propriété (5-6% du prix) - Notaire — notaire (obligatoire pour toutes les ventes en France) - Cadastre — registre foncier (référentiel géométrique) - SCI — Société Civile Immobilière (société holding immobilière) - DPE — Diagnostic de Performance Energétique (diagnostic énergétique) - PLU — Plan Local d'Urbanisme (plan d'occupation des sols) - Mutation — événement de transfert de propriété - Prix au m² — prix par mètre carré --- ## Liens - Page principale : https://normi.fr - Documentation : https://normi.fr/docs - Guide d'installation : https://normi.fr/docs/install - Comparatif des API DVF et serveurs MCP immobiliers : https://www.normi.fr/comparatif-api-dvf - French DVF API comparison (English) : https://www.normi.fr/en/dvf-api-comparison - DVF brut vs DVF nettoyé (qualité des données, chiffres mesurés) : https://www.normi.fr/dvf-brut-vs-nettoye - Raw vs clean DVF (English) : https://www.normi.fr/en/raw-vs-clean-dvf - Résumé LLM : https://normi.fr/llms.txt - Manifest MCP : https://normi.fr/.well-known/mcp.json