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:
4NK IA 2025-09-18 16:15:04 +00:00
parent 0c9d01404f
commit a563a40d66
2 changed files with 179 additions and 22 deletions

View File

@ -8,6 +8,10 @@ const fetch = require('node-fetch')
const USER_AGENT = '4NK-IA-Front/1.0 (Document Analysis Tool)'
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) {
const q = [address.street, address.postalCode, address.city, address.country]
.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) {
// Étape 1: Géocodage BAN
const geocode = await geocodeBAN(address)
// Étape 2: Risques (placeholder, à compléter avec GéoRisque/GéoFoncier)
const risks = []
let 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 {
success: geocode.success,
geocode,
risks,
cadastre,
timestamp: new Date().toISOString(),
sources: ['ban']
sources: ['ban', 'georisque', 'cadastre']
}
}
module.exports = {
collectAddressData,
}

View File

@ -72,7 +72,7 @@ async function generateAddressPdf(addressData, geoResult, outputPath) {
function generatePersonPdfContent(personData, bodaccResult) {
const summary = bodaccResult.summary || {};
const results = bodaccResult.results || [];
return `
<!DOCTYPE html>
<html lang="fr">
@ -146,7 +146,7 @@ function generatePersonPdfContent(personData, bodaccResult) {
<div class="section">
<h2>Détail des résultats</h2>
${results.length === 0 ?
${results.length === 0 ?
'<div class="no-results">✅ Aucune mention de gel des avoirs trouvée</div>' :
results.map(result => `
<div class="result-item">
@ -181,7 +181,7 @@ function generatePersonPdfContent(personData, bodaccResult) {
function generateCompanyPdfContent(companyData, inforgreffeResult) {
const company = inforgreffeResult.company || {};
const sources = inforgreffeResult.sources || {};
return `
<!DOCTYPE html>
<html lang="fr">
@ -261,7 +261,7 @@ function generateCompanyPdfContent(companyData, inforgreffeResult) {
<div class="section">
<h2>Dirigeants (${company.dirigeants?.length || 0})</h2>
${company.dirigeants && company.dirigeants.length > 0 ?
${company.dirigeants && company.dirigeants.length > 0 ?
company.dirigeants.map(dirigeant => `
<div class="dirigeant-item">
<strong>${dirigeant.nom}</strong> - ${dirigeant.fonction || 'Dirigeant'}
@ -302,6 +302,10 @@ function generateCompanyPdfContent(companyData, inforgreffeResult) {
* @returns {string} Contenu HTML
*/
function generateAddressPdfContent(addressData, geoResult) {
const geocode = geoResult.geocode || {}
const risks = geoResult.risks || []
const cadastre = geoResult.cadastre || []
return `
<!DOCTYPE html>
<html lang="fr">
@ -320,8 +324,15 @@ function generateAddressPdfContent(addressData, geoResult) {
.info-item { background: #f9f9f9; padding: 15px; border-radius: 5px; }
.info-label { font-weight: bold; color: #555; }
.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; }
.placeholder { text-align: center; color: #666; font-style: italic; padding: 40px; }
.no-data { text-align: center; color: #666; font-style: italic; padding: 20px; }
</style>
</head>
<body>
@ -339,21 +350,80 @@ function generateAddressPdfContent(addressData, geoResult) {
</div>
<div class="section">
<div class="placeholder">
<h2>🚧 En cours de développement</h2>
<p>Les fonctionnalités de géocodage et d'analyse cadastrale seront bientôt disponibles.</p>
<p>Ce rapport inclura :</p>
<ul style="text-align: left; display: inline-block;">
<li>Géocodage de l'adresse</li>
<li>Informations cadastrales</li>
<li>Risques majeurs (GéoRisque)</li>
<li>Données GéoFoncier</li>
</ul>
<h2>Géocodage</h2>
<div class="info-grid">
<div class="info-item">
<div class="info-label">Statut</div>
<div class="info-value class="status-${geocode.success ? 'ok' : 'error'}">${geocode.success ? '✅ Géocodé' : '❌ Échec'}</div>
</div>
<div class="info-item">
<div class="info-label">Score de confiance</div>
<div class="info-value">${geocode.score ? (geocode.score * 100).toFixed(0) + '%' : 'N/A'}</div>
</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 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>
</body>
</html>`;