intelwatch 1.0.1 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "intelwatch",
3
- "version": "1.0.1",
3
+ "version": "1.1.1",
4
4
  "description": "Competitive intelligence CLI — track competitors, keywords, and brand mentions from the terminal",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -12,6 +12,7 @@
12
12
  "start": "node bin/intelwatch.js"
13
13
  },
14
14
  "dependencies": {
15
+ "@recognity/pdf-report": "file:../shared-pdf",
15
16
  "axios": "^1.7.9",
16
17
  "chalk": "^5.3.0",
17
18
  "cheerio": "^1.0.0",
@@ -0,0 +1,400 @@
1
+ import chalk from 'chalk';
2
+ import Table from 'cli-table3';
3
+ import { pappersGetFullDossier, pappersSearchByName } from '../scrapers/pappers.js';
4
+ import { searchPressMentions } from '../scrapers/brave-search.js';
5
+ import { analyzeSite } from '../scrapers/site-analyzer.js';
6
+ import { callAI, hasAIKey } from '../ai/client.js';
7
+ import { header, section, warn, error } from '../utils/display.js';
8
+ import { generatePDF } from '@recognity/pdf-report';
9
+
10
+ const LICENSE_URL = 'https://recognity.fr/tools/intelwatch';
11
+
12
+ export async function runMA(sirenOrName, options) {
13
+ const hasLicense = !!process.env.INTELWATCH_LICENSE_KEY;
14
+ const isPreview = !!options.preview;
15
+
16
+ // ── License gate ───────────────────────────────────────────────────────────
17
+ if (!hasLicense && !isPreview) {
18
+ console.log(chalk.yellow.bold('\n ⚡ Deep Profile Due Diligence — Module Premium\n'));
19
+ console.log(chalk.red(' The Deep Profile requires an Intelwatch Deep Profile license.'));
20
+ console.log(chalk.gray(` Get yours at ${LICENSE_URL}\n`));
21
+ console.log(chalk.gray(' Run with --preview for a limited preview (company identity + last year financials only).'));
22
+ console.log('');
23
+ process.exit(1);
24
+ }
25
+
26
+ if (isPreview && !hasLicense) {
27
+ console.log(chalk.yellow(' ⚡ PREVIEW MODE — Company identity + last year financials only'));
28
+ console.log(chalk.gray(` Upgrade to Intelwatch Deep Profile for full due diligence: ${LICENSE_URL}\n`));
29
+ }
30
+
31
+ // ── SIREN or name lookup ───────────────────────────────────────────────────
32
+ let siren = sirenOrName;
33
+
34
+ if (!/^\d{9}$/.test(sirenOrName)) {
35
+ console.log(chalk.gray(` Searching for: "${sirenOrName}"...`));
36
+ const { results, error: searchErr } = await pappersSearchByName(sirenOrName, { count: 1 });
37
+ if (searchErr || !results.length) {
38
+ error(`Company not found: ${searchErr || 'No results'}`);
39
+ process.exit(1);
40
+ }
41
+ siren = results[0].siren;
42
+ const foundName = results[0].nom_entreprise || results[0].denomination;
43
+ console.log(chalk.gray(` Found: ${foundName} (SIREN: ${siren})`));
44
+ }
45
+
46
+ // ── Fetch full dossier ─────────────────────────────────────────────────────
47
+ console.log(chalk.gray(' Fetching dossier from Pappers...'));
48
+ const { data, error: dossierErr } = await pappersGetFullDossier(siren);
49
+
50
+ if (dossierErr || !data) {
51
+ error(`Failed to fetch dossier: ${dossierErr || 'Unknown error'}`);
52
+ process.exit(1);
53
+ }
54
+
55
+ const { identity, financialHistory, ubo, bodacc, dirigeants, proceduresCollectives } = data;
56
+
57
+ // ── Header ─────────────────────────────────────────────────────────────────
58
+ header(`🏢 Due Diligence Deep Profile — ${identity.name || siren}`);
59
+
60
+ // ── Company Identity ───────────────────────────────────────────────────────
61
+ section(' 📋 Identité');
62
+ const statusColor = identity.status === 'Actif' ? chalk.green : chalk.red;
63
+ printRow('Nom', identity.name);
64
+ printRow('SIREN', identity.siren);
65
+ printRow('SIRET siège', identity.siret);
66
+ printRow('Forme juridique', identity.formeJuridique);
67
+ printRow('Capital', identity.capital != null ? `${formatEuro(identity.capital)} ${identity.capitalMonnaie}` : null);
68
+ printRow('NAF', identity.nafCode ? `${identity.nafCode} — ${identity.nafLabel}` : null);
69
+ printRow('Création', identity.dateCreation);
70
+ printRow('Statut', identity.status, statusColor(identity.status));
71
+ printRow('Effectifs', identity.effectifs);
72
+ printRow('Adresse', [identity.adresse, identity.codePostal, identity.ville].filter(Boolean).join(' ') || null);
73
+ if (identity.website) printRow('Site web', identity.website);
74
+
75
+ // ── Preview mode stops here (one year of financials) ──────────────────────
76
+ if (isPreview) {
77
+ const lastFin = financialHistory[0];
78
+ section(' 💶 Derniers résultats financiers (preview)');
79
+ if (lastFin) {
80
+ printRow('Année', String(lastFin.annee));
81
+ printRow('Chiffre d\'affaires', lastFin.ca != null ? formatEuro(lastFin.ca) : null);
82
+ printRow('Résultat net', lastFin.resultat != null ? formatEuro(lastFin.resultat) : null);
83
+ printRow('Capitaux propres', lastFin.capitauxPropres != null ? formatEuro(lastFin.capitauxPropres) : null);
84
+ } else {
85
+ console.log(chalk.gray(' Données financières non disponibles.'));
86
+ }
87
+ console.log('');
88
+ console.log(chalk.yellow(` ⚡ Accédez au rapport complet avec Intelwatch Deep Profile : ${LICENSE_URL}`));
89
+ console.log('');
90
+ return;
91
+ }
92
+
93
+ // ══════════════════════════════════════════════════════════════════════════
94
+ // FULL MODE (licensed users)
95
+ // ══════════════════════════════════════════════════════════════════════════
96
+
97
+ // ── Procédures collectives (alert at top if any) ──────────────────────────
98
+ if (proceduresCollectives.length > 0) {
99
+ section(' 🚨 Procédures collectives');
100
+ for (const p of proceduresCollectives) {
101
+ const label = [p.type, p.jugement].filter(Boolean).join(' — ');
102
+ const loc = p.tribunal ? ` (${p.tribunal})` : '';
103
+ console.log(chalk.red(` [${p.date || '?'}] ${label}${loc}`));
104
+ }
105
+ }
106
+
107
+ // ── Dirigeants & mandats ───────────────────────────────────────────────────
108
+ if (dirigeants.length > 0) {
109
+ section(` 👔 Dirigeants (${dirigeants.length})`);
110
+ for (const d of dirigeants) {
111
+ const name = [d.prenom, d.nom].filter(Boolean).join(' ');
112
+ console.log('');
113
+ console.log(' ' + chalk.white.bold(name) + chalk.gray(` — ${d.role || '?'}`));
114
+ if (d.dateNomination) console.log(chalk.gray(` Nommé le : ${d.dateNomination}`));
115
+ if (d.nationalite) console.log(chalk.gray(` Nationalité : ${d.nationalite}`));
116
+ if (d.mandats.length > 0) {
117
+ console.log(chalk.gray(` Mandats (${d.mandats.length}) :`));
118
+ for (const m of d.mandats.slice(0, 6)) {
119
+ const dot = m.etat === 'actif' ? chalk.green('●') : chalk.gray('○');
120
+ const denom = m.denomination || m.siren || '?';
121
+ console.log(chalk.gray(` ${dot} ${denom} — ${m.role || '?'}`));
122
+ }
123
+ if (d.mandats.length > 6) {
124
+ console.log(chalk.gray(` ... et ${d.mandats.length - 6} autre(s)`));
125
+ }
126
+ }
127
+ }
128
+ console.log('');
129
+ }
130
+
131
+ // ── UBO ───────────────────────────────────────────────────────────────────
132
+ section(` 🔑 Bénéficiaires effectifs — UBO (${ubo.length})`);
133
+ if (ubo.length > 0) {
134
+ for (const b of ubo) {
135
+ const name = [b.prenom, b.nom].filter(Boolean).join(' ');
136
+ const stakes = [];
137
+ if (b.pourcentageParts != null) stakes.push(`${b.pourcentageParts}% parts`);
138
+ if (b.pourcentageVotes != null) stakes.push(`${b.pourcentageVotes}% votes`);
139
+ const stakeStr = stakes.length ? chalk.yellow(` — ${stakes.join(', ')}`) : '';
140
+ console.log(' ' + chalk.white(name) + stakeStr);
141
+ if (b.nationalite) console.log(chalk.gray(` Nationalité : ${b.nationalite}`));
142
+ if (b.dateNaissance) console.log(chalk.gray(` Né(e) le : ${b.dateNaissance}`));
143
+ }
144
+ } else {
145
+ console.log(chalk.gray(' Non disponible ou non déclaré.'));
146
+ }
147
+
148
+ // ── Financial history table ────────────────────────────────────────────────
149
+ section(' 💶 Historique financier');
150
+ if (financialHistory.length > 0) {
151
+ const table = new Table({
152
+ head: ['Année', 'Chiffre d\'affaires', 'Résultat net', 'Capitaux propres'].map(h => chalk.cyan.bold(h)),
153
+ style: { head: [], border: ['grey'] },
154
+ colAligns: ['left', 'right', 'right', 'right'],
155
+ });
156
+ for (const f of financialHistory) {
157
+ table.push([
158
+ chalk.white(f.annee ?? '—'),
159
+ f.ca != null ? chalk.white(formatEuro(f.ca)) : chalk.gray('—'),
160
+ f.resultat != null
161
+ ? (f.resultat >= 0 ? chalk.green(formatEuro(f.resultat)) : chalk.red(formatEuro(f.resultat)))
162
+ : chalk.gray('—'),
163
+ f.capitauxPropres != null
164
+ ? (f.capitauxPropres >= 0 ? chalk.white(formatEuro(f.capitauxPropres)) : chalk.red(formatEuro(f.capitauxPropres)))
165
+ : chalk.gray('—'),
166
+ ]);
167
+ }
168
+ console.log(table.toString());
169
+ } else {
170
+ console.log(chalk.gray(' Aucune donnée financière disponible.'));
171
+ }
172
+
173
+ // ── BODACC publications ────────────────────────────────────────────────────
174
+ if (bodacc.length > 0) {
175
+ section(` 📰 Publications BODACC (${bodacc.length} dernières)`);
176
+ for (const pub of bodacc) {
177
+ const label = pub.description || pub.type || '?';
178
+ const trib = pub.tribunal ? chalk.gray(` — ${pub.tribunal}`) : '';
179
+ console.log(chalk.gray(` [${pub.date || '?'}] `) + chalk.white(label) + trib);
180
+ }
181
+ }
182
+
183
+ // ── Digital footprint ─────────────────────────────────────────────────────
184
+ const websiteUrl = identity.website
185
+ ? (identity.website.startsWith('http') ? identity.website : `https://${identity.website}`)
186
+ : null;
187
+
188
+ if (websiteUrl) {
189
+ section(' 🌐 Empreinte numérique');
190
+ console.log(chalk.gray(` Analyzing ${websiteUrl}...`));
191
+ try {
192
+ const siteData = await analyzeSite(websiteUrl);
193
+ if (siteData.error) {
194
+ warn(` Site non accessible: ${siteData.error}`);
195
+ } else {
196
+ const techNames = (siteData.techStack || []).map(t => t.name).join(', ') || 'aucune détectée';
197
+ printRow('Technologies', techNames);
198
+ if (siteData.performance) {
199
+ printRow('Performance', `${siteData.performance.responseTimeMs}ms, ${siteData.performance.htmlSizeKB} KB`);
200
+ }
201
+ if (siteData.security) {
202
+ const s = siteData.security;
203
+ const score = [s.https, s.hsts, s.xFrameOptions, s.csp, s.xContentType].filter(Boolean).length;
204
+ printRow('Sécurité', `${score}/5 (HTTPS:${s.https ? '✓' : '✗'} HSTS:${s.hsts ? '✓' : '✗'} CSP:${s.csp ? '✓' : '✗'})`);
205
+ }
206
+ if (siteData.socialLinks && Object.keys(siteData.socialLinks).length > 0) {
207
+ printRow('Réseaux sociaux', Object.keys(siteData.socialLinks).join(', '));
208
+ }
209
+ if (siteData.contentStats?.recentArticles?.length > 0) {
210
+ printRow('Blog', `${siteData.contentStats.articleCount} articles récents`);
211
+ }
212
+ }
213
+ } catch (e) {
214
+ warn(` Impossible d'analyser le site: ${e.message}`);
215
+ }
216
+ }
217
+
218
+ // ── Press & mentions ───────────────────────────────────────────────────────
219
+ let pressResults = [];
220
+ if (identity.name) {
221
+ section(' 📣 Presse & réputation');
222
+ console.log(chalk.gray(` Searching mentions for "${identity.name}"...`));
223
+ try {
224
+ const press = await searchPressMentions(identity.name);
225
+ pressResults = press.mentions || [];
226
+ if (press.mentionCount > 0) {
227
+ const bd = press.mentions.reduce((acc, m) => {
228
+ const k = /positive/.test(m.sentiment) ? 'positive'
229
+ : /negative/.test(m.sentiment) ? 'negative' : 'neutral';
230
+ acc[k] = (acc[k] || 0) + 1;
231
+ return acc;
232
+ }, {});
233
+ console.log(chalk.magenta(` ${press.mentionCount} mentions | 👍${bd.positive || 0} 😐${bd.neutral || 0} 👎${bd.negative || 0}`));
234
+ for (const m of press.mentions.slice(0, 8)) {
235
+ const emoji = /positive/.test(m.sentiment) ? '👍' : /negative/.test(m.sentiment) ? '👎' : '😐';
236
+ console.log(chalk.gray(` ${emoji} [${m.category}] ${(m.title || '').substring(0, 80)} (${m.domain})`));
237
+ }
238
+ } else {
239
+ console.log(chalk.gray(' Aucune mention récente trouvée.'));
240
+ }
241
+ } catch (e) {
242
+ warn(` Press search failed: ${e.message}`);
243
+ }
244
+ }
245
+
246
+ // ── AI Summary ────────────────────────────────────────────────────────────
247
+ if (options.ai) {
248
+ section(' 🤖 Synthèse IA — Due Diligence');
249
+ if (!hasAIKey()) {
250
+ warn(' No AI API key. Set OPENAI_API_KEY or ANTHROPIC_API_KEY.');
251
+ } else {
252
+ console.log(chalk.gray(' Generating AI due diligence summary...'));
253
+ try {
254
+ const finSummary = financialHistory
255
+ .map(f => `${f.annee}: CA=${f.ca != null ? formatEuro(f.ca) : 'N/A'}, Résultat=${f.resultat != null ? formatEuro(f.resultat) : 'N/A'}, CP=${f.capitauxPropres != null ? formatEuro(f.capitauxPropres) : 'N/A'}`)
256
+ .join('\n') || 'Non disponible';
257
+
258
+ const dirStr = dirigeants
259
+ .map(d => `${d.prenom || ''} ${d.nom || ''} (${d.role || '?'}): ${d.mandats.length} mandats`)
260
+ .join(', ') || 'Non disponible';
261
+
262
+ const uboStr = ubo
263
+ .map(b => `${b.prenom || ''} ${b.nom || ''}: ${b.pourcentageParts ?? '?'}% parts`)
264
+ .join(', ') || 'Non déclaré';
265
+
266
+ const procStr = proceduresCollectives.length
267
+ ? proceduresCollectives.map(p => `${p.date || '?'}: ${p.type || '?'}`).join(', ')
268
+ : 'Aucune';
269
+
270
+ const systemPrompt = 'Tu es un analyste Deep Profile expert. Rédige une synthèse de due diligence concise et professionnelle en français.';
271
+ const userPrompt = `Synthèse due diligence pour ${identity.name} (SIREN: ${identity.siren})
272
+
273
+ **Identité**
274
+ - Forme: ${identity.formeJuridique || '?'}
275
+ - Création: ${identity.dateCreation || '?'}
276
+ - Effectifs: ${identity.effectifs || '?'}
277
+ - NAF: ${identity.nafCode} — ${identity.nafLabel}
278
+ - Capital: ${identity.capital != null ? formatEuro(identity.capital) : '?'}
279
+
280
+ **Dirigeants**
281
+ ${dirStr}
282
+
283
+ **UBO (${ubo.length})**
284
+ ${uboStr}
285
+
286
+ **Historique financier**
287
+ ${finSummary}
288
+
289
+ **Procédures collectives**
290
+ ${procStr}
291
+
292
+ **Publications BODACC récentes**
293
+ ${bodacc.slice(0, 5).map(b => `${b.date || '?'}: ${b.type || '?'}`).join(', ') || 'Aucune'}
294
+
295
+ Rédige une synthèse en 5 points : 1) Profil, 2) Gouvernance & actionnariat, 3) Situation financière, 4) Risques identifiés, 5) Points d'attention pour l'acquéreur. Sois factuel, max 400 mots.`;
296
+
297
+ const summary = await callAI(systemPrompt, userPrompt, { maxTokens: 600 });
298
+ console.log('\n' + chalk.white(summary) + '\n');
299
+ } catch (e) {
300
+ warn(` AI summary failed: ${e.message}`);
301
+ }
302
+ }
303
+ }
304
+
305
+ // ── PDF export ──────────────────────────────────────────────────────────────
306
+ if (options.format === 'pdf') {
307
+ const outputPath = options.output || `profile-${siren}.pdf`;
308
+ const fmtEuro = (n) => {
309
+ if (n == null) return '—';
310
+ const abs = Math.abs(n);
311
+ const sign = n < 0 ? '-' : '';
312
+ if (abs >= 1e9) return `${sign}${(abs/1e9).toFixed(2)}B€`;
313
+ if (abs >= 1e6) return `${sign}${(abs/1e6).toFixed(1)}M€`;
314
+ if (abs >= 1e3) return `${sign}${Math.round(abs/1e3)}K€`;
315
+ return `${sign}${abs}€`;
316
+ };
317
+
318
+ const pressMentions = [];
319
+ if (pressResults?.length) {
320
+ pressResults.forEach(m => {
321
+ pressMentions.push({ title: m.title || '', source: m.source || '', sentiment: m.sentiment || 'neutral' });
322
+ });
323
+ }
324
+
325
+ const pdfData = {
326
+ aiSummary: null, // filled below if AI was used
327
+ competitors: [{
328
+ name: identity.name || siren,
329
+ url: identity.website || 'N/A',
330
+ tech: [identity.formeJuridique, identity.nafLabel, identity.nafCode].filter(Boolean),
331
+ social: {},
332
+ pappers: {
333
+ siren: identity.siren,
334
+ forme: identity.formeJuridique,
335
+ creation: identity.dateCreation,
336
+ naf: identity.nafCode ? identity.nafCode + ' — ' + identity.nafLabel : null,
337
+ ca: financialHistory?.[0]?.ca != null ? fmtEuro(financialHistory[0].ca) : 'N/A',
338
+ effectifs: identity.effectifs || 'N/A',
339
+ dirigeants: dirigeants?.map(d => d.nom || d.denomination || '?').slice(0, 5) || [],
340
+ },
341
+ press: pressMentions.length ? {
342
+ total: pressMentions.length,
343
+ positive: pressMentions.filter(m => m.sentiment === 'positive').length,
344
+ neutral: pressMentions.filter(m => m.sentiment === 'neutral').length,
345
+ negative: pressMentions.filter(m => m.sentiment === 'negative').length,
346
+ mentions: pressMentions.slice(0, 15),
347
+ } : undefined,
348
+ strengths: [],
349
+ weaknesses: [],
350
+ summary: `${identity.name || siren} — ${identity.formeJuridique || ''}, ${identity.nafLabel || ''}. Created ${identity.dateCreation || '?'}. ${financialHistory?.length ? `Financial history: ${financialHistory.length} years available.` : 'No financial data available.'}`,
351
+ }]
352
+ };
353
+
354
+ try {
355
+ await generatePDF({
356
+ type: 'intel-report',
357
+ title: `Deep Profile — ${identity.name || siren}`,
358
+ subtitle: `Company due diligence report · ${new Date().toLocaleDateString('en-GB', { year: 'numeric', month: 'long', day: 'numeric' })}`,
359
+ output: outputPath,
360
+ branding: {
361
+ company: 'Recognity',
362
+ footer: 'Powered by Recognity · recognity.fr',
363
+ colors: { primary: '#0a0a0a', accent: '#c8a961' },
364
+ },
365
+ data: pdfData,
366
+ });
367
+ console.log(chalk.green(`\n ✅ PDF report saved to ${outputPath}\n`));
368
+ } catch (e) {
369
+ warn(` PDF generation failed: ${e.message}`);
370
+ }
371
+ }
372
+
373
+ // ── Footer ─────────────────────────────────────────────────────────────────
374
+ console.log('');
375
+ const today = new Date().toLocaleDateString('fr-FR', { year: 'numeric', month: 'long', day: 'numeric' });
376
+ console.log(chalk.gray(` Source : Pappers.fr — ${today}`));
377
+ console.log('');
378
+ }
379
+
380
+ // ── Helpers ───────────────────────────────────────────────────────────────────
381
+
382
+ function printRow(label, value, coloredValue) {
383
+ const padded = label.padEnd(16);
384
+ const display = coloredValue ?? (value != null ? chalk.white(value) : chalk.gray('—'));
385
+ console.log(chalk.gray(` ${padded}: `) + display);
386
+ }
387
+
388
+ function formatNum(n) {
389
+ return Number(n).toLocaleString('fr-FR');
390
+ }
391
+
392
+ function formatEuro(n) {
393
+ if (n == null) return '—';
394
+ const abs = Math.abs(n);
395
+ const sign = n < 0 ? '-' : '';
396
+ if (abs >= 1_000_000_000) return `${sign}${(abs / 1_000_000_000).toFixed(2).replace('.', ',')} Md€`;
397
+ if (abs >= 1_000_000) return `${sign}${(abs / 1_000_000).toFixed(1).replace('.', ',')} M€`;
398
+ if (abs >= 1_000) return `${sign}${formatNum(Math.round(abs / 1_000))} K€`;
399
+ return `${sign}${formatNum(abs)} €`;
400
+ }
package/src/index.js CHANGED
@@ -11,6 +11,7 @@ import { setupNotifications } from './commands/notify.js';
11
11
  import { listTrackers, removeTrackerCmd } from './commands/list.js';
12
12
  import { runAISummary } from './commands/ai-summary.js';
13
13
  import { runPitch } from './commands/pitch.js';
14
+ import { runMA } from './commands/profile.js';
14
15
 
15
16
  const program = new Command();
16
17
 
@@ -154,6 +155,19 @@ program
154
155
  await runPitch(trackerId, options);
155
156
  });
