---
suivi: 1802
date: 2026-10-07
sujet: Diagnostic — brainstorming, ce qu'Aktor fournit déjà
chantier: aktor
type: diagnostic
statut: poussé
hash: c35934c8
fichiers:
  - docs/suivi/SUIVI_1802_diag_brainstorming_reutilisation_aktor.md
---

# SUIVI #1802 — diagnostic brainstorming (réutilisation Aktor)

Phase 1 seulement. Aucun code. Aucune écriture en base. Aucun appel Anthropic. Aucun envoi Teams.

Fiches lues en extrait : #1793 (morceaux 5 Mo, disque local, chemins fonciers), #1027 (cadrage, borne settings), #1577 (sources FAQ / journal), #1044 / #1046 (consolidation, déclenchement web). Aucune ne décrit un fil commun ni une trame de cahier des charges.

## 1. Aktor

### a. Périmètre actuel

URI publique `/aktor`, noms de route `assistant.*` (`routes/web.php:590-627`). Groupe `m365.groups:GS_GROUPE` + permission `assistant.lecture`. Conversations, messages, clôture, « envoyer tel quel », validation de proposition, suggestions.

Admin backlog : `admin/assistant` (`:631-646`), permission `assistant.gestion`.

Fiche « proposition à cadrer » : `GET/PATCH /taches-suivi/{tache}/proposition-aktor` réservé `GS_ADMIN_ERP` (`:701-706`). Notes de consolidation : `/aktor/consolidations`, `GS_DSI` ou `GS_ADMIN_ERP` (`:708-718`).

Contrôleur chat : `app/Http/Controllers/AssistantController.php`. Proposition : `AssistantPropositionController`. Consolidation : `AssistantConsolidationController`.

Pages : `resources/js/Pages/Assistant/Index.vue`, `resources/js/Pages/Aktor/Consolidations.vue`, `ConsolidationShow.vue`, widget `resources/js/Components/Dashboard/AktorWidget.vue`, fiche `resources/js/Components/TachesSuivi/TacheSuiviPropositionAktor.vue`, sources `resources/js/Components/Assistant/AktorSources.vue`.

Tables :

| Table | Rôle | Création |
|---|---|---|
| `assistant_conversations` | un propriétaire `user_id`, statut `ouverte` / `close` | `database/migrations/2026_07_21_100200_create_assistant_conversations_table.php:15-28` |
| `assistant_messages` | `role` user/assistant, `contenu`, `tokens`, puis `sources` | `2026_07_21_100300_create_assistant_messages_table.php:15-24` |
| `assistant_message_documents` | PJ disque local | `2026_07_22_190000_create_assistant_message_documents_table.php:18-38` |
| `assistant_suggestions` | pistes FAQ | `2026_07_21_100400_create_assistant_suggestions_table.php` |
| `assistant_propositions` | fiche markdown `contenu_md`, une par tâche | `2026_07_28_190000_create_assistant_propositions_table.php:16-43` |
| `assistant_consolidations` | note backlog + `contenu_json` | `2026_07_29_120000` et `2026_08_03_120000` |
| `assistant_conversation_taches` | pivot conversation ↔ tâche | `2026_07_24_110000` |

Le fil est privé. `assertOwnsConversation` refuse si `conversation.user_id` n'est pas l'appelant (`AssistantController.php:552-555`). La liste ne charge que les conversations de cet utilisateur (`:562-564`). `GS_ADMIN_ERP` ne voit pas les fils des autres. Ce n'est pas une discussion commune.

### b. Appel Anthropic

Service chat : `app/Services/Assistant/AssistantAnthropicService.php`. Modèle `config('assistant.model')` défaut `claude-sonnet-4-5`, secours `claude-haiku-4-5` (`config/assistant.php:14-16`). `max_tokens` 8192 (`AssistantAnthropicService.php:497`). Pas de flux : `Http::post` vers `https://api.anthropic.com/v1/messages` (`:503-510`). Timeout client 120 s (`:504`). Boucle bornée à 4 itérations, et le `tool_use` sort tout de suite (`:112-151`) : le chat n'enchaîne pas d'outils.

