Estimer la valeur d'un bien
Construisez une estimation fiable basée sur des transactions DVF réelles.
L'estimation par comparables est la méthode de référence des professionnels de l'immobilier. Elle consiste à trouver des biens similaires vendus récemment dans la même zone, puis à en déduire une fourchette de valeur pour le bien cible.
Trouver les comparables
Biens similaires vendus récemment, classés par score de similarité
Analyser les prix
Distribution des prix au m², fourchette basse/médiane/haute
Affiner si nécessaire
Élargir rayon ou période si peu de comparables
Étape 1 — Appeler find_comparables
L'endpoint GET /v1/comparables (ou l'outil MCP find_comparables) prend latitude, longitude et surface cible comme paramètres obligatoires.
import requests
API_KEY = "normi_votre_token"
BASE_URL = "https://mcp.normi.fr/v1"
def find_comparables(lat, lon, surface, type_local="Appartement", radius_m=500):
r = requests.get(
f"{BASE_URL}/comparables",
params={
"latitude": lat,
"longitude": lon,
"type_local": type_local,
"surface_min": surface * 0.8,
"surface_max": surface * 1.2,
"radius_m": radius_m,
"max_age_months": 12,
"exclude_bulk_sales": True,
},
headers={"X-API-Key": API_KEY},
)
r.raise_for_status()
return r.json()
# Appartement de 65m² à Paris 11e (lat/lon approximatifs)
result = find_comparables(48.8581, 2.3790, 65)
print(f"Comparables trouvés : {result['summary']['count']}")api-adresse.data.gouv.fr/search/?q=15+rue+de+la+Roquette+ParisÉtape 2 — Lire la réponse
La réponse contient deux blocs : comparables (liste triée par score) et summary (statistiques agrégées + fourchette d'estimation).
summary = result["summary"]
price_m2 = summary["price_m2"]
estimated = summary["estimated_range"]
print("=== Prix au m² ===")
print(f" Minimum : {price_m2['min']:,} €/m²")
print(f" Médiane : {price_m2['median']:,} €/m²")
print(f" Maximum : {price_m2['max']:,} €/m²")
print("\n=== Fourchette d'estimation (65m²) ===")
print(f" Basse : {estimated['low']:,} €")
print(f" Médiane : {estimated['median']:,} €")
print(f" Haute : {estimated['high']:,} €")
# Afficher les 3 meilleurs comparables
print("\n=== Top 3 comparables ===")
for comp in result["comparables"][:3]:
print(f" {comp['address']}")
print(f" {comp['surface_carrez']}m² — {comp['valeur_fonciere']:,} € ({comp['prix_m2']:,} €/m²)")
print(f" Distance: {comp['distance_m']}m — Score: {comp['similarity_score']:.2f}")
print(f" Vendu le: {comp['date_mutation']}")| Champ | Signification |
|---|---|
| similarity_score | 1.0 = comparable parfait. Basé sur distance + écart de surface. |
| price_m2.median | Prix médian au m² sur l'ensemble des comparables trouvés. |
| estimated_range.low | P25 : 25% des comparables sont en dessous de ce prix. |
| estimated_range.median | Prix médian × surface cible — valeur centrale de l'estimation. |
| estimated_range.high | P75 : 75% des comparables sont en dessous de ce prix. |
Étape 3 — Affiner si peu de comparables
Si summary.count est inférieur à 5, l'estimation est moins fiable. Élargissez progressivement les critères.
# Si moins de 5 comparables : élargir le rayon
if result["summary"]["count"] < 5:
print(f"Seulement {result['summary']['count']} comparable(s). Élargissement du rayon à 1000m...")
result = find_comparables(48.8581, 2.3790, 65, radius_m=1000)
print(f"Après élargissement : {result['summary']['count']} comparables")
# Si toujours peu : élargir la période
if result["summary"]["count"] < 5:
r = requests.get(
f"{BASE_URL}/comparables",
params={
"latitude": 48.8581,
"longitude": 2.3790,
"type_local": "Appartement",
"surface_min": 65 * 0.7, # fourchette élargie
"surface_max": 65 * 1.3,
"radius_m": 1000,
"max_age_months": 24, # 2 ans au lieu de 12 mois
},
headers={"X-API-Key": API_KEY},
)
result = r.json()
print(f"Avec 24 mois : {result['summary']['count']} comparables")Exemple JavaScript
const API_KEY = "normi_votre_token";
async function estimateProperty({ lat, lon, surface, typeLocal = "Appartement" }) {
const params = new URLSearchParams({
latitude: lat.toString(),
longitude: lon.toString(),
type_local: typeLocal,
surface_min: (surface * 0.8).toString(),
surface_max: (surface * 1.2).toString(),
radius_m: "500",
max_age_months: "12",
});
const res = await fetch(
`https://mcp.normi.fr/v1/comparables?${params}`,
{ headers: { "X-API-Key": API_KEY } }
);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
const { price_m2, estimated_range, count } = data.summary;
return {
comparablesCount: count,
pricePerM2: { min: price_m2.min, median: price_m2.median, max: price_m2.max },
estimatedValue: {
low: estimated_range.low,
median: estimated_range.median,
high: estimated_range.high,
},
topComparables: data.comparables.slice(0, 5),
creditsUsed: data._credits.used,
};
}
// Usage
const estimate = await estimateProperty({ lat: 48.8581, lon: 2.3790, surface: 65 });
console.log(`Estimation médiane : ${estimate.estimatedValue.median.toLocaleString("fr-FR")} €`);Avec Claude (MCP)
Si Normi est connecté à Claude via MCP, vous pouvez simplement demander :
Claude appellera automatiquement find_comparables et formatera les résultats de façon lisible.