Aller au contenu

Automatisation FIFPL (sous le capot)

Le module FIFPL automatise une tâche autrement manuelle et fastidieuse : récupérer la liste officielle des participants depuis le portail FIFPL, la rapprocher des transactions HubSpot, puis propager les noms et montants corrects dans HubSpot — d’où ils redescendent vers l’internal-api. Tout est déclenché par l’opérateur depuis l’interface ; cette page décrit chaque maillon.

exportParticipants (Puppeteer → of.fifpl.fr → XLSX → lignes typées)
matchParticipantRows (worker Claude Code ; sinon repli déterministe)
buildFifplSyncUpdates (diff par champ ; ignore les deals déjà à jour)
syncFifplParticipantNames (batch HubSpot 100 par 100, idempotent)
confirmFifplSync (polling internal-api jusqu'à propagation du webhook)
[CV-12H] splitFIFPLVirtualClassModules (découpe la classe virtuelle 12 h en 4 demi-journées)

Procédure tRPC exportParticipants. Entrée : le NACPRO (référence financeur) + la date de fin de session. Elle appelle le puppeteer-service avec le script fifpl-extract-participants-obscura (Chromium furtif, pour réduire les blocages anti-bot), qui se connecte à of.fifpl.fr, navigue jusqu’au dossier et télécharge l’XLSX.

L’XLSX est ensuite parsé avec ExcelJS, de façon tolérante aux variations d’en-têtes : les colonnes sont reconnues par alias normalisés —

  • nom : nom, nom usuel, nom d'usage, nom marital, nom de famille
  • prénom : prenom, prenoms
  • nom de naissance : nom de naissance, nom de jeune fille

Les dates (série Excel, JJ/MM/AAAA, AAAA-MM-JJ) et les montants français (1 234,56 €) sont normalisés.

L’export distingue les erreurs déterministes (mauvais NACPRO, identifiants invalides, schéma XLSX changé, HTTP 403 résiduel…) — qui échouent immédiatement car réessayer ne sert à rien — des erreurs transitoires (OOM Chromium, aléa réseau, hoquet du portail) — pour lesquelles un seul réessai est tenté (2 tentatives au total).

Procédure tRPC matchParticipantRows. L’appariement nom-à-nom est piégeux : prénoms/noms inversés, accents, traits d’union, noms composés, noms de naissance. Plutôt qu’une heuristique fragile, l’app délègue à un worker Claude Code :

  • Les candidats (transactions HubSpot) sont découpés en lots de 50 (MATCH_CHUNK_SIZE), chaque lot voyant toutes les lignes XLSX (l’appariement reste correct).
  • Jusqu’à 4 lots tournent en parallèle (MATCH_CONCURRENCY).
  • Chaque lot a un délai maximal de 180 s (DEFAULT_TIMEOUT_MS) — suffisant pour de grosses sessions (~400 participants).
  • Le worker renvoie, par candidat, le meilleur rowIndex (ou null), un score de confiance, le champ d’appariement et une justification ; la réponse est extraite du flux SSE puis validée par Zod.

Procédure tRPC syncFifplParticipantNames. Le cœur de la robustesse :

  1. Pré-filtrage : buildFifplSyncUpdates ignore les transactions dont l’état HubSpot possède déjà toutes les valeurs que la ligne pousserait (via le garde fifplDealUpToDate).
  2. Lecture de l’état courant : lecture par lot des contacts et deals HubSpot, 100 par lot (BATCH_LIMIT, limite de l’API HubSpot).
  3. Diff par champ : on ne calcule que les propriétés qui diffèrent (si un même contact/deal apparaît dans plusieurs mises à jour, les diffs sont fusionnés — la dernière écriture gagne par propriété, chaque id n’apparaît qu’une fois).
  4. Écriture par lot : mise à jour uniquement des ids au diff non vide, 100 par lot. Un lot en échec est enregistré (avec ses ids et le message HubSpot) mais n’interrompt pas les autres — pas de rollback. L’UI montre quels deals/contacts ont échoué et pourquoi.

Champs écrits — contacts : fifpl_first_name, fifpl_last_name, maiden_name, date_of_birth ; deals : fifpl_firstname, fifpl_lastname, amount.

État machine confirmingFifplSync. La descente HubSpot → internal-api est asynchrone (pilotée par un webhook). L’app interroge donc l’internal-api en boucle (re-fetch des sessions par crmId) jusqu’à ce que les noms attendus apparaissent côté session (prédicat nameFieldsMatch).

En cas de timeout, la machine passe en état d’attente offrant à l’opérateur de réessayer ou ignorer ; les dossiers non confirmés sont listés. Aucune étape suivante ne démarre tant que la propagation n’est pas confirmée.

Après la synchro des noms, si toutes les sessions financées requièrent le découpage CV-12H, l’app récupère la classe virtuelle du dossier et appelle splitFIFPLVirtualClassModules : une classe virtuelle de 12 h est découpée en 4 demi-journées (exigence FIFPL), via la logique partagée buildFIFPLVirtualClass12HSplitModules.

  • Contrôleurs tRPC : librairies/trpc/src/controller/fifpl/index.ts (exportParticipants, matchParticipantRows), fifpl/fifpl-client.ts (Puppeteer + parsing XLSX, alias d’en-têtes, classification d’erreurs), fifpl/worker-client.ts (worker Claude, SSE, timeout 180 s).
  • Synchro HubSpot : librairies/trpc/src/controller/billing/index.ts (syncFifplParticipantNames, BATCH_LIMIT = 100, lecture/diff/écriture par lot).
  • Appariement / idempotence : applications/billing/src/machines/fifpl-row-matcher.ts (buildFifplSyncUpdates, applyFifplMatches, fifplDealUpToDate, nameFieldsMatch) ; logique de noms partagée librairies/business-logic-shared/src/fifpl/name-matcher.ts.
  • Constantes clés : MATCH_CHUNK_SIZE = 50, MATCH_CONCURRENCY = 4, DEFAULT_TIMEOUT_MS = 180_000, BATCH_LIMIT = 100, export FIFPL = max 2 tentatives.
  • Découpage CV-12H : librairies/trpc/src/controller/virtual-class/index.ts (splitFIFPLVirtualClassModules).
  • Propriétés HubSpot : voir librairies/business-logic-shared/src/hubspot/index.ts.