- Alignement backend: seules 4 entités retournées (persons, companies, addresses, contractual) - Version API mise à jour à 1.0.1 dans /api/health - Interface onglets d entités: Personnes, Adresses, Entreprises, Contractuel - Correction erreurs TypeScript pour build stricte - Tests et documentation mis à jour - CHANGELOG.md mis à jour avec version 1.1.1
396 lines
10 KiB
JavaScript
396 lines
10 KiB
JavaScript
/**
|
|
* Collecteur GéoFoncier
|
|
* Accès aux données foncières et immobilières via l'API GéoFoncier
|
|
*/
|
|
|
|
const fetch = require('node-fetch');
|
|
|
|
const GEOFONCIER_BASE_URL = 'https://api2.geofoncier.fr';
|
|
const GEOFONCIER_EXPERT_URL = 'https://site-expert.geofoncier.fr';
|
|
const USER_AGENT = '4NK-IA-Front/1.0 (Document Analysis Tool)';
|
|
const REQUEST_TIMEOUT_MS = 20000; // 20 secondes timeout
|
|
const REQUEST_DELAY_MS = 2000; // 2 secondes entre les requêtes
|
|
|
|
/**
|
|
* Recherche les informations foncières pour une adresse
|
|
* @param {Object} address - Adresse à rechercher
|
|
* @returns {Promise<Object>} Résultat de la recherche GéoFoncier
|
|
*/
|
|
async function searchGeofoncierInfo(address) {
|
|
const startTime = Date.now();
|
|
|
|
try {
|
|
console.log(`[GéoFoncier] Recherche info foncière pour: ${address.street}, ${address.city}`);
|
|
|
|
// Étape 1: Géocodage de l'adresse
|
|
const geocodeResult = await geocodeAddress(address);
|
|
|
|
if (!geocodeResult.success || !geocodeResult.coordinates) {
|
|
return {
|
|
success: false,
|
|
duration: Date.now() - startTime,
|
|
error: 'Géocodage échoué',
|
|
address,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
}
|
|
|
|
// Étape 2: Recherche des parcelles cadastrales
|
|
const parcellesResult = await searchParcelles(geocodeResult.coordinates);
|
|
|
|
// Délai de politesse
|
|
await new Promise(resolve => setTimeout(resolve, REQUEST_DELAY_MS));
|
|
|
|
// Étape 3: Recherche des informations foncières
|
|
const foncierResult = await searchInfoFonciere(geocodeResult.coordinates);
|
|
|
|
// Délai de politesse
|
|
await new Promise(resolve => setTimeout(resolve, REQUEST_DELAY_MS));
|
|
|
|
// Étape 4: Recherche des mutations récentes
|
|
const mutationsResult = await searchMutations(geocodeResult.coordinates);
|
|
|
|
const duration = Date.now() - startTime;
|
|
console.log(`[GéoFoncier] Recherche terminée en ${duration}ms`);
|
|
|
|
return {
|
|
success: true,
|
|
duration,
|
|
address,
|
|
geocode: geocodeResult,
|
|
parcelles: parcellesResult,
|
|
infoFonciere: foncierResult,
|
|
mutations: mutationsResult,
|
|
timestamp: new Date().toISOString(),
|
|
source: 'geofoncier.fr'
|
|
};
|
|
|
|
} catch (error) {
|
|
const duration = Date.now() - startTime;
|
|
console.error(`[GéoFoncier] Erreur recherche:`, error.message);
|
|
|
|
return {
|
|
success: false,
|
|
duration,
|
|
address,
|
|
error: error.message,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Géocode une adresse via GéoFoncier
|
|
* @param {Object} address - Adresse à géocoder
|
|
* @returns {Promise<Object>} Résultat du géocodage
|
|
*/
|
|
async function geocodeAddress(address) {
|
|
try {
|
|
const query = `${address.street}, ${address.postalCode} ${address.city}`;
|
|
const geocodeUrl = `${GEOFONCIER_BASE_URL}/geocoding/search?q=${encodeURIComponent(query)}&limit=1`;
|
|
|
|
const response = await fetch(geocodeUrl, {
|
|
method: 'GET',
|
|
headers: {
|
|
'User-Agent': USER_AGENT,
|
|
'Accept': 'application/json'
|
|
},
|
|
timeout: REQUEST_TIMEOUT_MS
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.features && data.features.length > 0) {
|
|
const feature = data.features[0];
|
|
return {
|
|
success: true,
|
|
coordinates: {
|
|
lat: feature.geometry.coordinates[1],
|
|
lon: feature.geometry.coordinates[0]
|
|
},
|
|
label: feature.properties.label,
|
|
score: feature.properties.score || 0,
|
|
data: feature.properties
|
|
};
|
|
}
|
|
|
|
return {
|
|
success: false,
|
|
error: 'Aucun résultat de géocodage'
|
|
};
|
|
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error.message
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recherche les parcelles cadastrales
|
|
* @param {Object} coordinates - Coordonnées {lat, lon}
|
|
* @returns {Promise<Object>} Résultat de la recherche de parcelles
|
|
*/
|
|
async function searchParcelles(coordinates) {
|
|
try {
|
|
const parcellesUrl = `${GEOFONCIER_BASE_URL}/cadastre/parcelles?lat=${coordinates.lat}&lon=${coordinates.lon}&radius=100`;
|
|
|
|
const response = await fetch(parcellesUrl, {
|
|
method: 'GET',
|
|
headers: {
|
|
'User-Agent': USER_AGENT,
|
|
'Accept': 'application/json'
|
|
},
|
|
timeout: REQUEST_TIMEOUT_MS
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
const parcelles = [];
|
|
if (data.parcelles && Array.isArray(data.parcelles)) {
|
|
for (const parcelle of data.parcelles) {
|
|
parcelles.push({
|
|
numero: parcelle.numero || '',
|
|
section: parcelle.section || '',
|
|
commune: parcelle.commune || '',
|
|
surface: parcelle.surface || 0,
|
|
nature: parcelle.nature || '',
|
|
adresse: parcelle.adresse || '',
|
|
proprietaire: parcelle.proprietaire || '',
|
|
dateMutation: parcelle.dateMutation || '',
|
|
valeur: parcelle.valeur || 0
|
|
});
|
|
}
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
parcelles,
|
|
total: parcelles.length
|
|
};
|
|
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error.message,
|
|
parcelles: []
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recherche les informations foncières
|
|
* @param {Object} coordinates - Coordonnées {lat, lon}
|
|
* @returns {Promise<Object>} Résultat de la recherche foncière
|
|
*/
|
|
async function searchInfoFonciere(coordinates) {
|
|
try {
|
|
const foncierUrl = `${GEOFONCIER_EXPERT_URL}/api/foncier/info?lat=${coordinates.lat}&lon=${coordinates.lon}`;
|
|
|
|
const response = await fetch(foncierUrl, {
|
|
method: 'GET',
|
|
headers: {
|
|
'User-Agent': USER_AGENT,
|
|
'Accept': 'application/json'
|
|
},
|
|
timeout: REQUEST_TIMEOUT_MS
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
return {
|
|
success: true,
|
|
info: {
|
|
valeurFonciere: data.valeurFonciere || 0,
|
|
valeurLocative: data.valeurLocative || 0,
|
|
surfaceHabitable: data.surfaceHabitable || 0,
|
|
surfaceTerrain: data.surfaceTerrain || 0,
|
|
nombrePieces: data.nombrePieces || 0,
|
|
anneeConstruction: data.anneeConstruction || '',
|
|
typeHabitation: data.typeHabitation || '',
|
|
energie: data.energie || '',
|
|
ges: data.ges || '',
|
|
diagnostics: data.diagnostics || []
|
|
}
|
|
};
|
|
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error.message,
|
|
info: null
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recherche les mutations récentes
|
|
* @param {Object} coordinates - Coordonnées {lat, lon}
|
|
* @returns {Promise<Object>} Résultat de la recherche de mutations
|
|
*/
|
|
async function searchMutations(coordinates) {
|
|
try {
|
|
const mutationsUrl = `${GEOFONCIER_BASE_URL}/mutations/recentes?lat=${coordinates.lat}&lon=${coordinates.lon}&radius=200&years=5`;
|
|
|
|
const response = await fetch(mutationsUrl, {
|
|
method: 'GET',
|
|
headers: {
|
|
'User-Agent': USER_AGENT,
|
|
'Accept': 'application/json'
|
|
},
|
|
timeout: REQUEST_TIMEOUT_MS
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
const mutations = [];
|
|
if (data.mutations && Array.isArray(data.mutations)) {
|
|
for (const mutation of data.mutations) {
|
|
mutations.push({
|
|
date: mutation.date || '',
|
|
type: mutation.type || '',
|
|
valeur: mutation.valeur || 0,
|
|
surface: mutation.surface || 0,
|
|
prixM2: mutation.prixM2 || 0,
|
|
vendeur: mutation.vendeur || '',
|
|
acheteur: mutation.acheteur || '',
|
|
adresse: mutation.adresse || '',
|
|
reference: mutation.reference || ''
|
|
});
|
|
}
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
mutations,
|
|
total: mutations.length,
|
|
periode: '5 dernières années'
|
|
};
|
|
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error.message,
|
|
mutations: []
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recherche les informations de zonage
|
|
* @param {Object} coordinates - Coordonnées {lat, lon}
|
|
* @returns {Promise<Object>} Résultat de la recherche de zonage
|
|
*/
|
|
async function searchZonage(coordinates) {
|
|
try {
|
|
const zonageUrl = `${GEOFONCIER_BASE_URL}/zonage/info?lat=${coordinates.lat}&lon=${coordinates.lon}`;
|
|
|
|
const response = await fetch(zonageUrl, {
|
|
method: 'GET',
|
|
headers: {
|
|
'User-Agent': USER_AGENT,
|
|
'Accept': 'application/json'
|
|
},
|
|
timeout: REQUEST_TIMEOUT_MS
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
return {
|
|
success: true,
|
|
zonage: {
|
|
zone: data.zone || '',
|
|
sousZone: data.sousZone || '',
|
|
constructibilite: data.constructibilite || '',
|
|
hauteurMax: data.hauteurMax || '',
|
|
densiteMax: data.densiteMax || '',
|
|
servitudes: data.servitudes || [],
|
|
restrictions: data.restrictions || []
|
|
}
|
|
};
|
|
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error.message,
|
|
zonage: null
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recherche les informations de voirie
|
|
* @param {Object} coordinates - Coordonnées {lat, lon}
|
|
* @returns {Promise<Object>} Résultat de la recherche de voirie
|
|
*/
|
|
async function searchVoirie(coordinates) {
|
|
try {
|
|
const voirieUrl = `${GEOFONCIER_BASE_URL}/voirie/info?lat=${coordinates.lat}&lon=${coordinates.lon}`;
|
|
|
|
const response = await fetch(voirieUrl, {
|
|
method: 'GET',
|
|
headers: {
|
|
'User-Agent': USER_AGENT,
|
|
'Accept': 'application/json'
|
|
},
|
|
timeout: REQUEST_TIMEOUT_MS
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
return {
|
|
success: true,
|
|
voirie: {
|
|
type: data.type || '',
|
|
largeur: data.largeur || 0,
|
|
revetement: data.revetement || '',
|
|
eclairage: data.eclairage || false,
|
|
canalisations: data.canalisations || [],
|
|
transports: data.transports || []
|
|
}
|
|
};
|
|
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error.message,
|
|
voirie: null
|
|
};
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
searchGeofoncierInfo,
|
|
geocodeAddress,
|
|
searchParcelles,
|
|
searchInfoFonciere,
|
|
searchMutations,
|
|
searchZonage,
|
|
searchVoirie
|
|
};
|