Erreurs : clé absente ou connexion → `null` puis exception « Échec appel Anthropic » (`:139-140`, `:511-514`). Le contrôleur répond 502 « L'assistant est temporairement indisponible » (`AssistantController.php:282-293`). Quota : 25 messages utilisateur / jour / personne, 429 (`AssistantQuotaService.php:14-36`, `AssistantController.php:188-192`).

Consolidation (autre service) : `AssistantConsolidationAgentService.php`, `MAX_TOKENS_CONSOLIDATION = 16384` (`:22`), timeout 180 s (`:876`), vrais `tool_use`.

Durée PHP : `.user.ini:7` pose `max_execution_time = 300`. Aucune constante 165 dans le dépôt. Le plafond 165 s cité pour le web OVH n'est pas dans le code. Le timeout chat (120 s) passe sous 165 ; celui de la consolidation (180 s) le dépasse.

Coûts : `App\Support\AnthropicCostEstimator` (grille USD / million dans `config/services.php:45-57`, date 2026-07-17). `LogOutboundHttpCall` écrit `api_logs` (`input_tokens`, `output_tokens`, `cost_estimate_usd`) sur chaque `Http::` (`app/Listeners/LogOutboundHttpCall.php:35-46`, `app/Models/ApiLog.php:20-46`). Le message Aktor ne stocke que la somme `tokens` (`AssistantController.php:300`).

### c. Pièces jointes

Images jpeg, png, webp, gif et PDF. Max 3 fichiers, 5 Mo chacun (`AssistantMessageDocumentService.php:18-30`, `AssistantController.php:198`). Sélecteur fichier seulement (`Assistant/Index.vue:1108-1124`). Pas de glisser-déposer ni de Ctrl+V.

Envoi au modèle : base64, pas d'extraction de texte. Image → bloc `image` ; PDF → bloc `document` (`AssistantMessageDocumentService.php:117-144`). Uniquement les PJ du message courant, pas l'historique (`:91`). Secours Haiku : PDF exclus, note dans la réponse (`:119-121`, `AssistantAnthropicService.php:121`). Le prompt dit de se limiter aux ~10 premières pages (`AssistantAnthropicService.php:258`).

### d. Connaissance d'Hectarion

Injecté à chaque tour, filtré, jamais les tables métier :

- FAQ visible par le demandeur, top 5, budget ~4 000 jetons, chaque article tronqué à 4 000 caractères (`AssistantFaqRetrievalService.php:81-102` et `:308-318`, `config/assistant.php:23`).
- Journal, top 3, budget ~2 000 jetons, filtre `public_groupes` sauf droit « tout voir » (`AssistantJournalRetrievalService.php:27-52`, `config/assistant.php:26`).
- Tâches de suivi visibles par le demandeur, top 5, budget ~1 500 jetons (`AssistantTacheSuiviRetrievalService.php:41-54`, `config/assistant.php:37`). Ce bloc contient des intitulés de tâches : données de travail, pas seulement des fonctionnalités.
- Cadrage : en-tête `PROJECT.md` + sections + frontmatters `docs/suivi`, borne 12 000 caractères, réglage `settings.claude.cadrage_contexte_max_chars` (`AssistantCadrageContexteService.php:49-79` et `:103-108`).
- « Carte » : `AktorZonesCommunes::catalogue()` liste 12 fichiers chauds (consolidation seulement), pas une carte des modules (`app/Support/AktorZonesCommunes.php:16-33`).

Historique renvoyé : 40 derniers messages en texte (`AssistantAnthropicService.php:435-438`). Taille du tour = ces 40 messages + 6 blocs système (consignes, couverture, FAQ, journal, tâches, cadrage) + base64 des PJ du tour.

