feat: intégration collecteurs Bodacc/Inforgreffe + génération PDF
- Collecteur Bodacc: scraping léger pour gel des avoirs (personnes) - Collecteur Inforgreffe/Societe.com: données entreprises (SIREN, dirigeants, etc.) - Générateur PDF: rapports formatés HTML pour chaque type dentité
This commit is contained in:
parent
39c452002a
commit
8e3daad446
235
backend/collectors/bodaccCollector.js
Normal file
235
backend/collectors/bodaccCollector.js
Normal file
@ -0,0 +1,235 @@
|
||||
/**
|
||||
* Collecteur Bodacc - Gel des avoirs
|
||||
* Scraping léger avec politesse pour vérifier les mentions de gel des avoirs
|
||||
*/
|
||||
|
||||
const fetch = require('node-fetch');
|
||||
const { JSDOM } = require('jsdom');
|
||||
|
||||
const BODACC_BASE_URL = 'https://www.bodacc.fr';
|
||||
const USER_AGENT = '4NK-IA-Front/1.0 (Document Analysis Tool)';
|
||||
const REQUEST_DELAY_MS = 1000; // 1 seconde entre les requêtes
|
||||
const REQUEST_TIMEOUT_MS = 10000; // 10 secondes timeout
|
||||
|
||||
/**
|
||||
* Effectue une recherche sur Bodacc pour un nom/prénom donné
|
||||
* @param {string} lastName - Nom de famille (obligatoire)
|
||||
* @param {string} firstName - Prénom (optionnel)
|
||||
* @returns {Promise<Object>} Résultat de la recherche
|
||||
*/
|
||||
async function searchBodaccGelAvoirs(lastName, firstName = '') {
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
console.log(`[Bodacc] Recherche gel des avoirs pour: ${lastName} ${firstName}`);
|
||||
|
||||
// Construction de la requête de recherche
|
||||
const searchParams = new URLSearchParams({
|
||||
'q': `${lastName} ${firstName}`.trim(),
|
||||
'type': 'gel-avoirs',
|
||||
'date_debut': '2020-01-01', // Recherche sur les 4 dernières années
|
||||
'date_fin': new Date().toISOString().split('T')[0]
|
||||
});
|
||||
|
||||
const searchUrl = `${BODACC_BASE_URL}/recherche?${searchParams.toString()}`;
|
||||
|
||||
// Requête avec politesse
|
||||
const response = await fetch(searchUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'User-Agent': USER_AGENT,
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'Accept-Language': 'fr-FR,fr;q=0.9,en;q=0.8',
|
||||
'Accept-Encoding': 'gzip, deflate, br',
|
||||
'Connection': 'keep-alive',
|
||||
'Upgrade-Insecure-Requests': '1'
|
||||
},
|
||||
timeout: REQUEST_TIMEOUT_MS
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const html = await response.text();
|
||||
const dom = new JSDOM(html);
|
||||
const document = dom.window.document;
|
||||
|
||||
// Extraction des résultats
|
||||
const results = extractBodaccResults(document, lastName, firstName);
|
||||
|
||||
// Délai de politesse avant la prochaine requête
|
||||
await new Promise(resolve => setTimeout(resolve, REQUEST_DELAY_MS));
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
console.log(`[Bodacc] Recherche terminée en ${duration}ms, ${results.length} résultats`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
duration,
|
||||
results,
|
||||
source: 'bodacc.fr',
|
||||
searchUrl,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
const duration = Date.now() - startTime;
|
||||
console.error(`[Bodacc] Erreur recherche:`, error.message);
|
||||
|
||||
return {
|
||||
success: false,
|
||||
duration,
|
||||
error: error.message,
|
||||
results: [],
|
||||
source: 'bodacc.fr',
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrait les résultats de la page Bodacc
|
||||
* @param {Document} document - Document DOM parsé
|
||||
* @param {string} lastName - Nom recherché
|
||||
* @param {string} firstName - Prénom recherché
|
||||
* @returns {Array} Liste des résultats trouvés
|
||||
*/
|
||||
function extractBodaccResults(document, lastName, firstName) {
|
||||
const results = [];
|
||||
|
||||
try {
|
||||
// Sélecteurs pour les résultats de gel des avoirs
|
||||
const resultSelectors = [
|
||||
'.result-item',
|
||||
'.search-result',
|
||||
'.bodacc-result',
|
||||
'[data-type="gel-avoirs"]'
|
||||
];
|
||||
|
||||
let resultElements = [];
|
||||
for (const selector of resultSelectors) {
|
||||
resultElements = document.querySelectorAll(selector);
|
||||
if (resultElements.length > 0) break;
|
||||
}
|
||||
|
||||
// Si pas de sélecteur spécifique, chercher dans le contenu général
|
||||
if (resultElements.length === 0) {
|
||||
const content = document.body.textContent || '';
|
||||
if (content.includes('gel des avoirs') || content.includes('GEL DES AVOIRS')) {
|
||||
// Résultat générique si on trouve des mentions
|
||||
results.push({
|
||||
name: `${firstName} ${lastName}`.trim(),
|
||||
type: 'gel-avoirs',
|
||||
date: new Date().toISOString().split('T')[0],
|
||||
sourceUrl: BODACC_BASE_URL,
|
||||
matchScore: 0.7,
|
||||
description: 'Mention de gel des avoirs détectée dans les résultats Bodacc'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Traitement des éléments de résultats spécifiques
|
||||
resultElements.forEach((element, index) => {
|
||||
try {
|
||||
const nameElement = element.querySelector('.name, .nom, .person-name, h3, h4');
|
||||
const dateElement = element.querySelector('.date, .publication-date, .date-publication');
|
||||
const linkElement = element.querySelector('a[href]');
|
||||
|
||||
const name = nameElement ? nameElement.textContent.trim() : `${firstName} ${lastName}`.trim();
|
||||
const date = dateElement ? dateElement.textContent.trim() : new Date().toISOString().split('T')[0];
|
||||
const sourceUrl = linkElement ? new URL(linkElement.href, BODACC_BASE_URL).href : BODACC_BASE_URL;
|
||||
|
||||
// Calcul du score de correspondance basique
|
||||
const matchScore = calculateMatchScore(name, lastName, firstName);
|
||||
|
||||
if (matchScore > 0.3) { // Seuil minimum de correspondance
|
||||
results.push({
|
||||
name,
|
||||
type: 'gel-avoirs',
|
||||
date,
|
||||
sourceUrl,
|
||||
matchScore,
|
||||
description: `Résultat ${index + 1} de gel des avoirs sur Bodacc`
|
||||
});
|
||||
}
|
||||
} catch (elementError) {
|
||||
console.warn(`[Bodacc] Erreur traitement élément ${index}:`, elementError.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.warn(`[Bodacc] Erreur extraction résultats:`, error.message);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule un score de correspondance entre le nom trouvé et le nom recherché
|
||||
* @param {string} foundName - Nom trouvé dans les résultats
|
||||
* @param {string} lastName - Nom recherché
|
||||
* @param {string} firstName - Prénom recherché
|
||||
* @returns {number} Score entre 0 et 1
|
||||
*/
|
||||
function calculateMatchScore(foundName, lastName, firstName) {
|
||||
const found = foundName.toLowerCase();
|
||||
const last = lastName.toLowerCase();
|
||||
const first = firstName.toLowerCase();
|
||||
|
||||
let score = 0;
|
||||
|
||||
// Correspondance exacte du nom de famille
|
||||
if (found.includes(last)) {
|
||||
score += 0.6;
|
||||
}
|
||||
|
||||
// Correspondance du prénom si fourni
|
||||
if (first && found.includes(first)) {
|
||||
score += 0.4;
|
||||
}
|
||||
|
||||
// Bonus pour correspondance exacte
|
||||
if (found === `${first} ${last}`.trim().toLowerCase()) {
|
||||
score = 1.0;
|
||||
}
|
||||
|
||||
return Math.min(score, 1.0);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
searchBodaccGelAvoirs,
|
||||
generateBodaccSummary
|
||||
};
|
||||
|
||||
/**
|
||||
* Génère un résumé des résultats pour le PDF
|
||||
* @param {Array} results - Résultats de la recherche
|
||||
* @param {string} lastName - Nom recherché
|
||||
* @param {string} firstName - Prénom recherché
|
||||
* @returns {Object} Résumé formaté
|
||||
*/
|
||||
function generateBodaccSummary(results, lastName, firstName) {
|
||||
const totalResults = results.length;
|
||||
const highConfidenceResults = results.filter(r => r.matchScore > 0.7);
|
||||
const recentResults = results.filter(r => {
|
||||
const resultDate = new Date(r.date);
|
||||
const oneYearAgo = new Date();
|
||||
oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
|
||||
return resultDate > oneYearAgo;
|
||||
});
|
||||
|
||||
return {
|
||||
searchTarget: `${firstName} ${lastName}`.trim(),
|
||||
totalResults,
|
||||
highConfidenceResults: highConfidenceResults.length,
|
||||
recentResults: recentResults.length,
|
||||
hasGelAvoirs: totalResults > 0,
|
||||
riskLevel: totalResults === 0 ? 'Aucun' :
|
||||
highConfidenceResults.length > 0 ? 'Élevé' :
|
||||
totalResults > 0 ? 'Moyen' : 'Faible',
|
||||
summary: totalResults === 0 ?
|
||||
'Aucune mention de gel des avoirs trouvée sur Bodacc' :
|
||||
`${totalResults} mention(s) trouvée(s), ${highConfidenceResults.length} avec haute confiance`
|
||||
};
|
||||
}
|
||||
430
backend/collectors/inforgreffeCollector.js
Normal file
430
backend/collectors/inforgreffeCollector.js
Normal file
@ -0,0 +1,430 @@
|
||||
/**
|
||||
* Collecteur Inforgreffe/Societe.com - Informations entreprises
|
||||
* Scraping léger avec politesse pour récupérer les données d'entreprises
|
||||
*/
|
||||
|
||||
const fetch = require('node-fetch');
|
||||
const { JSDOM } = require('jsdom');
|
||||
|
||||
const SOCIETE_COM_BASE_URL = 'https://www.societe.com';
|
||||
const INFORGREFFE_BASE_URL = 'https://www.inforgreffe.com';
|
||||
const USER_AGENT = '4NK-IA-Front/1.0 (Document Analysis Tool)';
|
||||
const REQUEST_DELAY_MS = 1500; // 1.5 secondes entre les requêtes
|
||||
const REQUEST_TIMEOUT_MS = 12000; // 12 secondes timeout
|
||||
|
||||
/**
|
||||
* Recherche une entreprise sur Societe.com et Inforgreffe
|
||||
* @param {string} companyName - Nom de l'entreprise
|
||||
* @param {string} siren - SIREN (optionnel)
|
||||
* @returns {Promise<Object>} Résultat de la recherche
|
||||
*/
|
||||
async function searchCompanyInfo(companyName, siren = '') {
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
console.log(`[Inforgreffe] Recherche entreprise: ${companyName} ${siren ? `(SIREN: ${siren})` : ''}`);
|
||||
|
||||
// Recherche sur Societe.com d'abord (plus accessible)
|
||||
const societeComResult = await searchSocieteCom(companyName, siren);
|
||||
|
||||
// Délai de politesse
|
||||
await new Promise(resolve => setTimeout(resolve, REQUEST_DELAY_MS));
|
||||
|
||||
// Recherche sur Inforgreffe si SIREN disponible
|
||||
let inforgreffeResult = null;
|
||||
if (siren) {
|
||||
inforgreffeResult = await searchInforgreffe(siren);
|
||||
await new Promise(resolve => setTimeout(resolve, REQUEST_DELAY_MS));
|
||||
}
|
||||
|
||||
// Fusion des résultats
|
||||
const mergedResult = mergeCompanyResults(societeComResult, inforgreffeResult, companyName);
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
console.log(`[Inforgreffe] Recherche terminée en ${duration}ms`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
duration,
|
||||
company: mergedResult,
|
||||
sources: {
|
||||
societeCom: societeComResult,
|
||||
inforgreffe: inforgreffeResult
|
||||
},
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
const duration = Date.now() - startTime;
|
||||
console.error(`[Inforgreffe] Erreur recherche:`, error.message);
|
||||
|
||||
return {
|
||||
success: false,
|
||||
duration,
|
||||
error: error.message,
|
||||
company: null,
|
||||
sources: {},
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recherche sur Societe.com
|
||||
* @param {string} companyName - Nom de l'entreprise
|
||||
* @param {string} siren - SIREN (optionnel)
|
||||
* @returns {Promise<Object>} Résultat Societe.com
|
||||
*/
|
||||
async function searchSocieteCom(companyName, siren = '') {
|
||||
try {
|
||||
// Construction de l'URL de recherche
|
||||
const searchQuery = siren || companyName;
|
||||
const searchUrl = `${SOCIETE_COM_BASE_URL}/search?q=${encodeURIComponent(searchQuery)}`;
|
||||
|
||||
const response = await fetch(searchUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'User-Agent': USER_AGENT,
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'Accept-Language': 'fr-FR,fr;q=0.9,en;q=0.8',
|
||||
'Accept-Encoding': 'gzip, deflate, br',
|
||||
'Connection': 'keep-alive',
|
||||
'Upgrade-Insecure-Requests': '1'
|
||||
},
|
||||
timeout: REQUEST_TIMEOUT_MS
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const html = await response.text();
|
||||
const dom = new JSDOM(html);
|
||||
const document = dom.window.document;
|
||||
|
||||
return extractSocieteComData(document, companyName, siren);
|
||||
|
||||
} catch (error) {
|
||||
console.warn(`[Societe.com] Erreur:`, error.message);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
data: null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recherche sur Inforgreffe
|
||||
* @param {string} siren - SIREN de l'entreprise
|
||||
* @returns {Promise<Object>} Résultat Inforgreffe
|
||||
*/
|
||||
async function searchInforgreffe(siren) {
|
||||
try {
|
||||
// URL de recherche par SIREN
|
||||
const searchUrl = `${INFORGREFFE_BASE_URL}/entreprise/${siren}`;
|
||||
|
||||
const response = await fetch(searchUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'User-Agent': USER_AGENT,
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'Accept-Language': 'fr-FR,fr;q=0.9,en;q=0.8',
|
||||
'Accept-Encoding': 'gzip, deflate, br',
|
||||
'Connection': 'keep-alive',
|
||||
'Upgrade-Insecure-Requests': '1'
|
||||
},
|
||||
timeout: REQUEST_TIMEOUT_MS
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const html = await response.text();
|
||||
const dom = new JSDOM(html);
|
||||
const document = dom.window.document;
|
||||
|
||||
return extractInforgreffeData(document, siren);
|
||||
|
||||
} catch (error) {
|
||||
console.warn(`[Inforgreffe] Erreur:`, error.message);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
data: null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrait les données de Societe.com
|
||||
* @param {Document} document - Document DOM parsé
|
||||
* @param {string} companyName - Nom de l'entreprise
|
||||
* @param {string} siren - SIREN
|
||||
* @returns {Object} Données extraites
|
||||
*/
|
||||
function extractSocieteComData(document, companyName, siren) {
|
||||
try {
|
||||
const data = {
|
||||
name: companyName,
|
||||
siren: siren,
|
||||
siret: '',
|
||||
forme: '',
|
||||
capital: '',
|
||||
adresse: '',
|
||||
dirigeants: [],
|
||||
activite: '',
|
||||
dateCreation: '',
|
||||
sourceUrl: SOCIETE_COM_BASE_URL
|
||||
};
|
||||
|
||||
// Extraction du nom de l'entreprise
|
||||
const nameElement = document.querySelector('.company-name, .nom-entreprise, h1, .title');
|
||||
if (nameElement) {
|
||||
data.name = nameElement.textContent.trim();
|
||||
}
|
||||
|
||||
// Extraction du SIREN/SIRET
|
||||
const sirenElement = document.querySelector('.siren, .num-siren, [data-siren]');
|
||||
if (sirenElement) {
|
||||
const sirenText = sirenElement.textContent || sirenElement.getAttribute('data-siren');
|
||||
data.siren = extractSiren(sirenText);
|
||||
}
|
||||
|
||||
// Extraction de la forme juridique
|
||||
const formeElement = document.querySelector('.forme, .forme-juridique, .legal-form');
|
||||
if (formeElement) {
|
||||
data.forme = formeElement.textContent.trim();
|
||||
}
|
||||
|
||||
// Extraction du capital
|
||||
const capitalElement = document.querySelector('.capital, .capital-social, .share-capital');
|
||||
if (capitalElement) {
|
||||
data.capital = capitalElement.textContent.trim();
|
||||
}
|
||||
|
||||
// Extraction de l'adresse
|
||||
const adresseElement = document.querySelector('.adresse, .address, .company-address');
|
||||
if (adresseElement) {
|
||||
data.adresse = adresseElement.textContent.trim();
|
||||
}
|
||||
|
||||
// Extraction des dirigeants
|
||||
const dirigeantsElements = document.querySelectorAll('.dirigeant, .manager, .president, .gérant');
|
||||
dirigeantsElements.forEach(element => {
|
||||
const name = element.textContent.trim();
|
||||
if (name && name.length > 2) {
|
||||
data.dirigeants.push({
|
||||
nom: name,
|
||||
fonction: 'Dirigeant',
|
||||
source: 'societe.com'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Extraction de l'activité
|
||||
const activiteElement = document.querySelector('.activite, .activity, .secteur');
|
||||
if (activiteElement) {
|
||||
data.activite = activiteElement.textContent.trim();
|
||||
}
|
||||
|
||||
// Extraction de la date de création
|
||||
const dateElement = document.querySelector('.date-creation, .creation-date, .date-creation-entreprise');
|
||||
if (dateElement) {
|
||||
data.dateCreation = dateElement.textContent.trim();
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.warn(`[Societe.com] Erreur extraction:`, error.message);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
data: null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrait les données d'Inforgreffe
|
||||
* @param {Document} document - Document DOM parsé
|
||||
* @param {string} siren - SIREN
|
||||
* @returns {Object} Données extraites
|
||||
*/
|
||||
function extractInforgreffeData(document, siren) {
|
||||
try {
|
||||
const data = {
|
||||
siren: siren,
|
||||
name: '',
|
||||
siret: '',
|
||||
forme: '',
|
||||
capital: '',
|
||||
adresse: '',
|
||||
dirigeants: [],
|
||||
activite: '',
|
||||
dateCreation: '',
|
||||
sourceUrl: `${INFORGREFFE_BASE_URL}/entreprise/${siren}`
|
||||
};
|
||||
|
||||
// Extraction du nom de l'entreprise
|
||||
const nameElement = document.querySelector('.company-name, .nom-entreprise, h1, .title');
|
||||
if (nameElement) {
|
||||
data.name = nameElement.textContent.trim();
|
||||
}
|
||||
|
||||
// Extraction du SIRET
|
||||
const siretElement = document.querySelector('.siret, .num-siret, [data-siret]');
|
||||
if (siretElement) {
|
||||
data.siret = siretElement.textContent.trim();
|
||||
}
|
||||
|
||||
// Extraction de la forme juridique
|
||||
const formeElement = document.querySelector('.forme, .forme-juridique, .legal-form');
|
||||
if (formeElement) {
|
||||
data.forme = formeElement.textContent.trim();
|
||||
}
|
||||
|
||||
// Extraction du capital
|
||||
const capitalElement = document.querySelector('.capital, .capital-social, .share-capital');
|
||||
if (capitalElement) {
|
||||
data.capital = capitalElement.textContent.trim();
|
||||
}
|
||||
|
||||
// Extraction de l'adresse
|
||||
const adresseElement = document.querySelector('.adresse, .address, .company-address');
|
||||
if (adresseElement) {
|
||||
data.adresse = adresseElement.textContent.trim();
|
||||
}
|
||||
|
||||
// Extraction des dirigeants
|
||||
const dirigeantsElements = document.querySelectorAll('.dirigeant, .manager, .president, .gérant');
|
||||
dirigeantsElements.forEach(element => {
|
||||
const name = element.textContent.trim();
|
||||
if (name && name.length > 2) {
|
||||
data.dirigeants.push({
|
||||
nom: name,
|
||||
fonction: 'Dirigeant',
|
||||
source: 'inforgreffe.com'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.warn(`[Inforgreffe] Erreur extraction:`, error.message);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
data: null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fusionne les résultats de Societe.com et Inforgreffe
|
||||
* @param {Object} societeComResult - Résultat Societe.com
|
||||
* @param {Object} inforgreffeResult - Résultat Inforgreffe
|
||||
* @param {string} originalName - Nom original de l'entreprise
|
||||
* @returns {Object} Données fusionnées
|
||||
*/
|
||||
function mergeCompanyResults(societeComResult, inforgreffeResult, originalName) {
|
||||
const merged = {
|
||||
name: originalName,
|
||||
siren: '',
|
||||
siret: '',
|
||||
forme: '',
|
||||
capital: '',
|
||||
adresse: '',
|
||||
dirigeants: [],
|
||||
activite: '',
|
||||
dateCreation: '',
|
||||
sources: []
|
||||
};
|
||||
|
||||
// Fusion des données Societe.com
|
||||
if (societeComResult.success && societeComResult.data) {
|
||||
const sc = societeComResult.data;
|
||||
merged.name = sc.name || merged.name;
|
||||
merged.siren = sc.siren || merged.siren;
|
||||
merged.siret = sc.siret || merged.siret;
|
||||
merged.forme = sc.forme || merged.forme;
|
||||
merged.capital = sc.capital || merged.capital;
|
||||
merged.adresse = sc.adresse || merged.adresse;
|
||||
merged.activite = sc.activite || merged.activite;
|
||||
merged.dateCreation = sc.dateCreation || merged.dateCreation;
|
||||
merged.dirigeants.push(...sc.dirigeants);
|
||||
merged.sources.push('societe.com');
|
||||
}
|
||||
|
||||
// Fusion des données Inforgreffe
|
||||
if (inforgreffeResult && inforgreffeResult.success && inforgreffeResult.data) {
|
||||
const ig = inforgreffeResult.data;
|
||||
merged.name = ig.name || merged.name;
|
||||
merged.siren = ig.siren || merged.siren;
|
||||
merged.siret = ig.siret || merged.siret;
|
||||
merged.forme = ig.forme || merged.forme;
|
||||
merged.capital = ig.capital || merged.capital;
|
||||
merged.adresse = ig.adresse || merged.adresse;
|
||||
merged.dateCreation = ig.dateCreation || merged.dateCreation;
|
||||
merged.dirigeants.push(...ig.dirigeants);
|
||||
merged.sources.push('inforgreffe.com');
|
||||
}
|
||||
|
||||
// Déduplication des dirigeants
|
||||
merged.dirigeants = merged.dirigeants.filter((dirigeant, index, self) =>
|
||||
index === self.findIndex(d => d.nom === dirigeant.nom)
|
||||
);
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrait le SIREN d'un texte
|
||||
* @param {string} text - Texte contenant potentiellement un SIREN
|
||||
* @returns {string} SIREN extrait
|
||||
*/
|
||||
function extractSiren(text) {
|
||||
if (!text) return '';
|
||||
|
||||
// Recherche d'un SIREN (9 chiffres)
|
||||
const sirenMatch = text.match(/\b(\d{9})\b/);
|
||||
return sirenMatch ? sirenMatch[1] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un résumé des données d'entreprise pour le PDF
|
||||
* @param {Object} companyData - Données de l'entreprise
|
||||
* @returns {Object} Résumé formaté
|
||||
*/
|
||||
function generateCompanySummary(companyData) {
|
||||
return {
|
||||
name: companyData.name,
|
||||
siren: companyData.siren,
|
||||
siret: companyData.siret,
|
||||
forme: companyData.forme,
|
||||
capital: companyData.capital,
|
||||
adresse: companyData.adresse,
|
||||
dirigeantsCount: companyData.dirigeants.length,
|
||||
activite: companyData.activite,
|
||||
dateCreation: companyData.dateCreation,
|
||||
sources: companyData.sources,
|
||||
hasCompleteInfo: !!(companyData.siren && companyData.forme && companyData.adresse),
|
||||
summary: companyData.siren ?
|
||||
`Entreprise trouvée: ${companyData.name} (SIREN: ${companyData.siren})` :
|
||||
`Informations partielles pour: ${companyData.name}`
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
searchCompanyInfo,
|
||||
generateCompanySummary
|
||||
};
|
||||
366
backend/collectors/pdfGenerator.js
Normal file
366
backend/collectors/pdfGenerator.js
Normal file
@ -0,0 +1,366 @@
|
||||
/**
|
||||
* Générateur de PDF pour les entités enrichies
|
||||
* Génère des PDF formatés pour les personnes, adresses et entreprises
|
||||
*/
|
||||
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* Génère un PDF pour une personne (Bodacc - gel des avoirs)
|
||||
* @param {Object} personData - Données de la personne
|
||||
* @param {Object} bodaccResult - Résultat de la recherche Bodacc
|
||||
* @param {string} outputPath - Chemin de sortie du PDF
|
||||
* @returns {Promise<string>} Chemin du PDF généré
|
||||
*/
|
||||
async function generatePersonPdf(personData, bodaccResult, outputPath) {
|
||||
try {
|
||||
const pdfContent = generatePersonPdfContent(personData, bodaccResult);
|
||||
await fs.writeFile(outputPath, pdfContent, 'utf8');
|
||||
console.log(`[PDF] Personne généré: ${outputPath}`);
|
||||
return outputPath;
|
||||
} catch (error) {
|
||||
console.error(`[PDF] Erreur génération personne:`, error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un PDF pour une entreprise (Inforgreffe/Societe.com)
|
||||
* @param {Object} companyData - Données de l'entreprise
|
||||
* @param {Object} inforgreffeResult - Résultat de la recherche Inforgreffe
|
||||
* @param {string} outputPath - Chemin de sortie du PDF
|
||||
* @returns {Promise<string>} Chemin du PDF généré
|
||||
*/
|
||||
async function generateCompanyPdf(companyData, inforgreffeResult, outputPath) {
|
||||
try {
|
||||
const pdfContent = generateCompanyPdfContent(companyData, inforgreffeResult);
|
||||
await fs.writeFile(outputPath, pdfContent, 'utf8');
|
||||
console.log(`[PDF] Entreprise généré: ${outputPath}`);
|
||||
return outputPath;
|
||||
} catch (error) {
|
||||
console.error(`[PDF] Erreur génération entreprise:`, error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un PDF pour une adresse (Cadastre/GéoRisque)
|
||||
* @param {Object} addressData - Données de l'adresse
|
||||
* @param {Object} geoResult - Résultat de la recherche géographique
|
||||
* @param {string} outputPath - Chemin de sortie du PDF
|
||||
* @returns {Promise<string>} Chemin du PDF généré
|
||||
*/
|
||||
async function generateAddressPdf(addressData, geoResult, outputPath) {
|
||||
try {
|
||||
const pdfContent = generateAddressPdfContent(addressData, geoResult);
|
||||
await fs.writeFile(outputPath, pdfContent, 'utf8');
|
||||
console.log(`[PDF] Adresse généré: ${outputPath}`);
|
||||
return outputPath;
|
||||
} catch (error) {
|
||||
console.error(`[PDF] Erreur génération adresse:`, error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère le contenu HTML pour le PDF d'une personne
|
||||
* @param {Object} personData - Données de la personne
|
||||
* @param {Object} bodaccResult - Résultat Bodacc
|
||||
* @returns {string} Contenu HTML
|
||||
*/
|
||||
function generatePersonPdfContent(personData, bodaccResult) {
|
||||
const summary = bodaccResult.summary || {};
|
||||
const results = bodaccResult.results || [];
|
||||
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Rapport Bodacc - ${personData.firstName} ${personData.lastName}</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 20px; line-height: 1.6; }
|
||||
.header { background: #f5f5f5; padding: 20px; border-radius: 5px; margin-bottom: 20px; }
|
||||
.title { color: #333; margin: 0; }
|
||||
.subtitle { color: #666; margin: 5px 0 0 0; }
|
||||
.section { margin: 20px 0; }
|
||||
.section h2 { color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 5px; }
|
||||
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
|
||||
.info-item { background: #f9f9f9; padding: 15px; border-radius: 5px; }
|
||||
.info-label { font-weight: bold; color: #555; }
|
||||
.info-value { margin-top: 5px; }
|
||||
.risk-high { color: #e74c3c; font-weight: bold; }
|
||||
.risk-medium { color: #f39c12; font-weight: bold; }
|
||||
.risk-low { color: #27ae60; font-weight: bold; }
|
||||
.result-item { background: #fff; border: 1px solid #ddd; padding: 15px; margin: 10px 0; border-radius: 5px; }
|
||||
.result-date { color: #666; font-size: 0.9em; }
|
||||
.result-score { background: #3498db; color: white; padding: 2px 8px; border-radius: 3px; font-size: 0.8em; }
|
||||
.footer { margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd; color: #666; font-size: 0.9em; }
|
||||
.no-results { text-align: center; color: #27ae60; font-style: italic; padding: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1 class="title">Rapport Bodacc - Gel des avoirs</h1>
|
||||
<p class="subtitle">Généré le ${new Date().toLocaleDateString('fr-FR')} à ${new Date().toLocaleTimeString('fr-FR')}</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Identité recherchée</h2>
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<div class="info-label">Nom complet</div>
|
||||
<div class="info-value">${personData.firstName} ${personData.lastName}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Date de naissance</div>
|
||||
<div class="info-value">${personData.birthDate || 'Non renseignée'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Résumé de la recherche</h2>
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<div class="info-label">Statut</div>
|
||||
<div class="info-value class="risk-${summary.riskLevel?.toLowerCase() || 'low'}">${summary.riskLevel || 'Faible'}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Résultats trouvés</div>
|
||||
<div class="info-value">${summary.totalResults || 0}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Haute confiance</div>
|
||||
<div class="info-value">${summary.highConfidenceResults || 0}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Récents (1 an)</div>
|
||||
<div class="info-value">${summary.recentResults || 0}</div>
|
||||
</div>
|
||||
</div>
|
||||
<p><strong>Synthèse:</strong> ${summary.summary || 'Aucune donnée disponible'}</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Détail des résultats</h2>
|
||||
${results.length === 0 ?
|
||||
'<div class="no-results">✅ Aucune mention de gel des avoirs trouvée</div>' :
|
||||
results.map(result => `
|
||||
<div class="result-item">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<h3 style="margin: 0;">${result.name}</h3>
|
||||
<span class="result-score">Score: ${(result.matchScore * 100).toFixed(0)}%</span>
|
||||
</div>
|
||||
<div class="result-date">Date: ${result.date}</div>
|
||||
<p><strong>Description:</strong> ${result.description}</p>
|
||||
<p><strong>Source:</strong> <a href="${result.sourceUrl}" target="_blank">${result.sourceUrl}</a></p>
|
||||
</div>
|
||||
`).join('')
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p><strong>Source:</strong> ${bodaccResult.source || 'Bodacc.fr'}</p>
|
||||
<p><strong>Recherche effectuée le:</strong> ${bodaccResult.timestamp ? new Date(bodaccResult.timestamp).toLocaleString('fr-FR') : 'Non disponible'}</p>
|
||||
<p><strong>Durée de la recherche:</strong> ${bodaccResult.duration || 0}ms</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>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère le contenu HTML pour le PDF d'une entreprise
|
||||
* @param {Object} companyData - Données de l'entreprise
|
||||
* @param {Object} inforgreffeResult - Résultat Inforgreffe
|
||||
* @returns {string} Contenu HTML
|
||||
*/
|
||||
function generateCompanyPdfContent(companyData, inforgreffeResult) {
|
||||
const company = inforgreffeResult.company || {};
|
||||
const sources = inforgreffeResult.sources || {};
|
||||
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Rapport Entreprise - ${company.name || companyData.name}</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 20px; line-height: 1.6; }
|
||||
.header { background: #f5f5f5; padding: 20px; border-radius: 5px; margin-bottom: 20px; }
|
||||
.title { color: #333; margin: 0; }
|
||||
.subtitle { color: #666; margin: 5px 0 0 0; }
|
||||
.section { margin: 20px 0; }
|
||||
.section h2 { color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 5px; }
|
||||
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
|
||||
.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-partial { color: #f39c12; font-weight: bold; }
|
||||
.status-missing { color: #e74c3c; font-weight: bold; }
|
||||
.dirigeant-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; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1 class="title">Rapport Entreprise</h1>
|
||||
<p class="subtitle">Généré le ${new Date().toLocaleDateString('fr-FR')} à ${new Date().toLocaleTimeString('fr-FR')}</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Informations générales</h2>
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<div class="info-label">Raison sociale</div>
|
||||
<div class="info-value">${company.name || companyData.name || 'Non renseignée'}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">SIREN</div>
|
||||
<div class="info-value class="status-${company.siren ? 'ok' : 'missing'}">${company.siren || 'Non trouvé'}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">SIRET</div>
|
||||
<div class="info-value class="status-${company.siret ? 'ok' : 'missing'}">${company.siret || 'Non trouvé'}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Forme juridique</div>
|
||||
<div class="info-value class="status-${company.forme ? 'ok' : 'missing'}">${company.forme || 'Non renseignée'}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Capital social</div>
|
||||
<div class="info-value class="status-${company.capital ? 'ok' : 'missing'}">${company.capital || 'Non renseigné'}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Date de création</div>
|
||||
<div class="info-value class="status-${company.dateCreation ? 'ok' : 'missing'}">${company.dateCreation || 'Non renseignée'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Adresse</h2>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Adresse complète</div>
|
||||
<div class="info-value class="status-${company.adresse ? 'ok' : 'missing'}">${company.adresse || 'Non renseignée'}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Activité</h2>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Secteur d'activité</div>
|
||||
<div class="info-value class="status-${company.activite ? 'ok' : 'missing'}">${company.activite || 'Non renseignée'}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Dirigeants (${company.dirigeants?.length || 0})</h2>
|
||||
${company.dirigeants && company.dirigeants.length > 0 ?
|
||||
company.dirigeants.map(dirigeant => `
|
||||
<div class="dirigeant-item">
|
||||
<strong>${dirigeant.nom}</strong> - ${dirigeant.fonction || 'Dirigeant'}
|
||||
<br><small>Source: ${dirigeant.source || 'Non spécifiée'}</small>
|
||||
</div>
|
||||
`).join('') :
|
||||
'<p class="status-missing">Aucun dirigeant trouvé</p>'
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Sources consultées</h2>
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<div class="info-label">Societe.com</div>
|
||||
<div class="info-value class="status-${sources.societeCom?.success ? 'ok' : 'missing'}">${sources.societeCom?.success ? '✅ Consulté' : '❌ Non accessible'}</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Inforgreffe</div>
|
||||
<div class="info-value class="status-${sources.inforgreffe?.success ? 'ok' : 'missing'}">${sources.inforgreffe?.success ? '✅ Consulté' : '❌ Non accessible'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p><strong>Recherche effectuée le:</strong> ${inforgreffeResult.timestamp ? new Date(inforgreffeResult.timestamp).toLocaleString('fr-FR') : 'Non disponible'}</p>
|
||||
<p><strong>Durée de la recherche:</strong> ${inforgreffeResult.duration || 0}ms</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>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère le contenu HTML pour le PDF d'une adresse
|
||||
* @param {Object} addressData - Données de l'adresse
|
||||
* @param {Object} geoResult - Résultat géographique
|
||||
* @returns {string} Contenu HTML
|
||||
*/
|
||||
function generateAddressPdfContent(addressData, geoResult) {
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Rapport Adresse - ${addressData.street || 'Adresse'}</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 20px; line-height: 1.6; }
|
||||
.header { background: #f5f5f5; padding: 20px; border-radius: 5px; margin-bottom: 20px; }
|
||||
.title { color: #333; margin: 0; }
|
||||
.subtitle { color: #666; margin: 5px 0 0 0; }
|
||||
.section { margin: 20px 0; }
|
||||
.section h2 { color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 5px; }
|
||||
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
|
||||
.info-item { background: #f9f9f9; padding: 15px; border-radius: 5px; }
|
||||
.info-label { font-weight: bold; color: #555; }
|
||||
.info-value { margin-top: 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; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1 class="title">Rapport Adresse</h1>
|
||||
<p class="subtitle">Généré le ${new Date().toLocaleDateString('fr-FR')} à ${new Date().toLocaleTimeString('fr-FR')}</p>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Adresse analysée</h2>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Adresse complète</div>
|
||||
<div class="info-value">${addressData.street || ''} ${addressData.postalCode || ''} ${addressData.city || ''} ${addressData.country || ''}</div>
|
||||
</div>
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p><em>Ce rapport a été généré automatiquement par 4NK IA Front.</em></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generatePersonPdf,
|
||||
generateCompanyPdf,
|
||||
generateAddressPdf
|
||||
};
|
||||
@ -16,6 +16,11 @@ const { preprocessImageForOCR, analyzeImageMetadata } = require('./imagePreproce
|
||||
const { nameConfidenceBoost } = require('./nameDirectory')
|
||||
const pdf = require('pdf-parse')
|
||||
|
||||
// Collecteurs d'enrichissement
|
||||
const { searchBodaccGelAvoirs, generateBodaccSummary } = require('./collectors/bodaccCollector')
|
||||
const { searchCompanyInfo, generateCompanySummary } = require('./collectors/inforgreffeCollector')
|
||||
const { generatePersonPdf, generateCompanyPdf, generateAddressPdf } = require('./collectors/pdfGenerator')
|
||||
|
||||
const app = express()
|
||||
const PORT = process.env.PORT || 3001
|
||||
|
||||
@ -2574,9 +2579,9 @@ app.get('/api/health', (req, res) => {
|
||||
})
|
||||
})
|
||||
|
||||
// Enrichissement asynchrone des entités (squelette)
|
||||
// Enrichissement asynchrone des entités avec collecteurs réels
|
||||
// Démarre une collecte et enregistre un statut côté cache
|
||||
app.post('/api/folders/:folderHash/files/:fileHash/enrich/:kind', (req, res) => {
|
||||
app.post('/api/folders/:folderHash/files/:fileHash/enrich/:kind', async (req, res) => {
|
||||
try {
|
||||
const { folderHash, fileHash, kind } = req.params
|
||||
if (!['person', 'address', 'company'].includes(kind)) {
|
||||
@ -2598,30 +2603,120 @@ app.post('/api/folders/:folderHash/files/:fileHash/enrich/:kind', (req, res) =>
|
||||
}
|
||||
fs.writeFileSync(statusPath, JSON.stringify(status, null, 2))
|
||||
|
||||
// Simuler une collecte asynchrone courte
|
||||
setTimeout(() => {
|
||||
res.json({ success: true, message: 'Enrichissement démarré' })
|
||||
|
||||
// Enrichissement asynchrone selon le type
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
let result = null
|
||||
let pdfGenerated = false
|
||||
|
||||
if (kind === 'person') {
|
||||
// Recherche Bodacc pour les personnes
|
||||
const cacheFile = path.join(cachePath, `${fileHash}.json`)
|
||||
if (fs.existsSync(cacheFile)) {
|
||||
const cacheData = JSON.parse(fs.readFileSync(cacheFile, 'utf8'))
|
||||
const persons = cacheData.entities?.persons || []
|
||||
|
||||
for (const person of persons) {
|
||||
if (person.lastName) {
|
||||
console.log(`[Enrich] Recherche Bodacc pour: ${person.firstName} ${person.lastName}`)
|
||||
result = await searchBodaccGelAvoirs(person.lastName, person.firstName)
|
||||
|
||||
if (result.success) {
|
||||
const summary = generateBodaccSummary(result.results, person.lastName, person.firstName)
|
||||
result.summary = summary
|
||||
|
||||
// Génération du PDF
|
||||
try {
|
||||
await generatePersonPdf(person, result, pdfPath)
|
||||
pdfGenerated = true
|
||||
} catch (pdfError) {
|
||||
console.warn(`[Enrich] Erreur génération PDF personne:`, pdfError.message)
|
||||
}
|
||||
}
|
||||
break // Traiter seulement la première personne trouvée
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (kind === 'company') {
|
||||
// Recherche Inforgreffe pour les entreprises
|
||||
const cacheFile = path.join(cachePath, `${fileHash}.json`)
|
||||
if (fs.existsSync(cacheFile)) {
|
||||
const cacheData = JSON.parse(fs.readFileSync(cacheFile, 'utf8'))
|
||||
const companies = cacheData.entities?.companies || []
|
||||
|
||||
for (const company of companies) {
|
||||
if (company.name) {
|
||||
console.log(`[Enrich] Recherche Inforgreffe pour: ${company.name}`)
|
||||
result = await searchCompanyInfo(company.name, company.siren)
|
||||
|
||||
if (result.success) {
|
||||
// Génération du PDF
|
||||
try {
|
||||
await generateCompanyPdf(company, result, pdfPath)
|
||||
pdfGenerated = true
|
||||
} catch (pdfError) {
|
||||
console.warn(`[Enrich] Erreur génération PDF entreprise:`, pdfError.message)
|
||||
}
|
||||
}
|
||||
break // Traiter seulement la première entreprise trouvée
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (kind === 'address') {
|
||||
// Placeholder pour les adresses (géocodage à implémenter)
|
||||
result = {
|
||||
success: true,
|
||||
duration: 1000,
|
||||
message: 'Enrichissement adresse en cours de développement',
|
||||
data: { placeholder: true }
|
||||
}
|
||||
|
||||
// Génération du PDF placeholder
|
||||
try {
|
||||
const addressData = { street: 'Adresse', city: 'Ville' }
|
||||
await generateAddressPdf(addressData, result, pdfPath)
|
||||
pdfGenerated = true
|
||||
} catch (pdfError) {
|
||||
console.warn(`[Enrich] Erreur génération PDF adresse:`, pdfError.message)
|
||||
}
|
||||
}
|
||||
|
||||
// Mise à jour du statut final
|
||||
const done = {
|
||||
...status,
|
||||
state: 'done',
|
||||
kind,
|
||||
state: result?.success ? 'done' : 'error',
|
||||
startedAt: status.startedAt,
|
||||
finishedAt: new Date().toISOString(),
|
||||
message: 'Collecte terminée',
|
||||
sources: (kind === 'person')
|
||||
? ['bodacc_gel_avoirs']
|
||||
: (kind === 'company')
|
||||
? ['kbis_inforgreffe', 'societe_com']
|
||||
: ['cadastre', 'georisque', 'geofoncier'],
|
||||
message: result?.success ? 'Collecte terminée' : 'Erreur de collecte',
|
||||
sources: result?.sources ? Object.keys(result.sources) :
|
||||
(kind === 'person') ? ['bodacc_gel_avoirs'] :
|
||||
(kind === 'company') ? ['kbis_inforgreffe', 'societe_com'] :
|
||||
['cadastre', 'georisque', 'geofoncier'],
|
||||
data: result,
|
||||
pdfGenerated
|
||||
}
|
||||
fs.writeFileSync(statusPath, JSON.stringify(done, null, 2))
|
||||
// Générer un PDF minimal (texte) pour preuve de concept
|
||||
try {
|
||||
const content = `Dossier d'enrichissement\nKind: ${kind}\nFichier: ${fileHash}\nSources: ${done.sources.join(', ')}\nDate: ${new Date().toISOString()}\n`
|
||||
fs.writeFileSync(pdfPath, content)
|
||||
} catch {}
|
||||
} catch {}
|
||||
}, 1500)
|
||||
|
||||
console.log(`[Enrich] Terminé pour ${kind}:`, done.state)
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[Enrich] Erreur enrichissement ${kind}:`, error.message)
|
||||
|
||||
const errorStatus = {
|
||||
kind,
|
||||
state: 'error',
|
||||
startedAt: status.startedAt,
|
||||
finishedAt: new Date().toISOString(),
|
||||
message: `Erreur: ${error.message}`,
|
||||
sources: [],
|
||||
error: error.message
|
||||
}
|
||||
fs.writeFileSync(statusPath, JSON.stringify(errorStatus, null, 2))
|
||||
}
|
||||
}, 2000) // Délai de 2 secondes pour laisser le temps aux collecteurs
|
||||
|
||||
return res.json({ success: true })
|
||||
} catch (e) {
|
||||
return res.status(500).json({ success: false, error: e?.message || String(e) })
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user