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 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,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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>`;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user