Risque de données : le prompt interdit la mémoire modèle et les tâches hors extrait (`:256`). Les extraits FAQ/journal sont des textes produit. Les tâches visibles et les PJ (factures, captures) partent chez Anthropic. Le bandeau le dit (`AssistantController.php:87`). Un brainstorming qui réutiliserait le retrieval tâches enverrait des sujets internes au modèle.

### e. Consignes

Prompt système du chat : heredoc dans `buildSystemBlocks` (`AssistantAnthropicService.php:211-311`). Versionné avec le code. Pas de réglage en base pour le texte. Seule la borne de cadrage est un `settings` modifiable sans déploiement (`:103-108`). Les noms de modèles passent par `.env` (`config/assistant.php:14-16`). Changer le comportement de l'agent = déploiement.

### f. Sorties structurées

Le chat ne demande pas de JSON schema à l'API. Il exige un commentaire HTML en fin de réponse :

`<!--HECTARION_PROPOSITION:{...json...}-->`

(`AssistantAnthropicService.php:299-301`). `AssistantPropositionMarkerParser::extraire` décode le JSON (`:72-80`). JSON invalide : pas d'action. Le marqueur est retiré du texte visible. La fiche d'évolution est du markdown dans le champ `fiche_cadrage`, stocké dans `assistant_propositions.contenu_md`. La consolidation, elle, valide un JSON d'outils et le range dans `contenu_json` (`AssistantConsolidationAgentService.php:525`).

Rien de tout cela n'est une trame de 16 rubriques avec auteur et message d'origine.

## 2. Briques transverses

### a. Cloche

`App\Services\AlerteService` + modèle `Alerte`. Création typique : `Alerte::updateOrCreate` sur (`entity_type`, `entity_id`, `scenario`). Exemple mention facture (`AlerteService.php:361-378`) : une alerte par commentaire et par personne, `lu_at` remis à null si on réécrit. L'auteur n'est pas notifié (`:337`). Même forme pour tâche, demande juridique, offre, registre (`:386`, `:438`, `:485`, `:526`). Pas d'API générique « notifier cet utilisateur » : chaque scénario a sa méthode.

### b. Teams

`TeamsNotificationService::notifierUtilisateur` (`app/Services/TeamsNotificationService.php:12-35`). POST JSON vers `config('services.teams.webhook_url')` = `TEAMS_WEBHOOK_URL`, timeout 10 s. Échec logué, pas d'exception remontée. Appelable depuis PHP-FPM. Un `php artisan` lancé en SSH ne joint pas `*.powerplatform.com`.

Déclencheurs déjà branchés sur une requête web : mentions des commentaires d'entité (`EntiteCommentaireService.php:103-106` puis `:111`), digest tâches via middleware (ci-dessous), alertes foncières, juridique, signalements. Les mentions de facture passent par la cloche seule (`AlerteService::notifierMentionFactureCommentaire`), pas par Teams.

Différer : oui, le modèle existe. Au prochain GET authentifié, le travail part dans `terminate()`, donc encore en FPM.

### c. Périodique sans cron CLI

`DeclencheDigestQuotidien` et `DeclencheFoncierAlertes`, enregistrés globalement (`bootstrap/app.php:35-36`).

Schéma : GET authentifié, après une heure (9 h digest, 8 h foncier), `insertOrIgnore` sur une date (`taches_digest_runs` / `foncier_alerte_runs`), puis le travail dans `terminate()` (`DeclencheFoncierAlertes.php:24-53`, `DeclencheDigestQuotidien.php:44-75`). Le digest appelle `Artisan::call('taches:digest')` dans le processus web (`:75`), pas depuis SSH. Une fois par jour pour tout l'ERP, pas à la minute, et seulement si quelqu'un ouvre une page. Réutilisable pour des relances d'inactivité, avec la même limite : rien ne part si personne ne se connecte.

### d. Salariés et groupes

`User::m365GroupNames()` lit le tableau brut `m365_groups` (`app/Models/User.php:128-130`). Le front expose le même tableau via `useAuthM365` (`resources/js/Composables/useAuthM365.js:12-16`). `hasM365Groupe` (`:19-23`) retourne vrai pour tout groupe si `is_admin_erp`, comme `User::hasM365Group()`.

