4NK_IA_front/backend/collectors/geofoncierCollector.js
4NK IA aad52027c1 ci: docker_tag=dev-test
- 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
2025-09-18 20:07:08 +00:00

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
};