156
157
 
158
+ // ─── m-a ──────────────────────────────────────────────────────────────────────
159
+
160
+ program
161
+ .command('profile <siren-or-name>')
162
+ .description('Deep company profile — due diligence report (requires Pro license)')
163
+ .option('--preview', 'Run limited preview: company identity + last year financials only')
164
+ .option('--ai', 'Generate an AI-powered due diligence summary (requires AI API key)')
165
+ .option('--format <type>', 'Output format: terminal (default) or pdf')
166
+ .option('--output <path>', 'Output file path for PDF')
167
+ .action(async (sirenOrName, options) => {
168
+ await runMA(sirenOrName, options);
169
+ });
170
+
157
171
  // ─── discover ─────────────────────────────────────────────────────────────────
158
172
 
159
173
  program
@@ -1,6 +1,7 @@
1
1
  import axios from 'axios';
2
2
 
3
3
  const PAPPERS_API = 'https://api.pappers.fr/v1';
4
+ const PAPPERS_API_V2 = 'https://api.pappers.fr/v2';
4
5
 
5
6
  function getApiKey() {
6
7
  return process.env.PAPPERS_API_KEY || null;
@@ -26,7 +27,7 @@ export async function pappersSearchByName(name, options = {}) {
26
27
  },
27
28
  timeout: 10000,
28
29
  });