`hasM365Group()` retourne vrai pour tout groupe si la personne est admin ERP (`User.php:143-149`). Pour une limite « 10 si GS_DIRECTION_GENERALE, sinon 3 », il faut `in_array` sur `m365GroupNames()`, pas `hasM365Group()`. La vue « GS_ADMIN_ERP voit tout » est un scope à part, pas un effet de cette méthode.

### e. Dépôt par morceaux

`FoncierDocumentMorceauService` (`app/Services/Foncier/FoncierDocumentMorceauService.php:24-26`). Morceau 5 Mo (`FoncierEntityDocumentService.php:64-68`, `config('foncier.documents.morceau_octets')`). Dossiers `storage/app/foncier-morceaux` et `foncier-prepares` (`:398-405`). Contrôleur et enregistrement liés aux documents fonciers. Réutilisable comme modèle, pas tel quel hors foncier. Aktor, lui, reste en un seul POST de 5 Mo.

### f. PDF

`barryvdh/laravel-dompdf`, façade `Barryvdh\DomPDF\Facade\Pdf`. Exemple : `ScenarioFaisabilitePdfExportService::chargerPdf` fait `Pdf::loadView(...)->setPaper(...)` (`app/Services/Foncier/ScenarioFaisabilitePdfExportService.php:94-98`). Même mécanisme sur la grille de prix, les avis d'imposition, l'annuaire. Pas de générateur markdown → PDF générique : il faut une vue Blade.

### g. Mentions et fils

Composant de saisie partagé côté jeton : `resources/js/utils/factureCommentMentions.js`, utilisé par `FactureDiscussionFil.vue` et `TacheSuiviCommentaireInput.vue`. Affichage cloche : `AlerteTexteAvecMentions.vue`.

Fil générique multi-entités : `resources/js/Components/Commentaires/EntiteCommentairesPanel.vue` (dossier foncier, offre, demande juridique, registre). Service `EntiteCommentaireService::ajouter` : mention, cloche, Teams (`:85-106`). Chaque entité a son contexte d'accès. Pas de fil « salle » sans entité métier. Aktor n'a pas de @.

## 3. Tableau

| Brique | État | Fichier | Réemploi |
|---|---|---|---|
| Fil de discussion | partiel | `AssistantController.php:552` | privé, un auteur ; salle commune à créer |
| Appel Anthropic synchrone | existe | `AssistantAnthropicService.php:486-510` | à copier (timeout 120, pas de flux), prompt neuf |
| Prompt modifiable sans déploiement | absent | `AssistantAnthropicService.php:211` | le texte est dans le PHP |
| Connaissance produit (FAQ, journal, suivi) | existe | `AssistantFaqRetrievalService.php:81`, `AssistantCadrageContexteService.php:49` | à réutiliser en lecture, sans le bloc tâches |
| Carte des modules | partiel | `AktorZonesCommunes.php:16` | 12 fichiers, pas une carte fonctionnelle |
| PJ image / PDF base64 | existe | `AssistantMessageDocumentService.php:117-144` | plafond 5 Mo, 3 fichiers ; pas de collage |
| Sortie structurée validée | partiel | `AssistantPropositionMarkerParser.php:72` | marqueur libre, pas une trame de 16 rubriques |
| Cloche dédoublonnée | existe | `AlerteService.php:363` | `updateOrCreate` ; nouvelle méthode de scénario |
| Teams depuis le web | existe | `TeamsNotificationService.php:12` | appel direct dans la requête qui mentionne |
| Relance sans cron SSH | existe | `DeclencheFoncierAlertes.php:24-53` | même verrou quotidien ; pas à l'heure pile |
| Groupes M365 | existe | `User.php:128` | compter sur le tableau brut |
| Morceaux 100 Mo | partiel | `FoncierDocumentMorceauService.php:398` | chemins et métier foncier ; à extraire si > 5 Mo |
| PDF | existe | `ScenarioFaisabilitePdfExportService.php:96` | `Pdf::loadView` + vue du cahier |
| @ et fil | partiel | `EntiteCommentairesPanel.vue`, `factureCommentMentions.js` | à brancher sur la salle, pas sur une facture |

