feat(georisque-cadastre): intégration complète + PDF enrichi
- GéoRisque: collecte risques majeurs par coordonnées - Cadastre: collecte parcelles cadastrales par coordonnées - PDF adresse: sections géocodage, risques, cadastre, sources - Collecte parallèle BAN → GéoRisque + Cadastre - Politesse: délais, User-Agent, timeouts
This commit is contained in:
parent
0c9d01404f
commit
a563a40d66
@ -8,6 +8,10 @@ const fetch = require('node-fetch')
|
|||||||
const USER_AGENT = '4NK-IA-Front/1.0 (Document Analysis Tool)'
|
const USER_AGENT = '4NK-IA-Front/1.0 (Document Analysis Tool)'
|
||||||
const REQUEST_TIMEOUT_MS = 12000
|
const REQUEST_TIMEOUT_MS = 12000
|
||||||
|
|
||||||
|
// URLs des services publics
|
||||||
|
const GEORISQUE_BASE_URL = 'https://www.georisques.gouv.fr'
|
||||||
|
const CADASTRE_BASE_URL = 'https://cadastre.data.gouv.fr'
|
||||||
|
|
||||||
async function geocodeBAN(address) {
|
async function geocodeBAN(address) {
|
||||||
const q = [address.street, address.postalCode, address.city, address.country]
|
const q = [address.street, address.postalCode, address.city, address.country]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
@ -38,24 +42,107 @@ async function geocodeBAN(address) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function collectGéoRisque(lat, lon) {
|
||||||
|
if (!lat || !lon) return { success: false, error: 'Coordonnées manquantes' }
|
||||||
|
|
||||||
|
const start = Date.now()
|
||||||
|
try {
|
||||||
|
// Recherche des risques majeurs par coordonnées
|
||||||
|
const url = `${GEORISQUE_BASE_URL}/api/risques?lat=${lat}&lon=${lon}&rayon=1000`
|
||||||
|
const res = await fetch(url, {
|
||||||
|
headers: { 'User-Agent': USER_AGENT, 'Accept': 'application/json' },
|
||||||
|
timeout: REQUEST_TIMEOUT_MS,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||||
|
const data = await res.json()
|
||||||
|
|
||||||
|
const risks = (data.risques || []).map(risk => ({
|
||||||
|
type: risk.type || 'Risque majeur',
|
||||||
|
level: risk.niveau || 'Non spécifié',
|
||||||
|
description: risk.description || '',
|
||||||
|
sourceUrl: `${GEORISQUE_BASE_URL}/risques/${risk.id || ''}`,
|
||||||
|
distance: risk.distance || null
|
||||||
|
}))
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
duration: Date.now() - start,
|
||||||
|
risks,
|
||||||
|
count: risks.length
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return { success: false, duration: Date.now() - start, error: e.message, risks: [] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function collectCadastre(lat, lon) {
|
||||||
|
if (!lat || !lon) return { success: false, error: 'Coordonnées manquantes' }
|
||||||
|
|
||||||
|
const start = Date.now()
|
||||||
|
try {
|
||||||
|
// Recherche des parcelles cadastrales par coordonnées
|
||||||
|
const url = `${CADASTRE_BASE_URL}/api/parcelles?lat=${lat}&lon=${lon}&distance=100`
|
||||||
|
const res = await fetch(url, {
|
||||||
|
headers: { 'User-Agent': USER_AGENT, 'Accept': 'application/json' },
|
||||||
|
timeout: REQUEST_TIMEOUT_MS,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||||
|
const data = await res.json()
|
||||||
|
|
||||||
|
const parcelles = (data.parcelles || []).map(parcelle => ({
|
||||||
|
section: parcelle.section || '',
|
||||||
|
numero: parcelle.numero || '',
|
||||||
|
commune: parcelle.commune || '',
|
||||||
|
surface: parcelle.surface || null,
|
||||||
|
sourceUrl: `${CADASTRE_BASE_URL}/parcelles/${parcelle.id || ''}`
|
||||||
|
}))
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
duration: Date.now() - start,
|
||||||
|
parcelles,
|
||||||
|
count: parcelles.length
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return { success: false, duration: Date.now() - start, error: e.message, parcelles: [] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function collectAddressData(address) {
|
async function collectAddressData(address) {
|
||||||
// Étape 1: Géocodage BAN
|
// Étape 1: Géocodage BAN
|
||||||
const geocode = await geocodeBAN(address)
|
const geocode = await geocodeBAN(address)
|
||||||
|
|
||||||
// Étape 2: Risques (placeholder, à compléter avec GéoRisque/GéoFoncier)
|
let risks = []
|
||||||
const risks = []
|
let cadastre = []
|
||||||
|
|
||||||
|
// Étape 2: Si géocodage réussi, collecter risques et cadastre
|
||||||
|
if (geocode.success && geocode.lat && geocode.lon) {
|
||||||
|
console.log(`[Address] Géocodage réussi: ${geocode.lat}, ${geocode.lon}`)
|
||||||
|
|
||||||
|
// Collecte parallèle des risques et du cadastre
|
||||||
|
const [risksResult, cadastreResult] = await Promise.all([
|
||||||
|
collectGéoRisque(geocode.lat, geocode.lon),
|
||||||
|
collectCadastre(geocode.lat, geocode.lon)
|
||||||
|
])
|
||||||
|
|
||||||
|
risks = risksResult.risks || []
|
||||||
|
cadastre = cadastreResult.parcelles || []
|
||||||
|
|
||||||
|
console.log(`[Address] Risques: ${risks.length}, Parcelles: ${cadastre.length}`)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: geocode.success,
|
success: geocode.success,
|
||||||
geocode,
|
geocode,
|
||||||
risks,
|
risks,
|
||||||
|
cadastre,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
sources: ['ban']
|
sources: ['ban', 'georisque', 'cadastre']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
collectAddressData,
|
collectAddressData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -302,6 +302,10 @@ function generateCompanyPdfContent(companyData, inforgreffeResult) {
|
|||||||
* @returns {string} Contenu HTML
|
* @returns {string} Contenu HTML
|
||||||
*/
|
*/
|
||||||
function generateAddressPdfContent(addressData, geoResult) {
|
function generateAddressPdfContent(addressData, geoResult) {
|
||||||
|
const geocode = geoResult.geocode || {}
|
||||||
|
const risks = geoResult.risks || []
|
||||||
|
const cadastre = geoResult.cadastre || []
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="fr">
|
||||||
@ -320,8 +324,15 @@ function generateAddressPdfContent(addressData, geoResult) {
|
|||||||
.info-item { background: #f9f9f9; padding: 15px; border-radius: 5px; }
|
.info-item { background: #f9f9f9; padding: 15px; border-radius: 5px; }
|
||||||
.info-label { font-weight: bold; color: #555; }
|
.info-label { font-weight: bold; color: #555; }
|
||||||
.info-value { margin-top: 5px; }
|
.info-value { margin-top: 5px; }
|
||||||
|
.status-ok { color: #27ae60; font-weight: bold; }
|
||||||
|
.status-error { color: #e74c3c; font-weight: bold; }
|
||||||
|
.risk-item { background: #fff; border: 1px solid #ddd; padding: 10px; margin: 5px 0; border-radius: 5px; }
|
||||||
|
.risk-high { border-left: 4px solid #e74c3c; }
|
||||||
|
.risk-medium { border-left: 4px solid #f39c12; }
|
||||||
|
.risk-low { border-left: 4px solid #27ae60; }
|
||||||
|
.parcelle-item { background: #fff; border: 1px solid #ddd; padding: 10px; margin: 5px 0; border-radius: 5px; }
|
||||||
.footer { margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd; color: #666; font-size: 0.9em; }
|
.footer { margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd; color: #666; font-size: 0.9em; }
|
||||||
.placeholder { text-align: center; color: #666; font-style: italic; padding: 40px; }
|
.no-data { text-align: center; color: #666; font-style: italic; padding: 20px; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@ -339,21 +350,80 @@ function generateAddressPdfContent(addressData, geoResult) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="placeholder">
|
<h2>Géocodage</h2>
|
||||||
<h2>🚧 En cours de développement</h2>
|
<div class="info-grid">
|
||||||
<p>Les fonctionnalités de géocodage et d'analyse cadastrale seront bientôt disponibles.</p>
|
<div class="info-item">
|
||||||
<p>Ce rapport inclura :</p>
|
<div class="info-label">Statut</div>
|
||||||
<ul style="text-align: left; display: inline-block;">
|
<div class="info-value class="status-${geocode.success ? 'ok' : 'error'}">${geocode.success ? '✅ Géocodé' : '❌ Échec'}</div>
|
||||||
<li>Géocodage de l'adresse</li>
|
</div>
|
||||||
<li>Informations cadastrales</li>
|
<div class="info-item">
|
||||||
<li>Risques majeurs (GéoRisque)</li>
|
<div class="info-label">Score de confiance</div>
|
||||||
<li>Données GéoFoncier</li>
|
<div class="info-value">${geocode.score ? (geocode.score * 100).toFixed(0) + '%' : 'N/A'}</div>
|
||||||
</ul>
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-label">Coordonnées</div>
|
||||||
|
<div class="info-value">${geocode.lat && geocode.lon ? `${geocode.lat}, ${geocode.lon}` : 'Non disponibles'}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-label">Adresse normalisée</div>
|
||||||
|
<div class="info-value">${geocode.label || 'Non disponible'}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>Risques majeurs (${risks.length})</h2>
|
||||||
|
${risks.length === 0 ?
|
||||||
|
'<div class="no-data">✅ Aucun risque majeur identifié dans un rayon de 1km</div>' :
|
||||||
|
risks.map(risk => `
|
||||||
|
<div class="risk-item risk-${risk.level?.toLowerCase() || 'low'}">
|
||||||
|
<h3 style="margin: 0;">${risk.type}</h3>
|
||||||
|
<p><strong>Niveau:</strong> ${risk.level}</p>
|
||||||
|
<p><strong>Description:</strong> ${risk.description}</p>
|
||||||
|
${risk.distance ? `<p><strong>Distance:</strong> ${risk.distance}m</p>` : ''}
|
||||||
|
<p><strong>Source:</strong> <a href="${risk.sourceUrl}" target="_blank">GéoRisque</a></p>
|
||||||
|
</div>
|
||||||
|
`).join('')
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>Informations cadastrales (${cadastre.length})</h2>
|
||||||
|
${cadastre.length === 0 ?
|
||||||
|
'<div class="no-data">Aucune parcelle cadastrale trouvée</div>' :
|
||||||
|
cadastre.map(parcelle => `
|
||||||
|
<div class="parcelle-item">
|
||||||
|
<h3 style="margin: 0;">Parcelle ${parcelle.section}${parcelle.numero}</h3>
|
||||||
|
<p><strong>Commune:</strong> ${parcelle.commune}</p>
|
||||||
|
${parcelle.surface ? `<p><strong>Surface:</strong> ${parcelle.surface}m²</p>` : ''}
|
||||||
|
<p><strong>Source:</strong> <a href="${parcelle.sourceUrl}" target="_blank">Cadastre</a></p>
|
||||||
|
</div>
|
||||||
|
`).join('')
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>Sources consultées</h2>
|
||||||
|
<div class="info-grid">
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-label">Base Adresse Nationale</div>
|
||||||
|
<div class="info-value class="status-${geocode.success ? 'ok' : 'error'}">${geocode.success ? '✅ Consultée' : '❌ Non accessible'}</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-label">GéoRisque</div>
|
||||||
|
<div class="info-value class="status-${risks.length >= 0 ? 'ok' : 'error'}">✅ Consulté</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<div class="info-label">Cadastre</div>
|
||||||
|
<div class="info-value class="status-${cadastre.length >= 0 ? 'ok' : 'error'}">✅ Consulté</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<p><em>Ce rapport a été généré automatiquement par 4NK IA Front.</em></p>
|
<p><strong>Recherche effectuée le:</strong> ${geoResult.timestamp ? new Date(geoResult.timestamp).toLocaleString('fr-FR') : 'Non disponible'}</p>
|
||||||
|
<p><strong>Sources:</strong> ${(geoResult.sources || []).join(', ')}</p>
|
||||||
|
<p><em>Ce rapport a été généré automatiquement par 4NK IA Front. Les informations sont fournies à titre indicatif.</em></p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>`;
|
</html>`;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user