29
- return { results: resp.data.resultats || [], error: null };
30
+ return { results: resp.data.resultats || resp.data.entreprises || [], error: null };
30
31
  } catch (err) {
31
32
  return { results: [], error: err.message };
32
33
  }
@@ -71,6 +72,104 @@ export async function pappersLookup(companyName) {
71
72
  return formatPappersDetail(detail.data);
72
73
  }
73
74
 
75
+ /**
76
+ * Get full M&A dossier for a company by SIREN.
77
+ * Returns parsed financial history, UBO, BODACC, dirigeants with mandats,
78
+ * and collective procedures.
79
+ */
80
+ export async function pappersGetFullDossier(siren) {
81
+ const apiKey = getApiKey();
82
+ if (!apiKey) return { data: null, error: 'No PAPPERS_API_KEY set' };
83
+
84
+ try {
85
+ const resp = await axios.get(`${PAPPERS_API}/entreprise`, {
86
+ params: { api_token: apiKey, siren },
87
+ timeout: 15000,
88
+ });
89
+
90
+ const d = resp.data;
91
+
92
+ // Financial history — last 5 years
93
+ const financialHistory = (d.finances || []).slice(0, 5).map(f => ({
94
+ annee: f.annee,
95
+ ca: f.chiffre_affaires ?? null,
96
+ resultat: f.resultat ?? null,
97
+ capitauxPropres: f.capitaux_propres ?? null,
98
+ effectif: f.effectif ?? null,
99
+ }));
100
+
101
+ // UBO — bénéficiaires effectifs
102
+ const ubo = (d.beneficiaires_effectifs || []).map(b => ({
103
+ nom: b.nom,
104
+ prenom: b.prenom,
105
+ dateNaissance: b.date_de_naissance_formate || b.date_naissance || null,
106
+ nationalite: b.nationalite || null,
107
+ pourcentageParts: b.pourcentage_parts ?? null,
108
+ pourcentageVotes: b.pourcentage_votes ?? null,
109
+ }));
110
+
111
+ // BODACC publications — last 10
112
+ const bodacc = (d.publications_bodacc || []).slice(0, 10).map(p => ({
113
+ date: p.date,
114
+ type: p.type,
115
+ tribunal: p.tribunal || null,
116
+ numero: p.numero_annonce || null,
117
+ description: p.acte?.actes_publies?.[0]?.type_acte || p.type || null,
118
+ }));
119
+
120
+ // Dirigeants with their mandats in other companies
121
+ const dirigeants = (d.dirigeants || []).map(dir => ({
122
+ nom: dir.nom,
123
+ prenom: dir.prenom,
124
+ role: dir.fonction,
125
+ dateNomination: dir.date_prise_de_poste || null,
126
+ dateNaissance: dir.date_de_naissance_formate || null,
127
+ nationalite: dir.nationalite || null,
128
+ mandats: (dir.entreprises_dirigees || []).map(e => ({
129
+ siren: e.siren,
130
+ denomination: e.denomination || e.nom_entreprise || null,
131
+ role: e.fonction || null,
132
+ etat: e.etat || null,
133
+ })),
134
+ }));
135
+
136
+ // Procédures collectives
137
+ const proceduresCollectives = (d.procedures_collectives || []).map(p => ({
138
+ date: p.date_effet || p.date || null,
139
+ type: p.type || null,
140
+ jugement: p.nature_jugement || null,
141
+ tribunal: p.tribunal || null,
142
+ }));
143
+
144
+ // Company identity
145
+ const identity = {
146
+ siren: d.siren,
147
+ siret: d.siege?.siret || null,
148
+ name: d.nom_entreprise || d.denomination || null,
149
+ dateCreation: d.date_creation || null,
150
+ nafCode: d.code_naf || null,
151
+ nafLabel: d.libelle_code_naf || null,
152
+ formeJuridique: d.forme_juridique || null,
153
+ effectifs: d.tranche_effectif || d.effectif || null,
154
+ adresse: d.siege?.adresse_ligne_1 || d.siege?.adresse || null,
155
+ ville: d.siege?.ville || null,
156
+ codePostal: d.siege?.code_postal || null,
157
+ capital: d.capital ?? null,
158
+ capitalMonnaie: d.devise_capital || 'EUR',
159
+ website: d.site_internet || d.domaine_de_messagerie || null,
160
+ status: d.etat === 'actif' ? 'Actif' : (d.etat || 'Inconnu'),
161
+ dateRadiation: d.date_radiation || null,
162
+ };
163
+
164
+ return {
165
+ data: { identity, financialHistory, ubo, bodacc, dirigeants, proceduresCollectives, raw: d },
166
+ error: null,
167
+ };
168
+ } catch (err) {
169
+ return { data: null, error: err.message };
170
+ }
171
+ }
172
+
74
173
  function formatPappersResult(r) {
75
174
  return {
76
175
  siren: r.siren,