## Risques

Délai. Le chat bloque le worker le temps du POST (120 s). Une synthèse de 16 rubriques sur un long fil peut approcher cette borne. Il n'y a pas de réponse en flux : l'écran attend. Copier le timeout 180 s de la consolidation dépasse le plafond web 165 s indiqué pour OVH, même si `.user.ini` affiche 300.

Coût. Chaque tour renvoie jusqu'à 40 messages plus les blocs système plus le base64 des images. Une salle à plusieurs voix multiplie les jetons. Le quota Aktor (25 / jour) est celui du helpdesk : le brancher tel quel empêcherait d'utiliser Aktor, ou l'inverse.

Contexte. Le cadrage est borné (12 000 caractères). Le journal et la FAQ aussi. Une « carte des fonctionnalités » complète ne tient pas dans un tour. Les tâches de suivi et les PJ sont des données : les laisser dans le prompt du brainstorming contredit « fonctionnalités seulement ».

Injection. Le message et le document sont dans `messages`, au même niveau que des instructions (`AssistantAnthropicService.php:452-470`). Un participant ou un PDF peut ordonner d'ignorer les consignes ou de remplir des rubriques. Aujourd'hui le seul effet structuré est un marqueur parsé, et la tâche n'est créée qu'après validation humaine. Un cahier des charges écrit directement depuis le JSON du modèle recopierait l'injection. Le serveur doit valider le schéma, attribuer chaque élément à l'auteur du message d'origine, et ne donner à l'agent aucun outil de lecture des tables métier.

## Architecture (non codée)

1. Tables nouvelles : salle (créateur, état, début, fin, délai d'inactivité), participants, messages (auteur, texte), pièces, rubriques (16 clés, statut remplie / sans objet / reportée), éléments (rubrique, auteur, message d'origine).
2. Ne pas réutiliser `assistant_conversations` : le fil y est privé et mono-utilisateur.
3. Appel HTTP calqué sur `AssistantAnthropicService::callMessagesApi` (Sonnet, 120 s, pas de flux), prompt de facilitation versionné à part, quota distinct des 25 messages Aktor.
4. Connaissance : FAQ + journal + frontmatters de suivi, jamais tâches ni tables métier.
5. Mentions : jetons de `factureCommentMentions.js`, cloche par `updateOrCreate`, Teams par `notifierUtilisateur` dans la requête web.
6. Relances : middleware `terminate()` sur GET authentifié, verrou par jour comme le foncier, scan des salles dont le dernier message dépasse le délai.
7. Pièces : le chemin 5 Mo base64 d'Aktor pour images et PDF ; les morceaux fonciers seulement si le plafond doit monter, après extraction du préfixe `foncier-`.
8. Cahier : markdown en base, PDF par `Pdf::loadView`.
9. Plafonds 3 et 10 : compte SQL du créateur, test `m365GroupNames()` brut ; `GS_ADMIN_ERP` = scope de lecture, pas `hasM365Group()`.
10. Lots : salle et droits, puis agent et trame, puis mentions, puis relance, puis PDF.

## DÉPLOIEMENT-TEST

Fiche seule. `git pull origin main` suffit. Pas de migration, pas de `journal:sync`.

## LEÇON

Aktor est un helpdesk privé : un propriétaire, un prompt figé, une réponse bloquante, des extraits produit plus les tâches visibles du demandeur. Une discussion commune avec cahier des charges réutilise l'appel HTTP, la cloche, Teams, le middleware quotidien et Dompdf. Elle ne réutilise ni la table des conversations, ni le prompt, ni le quota, ni le bloc « tâches de suivi ».
