# Scan ICP — décision partenaires pour WTX Europe

Application web de repérage et de qualification de partenaires industriels : constitution
d'un univers d'entreprises (API publique française avec complément Pappers plafonné), qualification
rapide, sélection des candidatures prometteuses puis enquête web ciblée (sites, presse et
**PDF publics**). Les preuves alimentent une recommandation explicable et une décision humaine,
avant la recherche des interlocuteurs pertinents et le suivi commercial.

Doctrine embarquée dans le code :

- **Qualité, pas volume** : 15-50 leads très qualifiés par vertical, garde-fou d'import à 3 000.
- **Le réel avant l'affiché** : un signal RSE ne vaut que s'il correspond à un fait daté et
  sourcé ; chaque fait affiché est cliquable vers sa source.
- **L'absence de données n'est jamais un zéro** : critère sans preuve → `donnees_insuffisantes`,
  hors dénominateur du score.
- **Trois axes, jamais un score opaque** : adéquation stratégique, solidité des preuves et
  opportunité actuelle sont affichées séparément.
- **Contradiction obligatoire** : chaque fiche confronte la thèse commerciale à sa meilleure
  contre-analyse et transforme les inconnues en questions de qualification.
- **L'humain décide** : `validee`/`ecartee` n'existent que par action humaine avec raison
  obligatoire, journalisée (boucle d'apprentissage de la grille).
- **Mesurer avant d'apprendre** : jeu aveugle, cas de calibration, résultats commerciaux et
  propositions de grille restent distincts ; aucune modification automatique de la grille.
- **Les traitements lourds restent hors des requêtes web** : import, Pappers, crawl et analyses
  Claude vivent dans les workers CLI. Seules la traduction NAF et l’estimation courte du volume
  peuvent appeler leurs services depuis la requête web sur un hébergement mutualisé.

## Diversification et parcours commercial

WTX fabrique historiquement des tubes pour l’automobile. Le profil industriel décrit ces
capacités permanentes ; chaque **terrain** décrit une hypothèse de marché, une offre à
explorer et ses critères. Le mobilier est un premier paramétrage. Un terrain médical,
un équipement de sport ou un autre marché se crée depuis l’interface sans ajouter de branche
sectorielle dans le moteur.

1. **Cadrer le terrain** : offre, activités ciblées, exclusions, recherches prioritaires et
   fonctions à contacter. Le profil industriel WTX reste commun aux terrains.
2. **Constituer les candidats** : chercher et importer les entreprises. Un SIREN correspond à
   une identité dans `societes` ; chaque ligne `entreprises` est sa candidature dans un terrain.
   La même société peut donc avoir plusieurs qualifications, décisions et contacts distincts.
3. **Qualifier rapidement** : confirmer l’identité puis chercher les premiers éléments des
   questions essentielles. Résultat : `prometteuse`, `a_clarifier` ou `hors_cible`. Une donnée
   absente reste inconnue ; une exclusion garde son motif propre au terrain.
4. **Approfondir la sélection** : choisir les candidatures prometteuses et confirmer le
   périmètre et le budget du lot. La qualification rapide ne déclenche pas automatiquement
   l’enquête complète. Les preuves détaillées, les limites et la contre-analyse restent consultables.
5. **Identifier qui contacter** : sur une candidature suffisamment qualifiée, rechercher les
   fonctions configurées (achats, sourcing, produit, industrialisation…). Le dirigeant n’est
   pas choisi par défaut. Une personne proposée doit avoir une fonction, une raison de la
   contacter, une source, une date de vérification et un canal professionnel public disponible.
6. **Décider et suivre** : conserver la décision humaine, la prochaine action, son échéance et
   son résultat. Rechercher un contact ou enregistrer une introduction n’envoie aucun message.

Dans **Terrain**, éditer l’offre et les consignes de recherche ainsi que les rôles de contacts.
Dans **Grille**, chaque critère possède sa question et ses signaux ; les drapeaux
`parametres.essentiel` et `parametres.qualification_rapide` pilotent les premières vérifications.
Les cartes métier de la fiche et du tableau de bord viennent des critères essentiels de la
grille. Une modification de la grille, de l’offre ou du profil industriel invalide une
qualification rapide antérieure pour les prochains passages de l’entonnoir. L’historique
reste consultable.

## Stack

PHP 8.2+ vanilla (zéro dépendance, zéro Composer), MySQL 8 (utf8mb4, PDO préparé partout),
HTML/CSS/JS vanilla, workers PHP CLI lancés par cron, IA via l'API Anthropic (cURL natif).

## Installation

### Prérequis

- PHP ≥ 8.2 avec extensions `pdo_mysql`, `curl`, `mbstring` (standard sur tout LAMP)
- MySQL 8 (ou MariaDB ≥ 10.6)
- Un cron
- Poppler (`pdftotext`) pour extraire les PDF publics trouvés pendant l'enquête

### 1. Base de données

```sql
CREATE DATABASE scan_icp CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'scan_icp'@'localhost' IDENTIFIED BY 'votre_mot_de_passe';
GRANT ALL PRIVILEGES ON scan_icp.* TO 'scan_icp'@'localhost';
```

### 2. Configuration

```bash
cp .env.example .env      # puis renseigner chaque valeur
```

Le `.env` vit à la racine du projet, **hors webroot**. Clés à obtenir :
`PAPPERS_API_KEY` (pappers.fr), `ANTHROPIC_API_KEY` (console.anthropic.com),
`SEARCH_API_KEY` (tavily.com ou brave.com/search/api — choix par `SEARCH_DRIVER`).
`ANTHROPIC_NAF_MODEL` utilise par défaut `claude-haiku-4-5-20251001`, modèle rapide réservé
à la traduction d’une intention métier en codes NAF. `ANTHROPIC_MODEL` reste indépendant et
continue d’alimenter les analyses longues.
`NAF_INTERPRETER_MODE=inline` est le réglage recommandé sur un hébergement mutualisé sans
worker permanent, tout comme `UNIVERS_ESTIMATION_MODE=inline`. Utilisez `queue` lorsqu'un
worker supervisé tourne en continu.
Configurez également l’envoi des invitations et des liens « mot de passe oublié ». Avec le
serveur de courrier local de l’hébergement, utilisez `MAIL_DRIVER=mail`, une adresse réelle dans
`MAIL_FROM` et, si nécessaire, la même adresse dans `MAIL_RETURN_PATH`. L’adresse doit appartenir
à un domaine que l’hébergeur autorise comme expéditeur. Les variables `SMTP_*` ne servent qu’avec
`MAIL_DRIVER=smtp`. Les liens utilisent la base publique définie par `APP_URL`.

### 3. Migrations + admin

```bash
php migrations/migrate.php
```

Idempotent (rejouable sans risque). Au premier passage, il crée l'admin
`admin@scanicp.local` et **affiche une seule fois un mot de passe temporaire** ;
le changement de mot de passe est imposé au premier login.

Lors d’une mise à jour d’une installation existante, l’ordre est impératif : arrêtez ou
laissez finir les anciens workers, exécutez les migrations, déployez le nouveau code, puis
redémarrez les workers. La migration `022_lots_analyse.sql` ajoute les colonnes utilisées par
le nouveau dispatcher ; elle doit donc précéder son premier démarrage. La migration `023`
rend les pauses fournisseur après HTTP 429 persistantes. Les migrations `024` à `030`
versionnent les fiches et les faits, garantissent leur fraîcheur et réconcilient les données
historiques avec la sémantique « point non établi », sans recherche manuelle à déléguer.
Les migrations de diversification doivent également précéder le redémarrage :

- `036_terrains_configurables.sql` : profil industriel, consignes et rôles par terrain,
  critères essentiels génériques ; le préréglage Frame existant publie une nouvelle version.
- `037_societes_et_qualification.sql` : identité partagée, unicité de candidature par
  SIREN et terrain, qualification rapide et autorisation distincte d’approfondissement.
- `038_contacts_cibles.sql` : recherches d’interlocuteurs, personnes, fonctions et preuves
  professionnelles rattachées à la candidature.
- `039_modele_revendeurs.sql` : actualise les seuls textes historiques du préréglage
  mobilier pour inclure les revendeurs multimarques et acheteurs de meubles finis. Une marque
  propre ou la sous-traitance actuelle ne sont plus des conditions obligatoires. Les offres
  personnalisées et versions historiques restent conservées.

Le migrateur et les workers partagent une garde MySQL : une migration est refusée tant
qu’un worker tient encore un slot, et un nouveau worker attend la fin du déploiement.

### 4. Vhost

Seul `/public` est exposé. Apache :

```apache
<VirtualHost *:80>
    ServerName scan-icp.example.com
    DocumentRoot /var/www/scan-icp/public
    <Directory /var/www/scan-icp/public>
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>
```

(Le `.htaccess` fourni route tout vers `index.php`.) Nginx : `try_files $uri /index.php?$query_string;`.

En développement : `php -S localhost:8080 -t public public/index.php`.

### 5. Cron des workers

```cron
* * * * * php /var/www/scan-icp/app/workers/worker.php --max-runtime=55 >> /dev/null 2>&1
```

Chaque exécution traite la file pendant 55 s puis rend la main. `WORKER_CRON_PROCESSES`
permet au cron de lancer plusieurs vrais processus en parallèle sans dupliquer la tâche
planifiée. `WORKER_MAX_PROCESSES` (3 par défaut) borne réellement leur nombre sur toutes
les machines qui partagent MySQL. Un lease MySQL propre à chaque job empêche qu'un
traitement encore vivant soit repris par un second worker.

Avec `NAF_INTERPRETER_MODE=queue`, lancez un worker supervisé en continu pour que
l’interprétation NAF réponde sans attendre le prochain cron :

```bash
php /var/www/scan-icp/app/workers/worker.php --max-runtime=0
```

Les jobs interactifs ont une priorité supérieure aux imports et analyses de fond. Dans un lot,
les étapes déjà avancées sont prioritaires, avec un vieillissement qui évite d'affamer les
nouveaux dossiers. Un HTTP 429 refroidit temporairement tous les jobs du même fournisseur.

### Lots d'analyse

L'étape `/pipeline` ne crée jamais directement des centaines de jobs. Elle réalise d'abord un
préflight persistant qui fige les identifiants, la version de grille, l'estimation de coût et de
durée, puis exige un seuil financier explicite. Après confirmation :

- une fenêtre de `ANALYSE_TAILLE_PAQUET` entreprises seulement est matérialisée ;
- `ANALYSE_CONCURRENCE_MAX` chaînes du lot peuvent avancer simultanément ;
- chaque appel à venir réserve une estimation avant de partir ;
- le lot passe automatiquement en pause avant le job suivant si son seuil est atteint ;
- pause, reprise et annulation restent disponibles sans perdre les résultats déjà écrits ;
- les analyses en erreur repassent par le même préflight sous forme d'un lot de réparation.

Le seuil est un garde-fou avant démarrage, pas une annulation d'appel réseau déjà engagé : un
appel en vol peut donc finir et être facturé. Les budgets mensuels global (`*`) et terrain sont
contrôlés ensemble à la confirmation ; la réservation d'un lot annulé reste engagée jusqu'au
retour de ses derniers appels.

### Pilotage des coûts

Le cockpit administrateur est disponible sur `/administration/couts`. Il distingue :

- la **profondeur demandée** (`recalcul local`, `re-notation`, `analyse complète`) ;
- l'**étape réellement facturée** (univers, identité, collecte, extraction, notation, synthèse) ;
- le coût incrémental d'un palier et le coût cumulé moyen pour atteindre un résultat ;
- le terrain, le lot, le run, le modèle, les tokens et la qualité d'attribution de chaque appel.

Les appels historiques antérieurs à la migration 021 restent inclus dans les totaux mais sont
signalés comme partiellement reconstitués. Les nouvelles exécutions, y compris les recalculs à
0 €, sont suivies dans `analyses_runs`. Les budgets mensuels se règlent directement dans le
cockpit. Après chaque déploiement, exécutez `php migrations/migrate.php` avant de relancer les
workers.

### Préréglage Frame historique : meuble complet (migration 035)

Avant la migration 039, ce préréglage historique visait la fabrication de **meubles complets pour une marque propre**, pas la
seule fourniture de tubes ou de piètements. La migration `035_meuble_complet.sql`
publie une nouvelle version de Frame, conserve les personnalisations des autres
critères et les résultats historiques, et rend le timing nullable. Elle ne lance
aucune analyse. Il reste modifiable comme les autres terrains ; ce contenu ne définit pas
l’offre de WTX pour tous les marchés. Appliquer les migrations avant de remettre les workers en route.

Les règles ci-dessous décrivent cette version historique. Depuis la migration 039,
le modèle Frame accepte les revendeurs multimarques et les acheteurs de meubles finis ;
la marque propre et l’externalisation ne sont plus des conditions obligatoires.

- Établir d'abord le site : probable = cinq pages officielles au maximum, sans
  recherches externes ; ambigu/introuvable/injoignable = arrêt de l'analyse.
- Chercher gamme propre, conception et partenaires de fabrication avant les
  informations RSE. Le nombre maximal de recherches externes reste inchangé.
- Les trois filtres portent sur la gamme compatible, le rôle de donneur d'ordres
  externalisant sa fabrication et les séries documentées. Une revente multimarque
  ne suffit pas. Les notes historiques ne sont pas reprises automatiquement sous
  ce nouveau contrat : choisir la ré-extraction ou actualiser les sources.
- Sans preuves essentielles, le dossier termine **À clarifier**, sans synthèse
  commerciale automatique. Un rejet documenté reste soumis à la décision humaine.
- La confiance mesure la qualité des faits vérifiés, sans bonus au nombre de pages.
  Zéro fait vérifié = zéro confiance ; sans signal positif vérifié et daté dans les
  24 derniers mois, le timing reste inconnu. Les risques inconnus ne bloquent plus
  seuls la priorité : preuves essentielles et couverture pondérée restent requises.

Contrats sans API : `tests/furniture_target.php`, `tests/furniture_migration.php`,
`tests/decision_qualification.php`, `tests/evidence_quality.php`,
`tests/source_passages.php` et `tests/enrichment_furniture_research.php`.
Les tests SQL utilisent des tables temporaires ou des transactions annulées.

### Cas OVH mutualisé

Conservez `NAF_INTERPRETER_MODE=inline` et `UNIVERS_ESTIMATION_MODE=inline` : ces deux
actions répondent alors immédiatement sans attendre la tâche planifiée. Dans l'espace client
OVHcloud, ajoutez une tâche CRON pointant vers `app/workers/worker.php` depuis la racine du
dépôt déployé, avec la même version de PHP que le site et les rapports d'erreur activés. Le
script utilise déjà une durée maximale de 55 secondes lorsqu'aucun argument n'est fourni.

La tâche planifiée reste indispensable pour les imports et les analyses de fond. La fréquence
horaire d'un hébergement mutualisé convient aux lots asynchrones, mais pas à un traitement
interactif ; pour des imports quasi immédiats, utilisez un worker permanent sur un VPS ou un
service équivalent.

## Utilisation

1. **Univers** (admin) : décrire librement le marché ; l’IA reçoit les 732 sous-classes NAF,
   propose uniquement des codes de ce référentiel, puis l’utilisateur accepte ou refuse chaque
   activité. Les libellés affichés sont toujours rétablis depuis le fichier local officiel.
   Après confirmation : fourchette CA/effectif/zone → « Estimer » puis « Lancer l'import ».
   L’API Recherche d’entreprises constitue gratuitement l’univers ; Pappers ne complète que les
   dossiers sans CA public, dans la limite du budget autorisé dans l’écran.
2. **Analyse** : le préflight fige le périmètre, la grille, le budget et le seuil ; le lot avance
   ensuite par vagues contrôlées. Après l’identité et la qualification rapide, le traitement
   s’arrête. Les candidatures prometteuses sélectionnées passent à l’approfondissement après
   validation d’un budget distinct, puis à la décision et à la recherche d’interlocuteurs.
3. **Fiche** : adéquation, preuves et timing ; faits vérifiés avec extrait et page PDF ; thèse,
   contre-analyse, questions de qualification, prochaine action et voie d'introduction ;
   décisions humaines, suivi commercial et impression.
4. **Grille ICP** (admin) : chaque terrain possède sa description et sa grille. Préparer un
   brouillon, régler les paramètres métier des règles structurées (fourchette de CA, pays du
   siège), documenter puis publier une **nouvelle version** — jamais modifier une version
   publiée. Le déploiement peut recalculer localement sans IA ou re-noter les faits existants.
   De nouvelles questions relancent d’abord la qualification rapide ; l’approfondissement
   nécessite ensuite une sélection prometteuse et un nouveau budget confirmé.
5. **Évaluation** : sépare le jeu aveugle des cas de calibration et mesure concordance, faux
   positifs et prospects manqués.
6. **Pilotage** (admin) : crée et modifie les terrains d’exploration, lit les résultats
   commerciaux et soumet les évolutions de grille à une validation explicite. Un nouveau terrain
   démarre avec une copie indépendante de la grille Frame.
7. **Journal & coûts** : traçabilité complète, reproductibilité des analyses et coûts.
8. **Menu utilisateur** : accès au profil, au changement de mot de passe et à la déconnexion.
   Les administrateurs disposent en plus d’un espace pour créer les comptes dans une modale,
   attribuer les rôles et renvoyer les invitations. Chaque utilisateur définit lui-même son mot
   de passe depuis un lien à usage unique reçu par e-mail.

## Commandes CLI

| Commande | Rôle |
|---|---|
| `php migrations/migrate.php` | Applique les migrations + seed admin |
| `php app/workers/worker.php --max-runtime=55` | Boucle de traitement des jobs (cron) |
| `php app/workers/worker.php --max-runtime=0` | Worker continu recommandé pour l’interprétation NAF interactive |
| `php app/workers/worker.php --une-fois` | Traite un seul job (debug) |
| `php app/workers/purge_entreprise.php {siren}` | RGPD : efface tout (base + cache disque) |

## Architecture

```
public/            seul dossier exposé (front controller + assets)
app/config/        config.php (parser .env maison, constantes, profil WTX)
app/core/          Db (PDO singleton), Auth (sessions, CSRF, rate-limit),
                   Router, Http (cURL, retry, 2 Mo max), helpers (machine à états…)
app/controllers/   un contrôleur par écran — aucun appel externe ici
app/services/      Pappers, Search (tavily|brave), Crawl (robots.txt, 1 req/s),
                   PDF publics, interprétation NAF contrainte, preuves, Resolve, Claude, Scoring
app/workers/       worker.php (dispatcher) + handlers/ (import_univers, resoudre,
                   qualifier, enrichir, scorer, angle_approche, contacts) + purge_entreprise.php
migrations/        migrations numérotées, idempotentes + migrate.php
storage/cache/     contenus bruts {siren}/{candidature}-{hashcontenu}.txt — hors webroot
storage/logs/      logs applicatifs des workers
```

Les anciens chemins de cache restent lisibles pour conserver les sources historiques.
Les nouveaux fichiers séparent les candidatures et les contenus collectés.

### Machine à états

```
identifiee → resolue → enrichie → scoree → a_valider → validee
                                                     ↘ ecartee
(tout état) → erreur (re-jouable via « Relancer les erreurs »)
```

Cette machine décrit l’avancement technique. Après `resolue`, le job `qualifier` peut
terminer le lot : `qualification_rapide_statut` porte alors le résultat métier. Le passage
vers `enrichie` exige une sélection et une autorisation d’approfondissement distinctes.

Chaque transition écrit dans `evenements`. Un job d’analyse en échec après 3 tentatives
(backoff 2/4/8 min) passe l'entreprise en `erreur` sans bloquer le batch.
La recherche de contacts est limitée à une tentative par lancement autorisé. Son échec
reste attaché à cette recherche : il n’écarte pas la candidature et ne change pas sa décision.

### Recommandation (calcul en PHP, jamais par l'IA)

- Filtre dur « non » → écarté, score NULL. « Incertain » ne rejette pas : compté insuffisant.
- Éliminatoire déclenché → écarté.
- `score_pct = Σ(note×poids) / (3×Σ poids des critères notés)` — les critères sans données
  sortent du dénominateur.
- ≥ `MAX_CRIT_INSUFFISANTS` critères sans données → `donnees_insuffisantes`.
- Le score d'adéquation est combiné à la confiance des preuves et au timing pour produire
  `Prioritaire`, `À enquêter`, `À surveiller` ou `Écartement proposé`.
- Les variables historiques `SEUIL_P1` / `SEUIL_P2` servent uniquement de seuils internes de
  calcul ; ces codes ne sont plus présentés aux utilisateurs.

## Choix documentés (points d'interprétation de la spec)

- **`jobs.type` étendu avec `import_univers`** : l’UI crée un job ; le worker interroge d’abord
  l’API publique puis, sous plafond budgétaire, Pappers. L’ancien type `import_pappers` reste
  disponible pour les cas historiques et les jeux de calibration.
- **Interprétation NAF contrainte** : les 732 codes et libellés sont injectés à chaque demande ;
  tout code absent est rejeté côté serveur, les libellés sont réhydratés depuis le référentiel
  local et la sélection/refus de l’utilisateur est journalisée. Une file prioritaire garde ce
  parcours interactif devant les traitements de fond.
- **Colonnes `jobs.verrou` et `jobs.disponible_le`** ajoutées au schéma de la spec : nécessaires
  pour la « vérification d'affectation » multi-workers et le backoff exponentiel demandés en §6.
- **Table `reponses_rdv`** (migration 010) : persistance des critères « à qualifier en RDV »
  saisis par le commercial sur la fiche.
- **Table `essais_login`** (migration 009) : rate-limit 5 essais / 15 min par IP.
- **`utilisateurs.doit_changer_mdp`** : le « mot de passe imposé au premier login » est réalisé
  par un mot de passe temporaire affiché une fois par `migrate.php` + changement forcé.
- **Filtres durs structurés** (`taille_pme_eti`, `zone_europe`) : moteur PHP contrôlé, jamais de
  SQL libre ni d’IA. Leurs paramètres métier sont stockés avec chaque version et chaque terrain.
  CA ou pays absent/invalide → `donnees_insuffisantes` (pas un rejet). `zone_europe` contrôle le
  pays du siège ; le marché principal n’est pas inféré par cette règle.
- **Recalculer sans IA** reprend les notes de la version précédente et applique les paramètres et
  poids courants ; **re-noter** réutilise les faits existants. Le mode historique **ré-extraire**
  fait désormais repasser les candidatures par la qualification rapide avec les nouvelles
  questions. Une nouvelle collecte approfondie demande ensuite une sélection prometteuse
  et son budget distinct ; elle n’est jamais une conséquence gratuite de la publication.
- **AMPM** n'a pas de SIREN propre (marque de La Redoute) : la ligne du jeu de test reste
  « sans SIREN » et l'entité juridique est couverte par la ligne La Redoute.
- **Barèmes de coûts** : Pappers, recherche et Claude sont configurables en `.env`
  (`PAPPERS_COUT_*`, `SEARCH_COUT_REQUETE_EUR`, `CLAUDE*_PRIX_EUR_MTOK_*`). Le modèle et le
  tarif Claude appliqué sont snapshotés avec chaque appel afin qu'une modification future ne
  réécrive pas l'historique.

## Sécurité & RGPD

- PDO préparé partout ; `htmlspecialchars` systématique en sortie ; CSP, `X-Frame-Options`,
  `X-Content-Type-Options` ; cookies `HttpOnly; SameSite=Lax` (+ `Secure` en HTTPS) ;
  CSRF vérifié sur tous les POST ; rate-limit login.
- Interlocuteurs : identité et fonction professionnelles, justification et citations publiques
  vérifiables. Aucun contact personnel ou adresse devinée ; les canaux professionnels publiés
  restent attribués à leurs sources. **Jamais de scraping LinkedIn** (URL stockée seule).
- LinkedIn : l'outil exploite uniquement les **extraits de posts publics indexés par le
  moteur de recherche** (stockés comme sources type `linkedin`, cliquables vers le post) et
  les URLs de pages/profils. linkedin.com n'est jamais consulté directement par le crawler —
  conforme à la charte du projet et aux CGU LinkedIn. La lecture complète des posts se fait
  par le commercial, connecté, via les liens « posts ↗ » de la fiche.
- Crawl : robots.txt respecté, 1 req/s/domaine, User-Agent `ScanICP/1.0 (contact: …)`,
  timeout 15 s, 2 Mo max/page.
- Droit d'effacement : `php app/workers/purge_entreprise.php {siren}`.

## Definition of done

Lancer l'écran **Évaluation** : les cas de calibration doivent conserver leur résultat attendu,
les cas aveugles ne doivent révéler l'attendu qu'après analyse, et tout écart doit être visible.
Vérifier ensuite qu'une fiche affiche trois axes séparés, des preuves auditables, une
contre-analyse et une prochaine action exploitable.

## Vérifier le parcours de diversification

Contrôles purs ou simulés, sans clé fournisseur ni base distante :

```bash
php tests/diversification_ui.php
node tests/pipeline_qualification_ui.js
php tests/terrain_journey.php
php tests/decision_limits_ui.php
php tests/contacts.php
```

Les contrôles SQL et les rendus de routes doivent utiliser une base locale dédiée et migrée.
Les scripts `seed_diversification_ui.php` et `diversification_ui_integration.php` refusent
une base dont le nom ne commence pas par `wtx_diversification_test_` et un hôte autre que
`localhost` ou `127.0.0.1`. Le seed crée uniquement des exemples explicitement marqués QA,
aucun job, aucun appel fournisseur. Il conserve ces exemples pour une inspection visuelle.

```bash
# Définir DB_HOST, DB_NAME, DB_USER et DB_PASS vers la base locale de test.
php migrations/migrate.php
php tests/seed_diversification_ui.php
php tests/diversification_ui_integration.php
php tests/diversification_flow.php
php tests/diversification_migrations.php
php tests/repair_planning.php
php tests/generic_scoring.php
php tests/contacts_integration.php
php tests/revendeurs_migration.php
php tests/smoke_routes.php
php tests/dashboard_ui.php
php tests/pipeline_ui.php
php tests/navigation_ui.php
```

Le scénario QA partage le même SIREN entre un terrain mobilier et un terrain hospitalier,
avec des résultats différents. Il vérifie les motifs, les questions configurées, les liens
entre candidatures, les compteurs non vides, la remise en qualification d’un résultat ancien
et le blocage de la recherche de contacts tant que la candidature n’est pas prometteuse.
Les tests ne prouvent pas la qualité commerciale d’un fournisseur IA réel : une validation
métier sur des entreprises choisies par WTX reste distincte de ces contrôles techniques.

## Démonstration du 23 septembre 2026

Le [guide de démonstration](docs/demo-2026-09-23.md) présente le jeu local traité :
8 entreprises examinées dans l’entonnoir, dont 3 dossiers avec analyse approfondie
et recherche d’interlocuteurs terminées (Camif, 4 Pieds et Kreabel).
Le [lanceur macOS](scripts/demo-20260923.command) ouvre cette base dédiée ;
la [notice](docs/demo-lancement-2026-09-23.md) indique où trouver les accès locaux.
La sauvegarde SQL et les sources publiques sont conservées dans
`storage/backups/demo-20260923/sauvegarde/`, hors Git. Le jeu traité est également
publié sur [wtxeurope.cloud](https://wtxeurope.cloud/dashboard?vertical=frame) : l’écran
Décisions distingue les trois dossiers exploitables des propositions d’écartement.
