Aller au contenu

Module Projection IA

Objectif

Projection IA permet a une societe de generer des visuels assistes par IA a partir d'une ou deux images. Le module couvre :

  • generation d'une image modifiee ;
  • fusion d'une image principale avec une image de reference ;
  • controle d'acces via la feature FEATURE_PROJECTION_IA et les permissions projection ia / projection porte entrée ;
  • debit de credits image avant generation ;
  • remboursement automatique du debit si la generation echoue ;
  • historique temporaire des generations ;
  • attribution de credits par renouvellement d'abonnement ;
  • administration des packs, des credits par plan et des credits manuels ;
  • notifications email lors des creditations.

Le module remplace progressivement l'ancien vocabulaire projection porte entrée / door_projection. Des alias existent encore dans le code et dans les migrations.

Carte technique

flowchart TD
    Router["app/core/Router.php"] --> Controller["AiProjectionController"]
    Controller --> Service["AiProjectionService"]
    Controller --> Credits["AiProjectionCreditModel"]
    Controller --> Logs["AiProjectionLogModel"]
    Service --> OpenAI["OpenAIClient"]
    Credits --> Notifications["AiProjectionCreditNotificationService"]
    Notifications --> Mailer["MailerService"]
    Logs --> Uploads["public/uploads/ai_projection/history/<societe_id>/"]

    View["app/views/projection-ia.php"] --> Js["public/assets/js/projection-ia.js"]
    Js --> Controller

    CronCredits["ai_projection_subscription_credits_cron.php"] --> Credits
    CronCleanup["ai_projection_history_cleanup_cron.php"] --> Logs
    CronNotifications["ai_projection_credit_notifications_cron.php"] --> Notifications

Fichiers a connaitre

Role Fichiers
Routage app/core/Router.php
Controleur app/controllers/AiProjectionController.php
Service IA app/services/ai_projection/AiProjectionService.php
Client OpenAI app/services/catalog/OpenAIClient.php
Credits app/models/AiProjectionCreditModel.php
Historique app/models/AiProjectionLogModel.php
Notifications credits app/services/AiProjectionCreditNotificationService.php
Vue app/views/projection-ia.php
Frontend public/assets/js/projection-ia.js
SQL app/bd/projection-ia.sql
Crons app/cron/ai_projection_subscription_credits_cron.php, app/cron/ai_projection_history_cleanup_cron.php, app/cron/ai_projection_credit_notifications_cron.php

Routes

Toutes les routes verifiees passent par :

  • Auth::check() ;
  • requireFeatureAccess(FEATURE_PROJECTION_IA, APP_DIR . '/dashboard') ;
  • puis AiProjectionController.
Route Methode Controleur Usage
projection-ia GET index() Page complete.
projection-ia/generate POST generate() Generation IA avec debit credit.
projection-ia/logs GET logs() Logs admin.
projection-ia/settings GET settings() Parametres admin : credits par plan, packs, logs.
projection-ia/plan-credits POST savePlanCredits() Enregistrer les credits par renouvellement de plan.
projection-ia/credit-packs GET/POST manageCreditPacks() Lister les packs actifs, creer/modifier/supprimer cote admin.
projection-ia/companies/search GET searchCompanies() Recherche de societes pour credit admin.
projection-ia/credit-company POST creditCompany() Credit manuel d'une ou plusieurs societes.
projection-ia/history GET history() Historique visible par la societe courante.
projection-ia/history-entry GET historyEntry() Detail d'une generation.
projection-ia/history/delete POST deleteHistory() Suppression d'une entree d'historique.
projection-porte-entree GET redirection Redirige vers projection-ia?mode=image_merge.
projection-porte-entree/generate POST generate() Alias legacy, force mode=image_merge si absent.
projection-porte-entree/logs GET logs() Alias legacy.

Point de vigilance verifie : le bloc de routes Projection IA est duplique deux fois de suite dans Router.php. Comme il s'agit d'une chaine elseif, le second bloc est normalement inatteignable. Toute modification de route doit tenir compte de cette duplication pour eviter une correction appliquee a un seul des deux blocs.

Acces et permissions

Le controleur refait un controle applicatif via enforceProjectionAccess() :

can('projection ia', 'view_all') || can('projection porte entrée', 'view_all')

Pour une requete AJAX, l'echec retourne un JSON success=false. Pour une page, l'utilisateur est redirige vers le dashboard.

Les actions admin appellent ensuite requireAdmin() :

  • settings() ;
  • savePlanCredits() ;
  • manageCreditPacks() en POST ;
  • searchCompanies() ;
  • creditCompany() ;
  • logs().

manageCreditPacks() en GET reste accessible aux utilisateurs autorises pour afficher les packs actifs.

Modes de generation

AiProjectionService expose deux modes.

Mode Constante Images requises Usage
single_edit MODE_SINGLE_EDIT primary_image Modifier une seule image selon des instructions.
image_merge MODE_IMAGE_MERGE primary_image + secondary_image Appliquer un detail, style ou materiau de la seconde image sur la premiere.

Compatibilite legacy :

  • door_swap est normalise vers image_merge ;
  • les anciens champs upload facade_image et door_image sont encore acceptes comme fallback pour primary_image et secondary_image.

Configuration OpenAI et images

Variables lues :

Variable Defaut Usage
OPENAI_API_KEY aucun Obligatoire pour generer.
OPENAI_BASE_URL https://api.openai.com/v1 Base API du client OpenAI.
OPENAI_AI_PROJECTION_MODEL aucun Modele prioritaire pour Projection IA.
OPENAI_DOOR_PROJECTION_MODEL aucun Fallback legacy si le modele Projection IA est absent.
AI_PROJECTION_MAX_IMAGE_SIZE_MB 10 Taille upload max, borne basse a 1 Mo.
DOOR_PROJECTION_MAX_IMAGE_SIZE_MB 10 Fallback legacy.
AI_PROJECTION_MAX_IMAGE_DIMENSION 1536 Dimension max avant compression, borne basse a 512 px.
DOOR_PROJECTION_MAX_IMAGE_DIMENSION 1536 Fallback legacy.
OPENAI_DOCUMENT_EXTRACTION_TIMEOUT 180 Timeout aussi utilise par OpenAIClient.

Formats acceptes par AiProjectionService :

  • image/jpeg ;
  • image/png ;
  • image/webp.

Avant l'appel API, le service peut redimensionner l'image avec GD :

  • si imagecreatefromstring() existe ;
  • si l'image depasse AI_PROJECTION_MAX_IMAGE_DIMENSION ;
  • sortie temporaire en JPEG qualite 85 ;
  • nettoyage des fichiers temporaires en finally.

Si GD n'est pas disponible ou si le redimensionnement echoue, l'image originale est transmise au client OpenAI.

Flux : generation

POST /projection-ia/generate suit cet ordre :

  1. Verifier l'acces Projection IA.
  2. Normaliser mode.
  3. Construire les fichiers uploades :
  4. primary_image ou fallback facade_image ;
  5. secondary_image ou fallback door_image.
  6. Debiter la societe via AiProjectionCreditModel::debitCurrentSocieteForGeneration($mode).
  7. Si le debit echoue, retourner success=false avec le solde et cost_per_generation.
  8. Appeler AiProjectionService::generate().
  9. Enregistrer l'historique via AiProjectionLogModel::recordGeneration().
  10. Retourner le resultat IA et le nouveau solde.
  11. Si une exception survient apres le debit, appeler refundGenerationDebit() avec l'id d'historique de debit.

Le cout d'une generation est fixe dans AiProjectionCreditModel::GENERATION_COST = 1.

Les admins ont un bypass credit :

  • getCurrentSocieteCreditSummary() retourne credit_bypass=true ;
  • debitCurrentSocieteForGeneration() ne cree pas de ligne de debit et ne diminue pas le solde.

Credits image

Solde societe

app/bd/projection-ia.sql ajoute sur societes :

  • ai_projection_credit_balance INT UNSIGNED NOT NULL DEFAULT 0 ;
  • ai_projection_credit_last_awarded_period_end DATETIME NULL.

AiProjectionCreditModel utilise SELECT ... FOR UPDATE dans lockSocieteCreditState() pour eviter les debits/credits concurrents sur une meme societe.

Historique credits

Table ai_projection_credit_history :

  • operation_type : debit, credit, refund, etc. ;
  • amount ;
  • balance_before ;
  • balance_after ;
  • source_type ;
  • source_id ;
  • justification.

Sources verifiees :

  • generation : debit d'une generation ;
  • generation_refund : remboursement automatique apres echec ;
  • subscription_bonus : attribution liee au renouvellement d'abonnement ;
  • admin_adjustment : credit manuel admin.

Credits par abonnement

Table ai_projection_plan_credits :

  • plan_id ;
  • credits_per_renewal ;
  • active ;
  • updated_by ;
  • updated_at.

getSocietesEligibleForSubscriptionRenewalCredits() selectionne les societes :

  • role artisan ;
  • current_period_end non nul ;
  • plan actif dans ai_projection_plan_credits ;
  • credits_per_renewal > 0 ;
  • pas encore creditees pour cette periode (current_period_end > ai_projection_credit_last_awarded_period_end ou champ nul).

creditSubscriptionRenewal() met a jour en transaction :

  • le solde ;
  • ai_projection_credit_last_awarded_period_end ;
  • l'historique ;
  • puis declenche une notification de credit.

Credits admin

creditCompany() permet :

  • credit d'une selection de societes ;
  • credit de toutes les societes si all_societies est present ;
  • compatibilite legacy avec societe_id.

Regles verifiees :

  • montant strictement positif ;
  • motif obligatoire ;
  • notification envoyee apres commit.

Packs

Table ai_projection_credit_packs :

  • name ;
  • price ;
  • credit_amount ;
  • bonus ;
  • price_id ;
  • active ;
  • sort_order.

Le modele permet de lister les packs actifs pour l'interface, et de creer/modifier/supprimer les packs cote admin.

Historique des generations

Table ai_projection_usage_logs :

  • societe_id, membre_id ;
  • mode ;
  • response_time_ms ;
  • tokens_consumed ;
  • instructions ;
  • primary_image_path ;
  • secondary_image_path ;
  • result_image_path ;
  • result_mime_type ;
  • expires_at ;
  • created_at.

Retention :

  • AiProjectionLogModel::HISTORY_RETENTION_DAYS = 7 ;
  • chaque generation expire a created_at + 7 jours ;
  • les lectures d'historique declenchent purgeExpiredHistorySilently() ;
  • le cron de purge appelle purgeExpiredHistory(2000).

Stockage fichiers :

  • chemin relatif : ai_projection/history/<societe_id>/ ;
  • chemin public : public/uploads/ai_projection/history/<societe_id>/ ;
  • images sources copiees avec prefixe source_primary_ et source_secondary_ ;
  • resultat stocke avec prefixe result_.

deleteHistoryEntry() doit supprimer la ligne et les fichiers associes. purgeExpiredHistory() supprime les fichiers des lignes expirees puis les lignes.

Acces historique :

  • une societe voit ses lignes par societe_id ;
  • un admin peut voir les lignes de sa societe active ou celles rattachees a son membre_id selon le contexte ;
  • seules les lignes non expirees avec result_image_path non vide sont renvoyees.

Notifications de credit

AiProjectionCreditNotificationService cree sa table de stockage a l'instanciation via ensureStorageSchema() :

ai_projection_credit_notifications :

  • societe_id ;
  • source_type ;
  • payload_json ;
  • status : pending, processing, sent, failed ;
  • retry_count ;
  • next_retry_at ;
  • last_error ;
  • sent_at ;
  • timestamps.

Comportement :

  • queueNotification() insere une ligne pending ;
  • sendNotificationById() passe la ligne en processing, recharge le payload puis envoie ;
  • si l'envoi reussit, statut sent ;
  • en cas d'echec, retry apres 300 s ;
  • abandon a partir de MAX_RETRIES = 3.

Le destinataire est resolu a partir de la societe cible. Le service cherche un email valide pour construire et envoyer un message via MailerService.

Crons

Cron Role Commande CLI
ai_projection_subscription_credits_cron.php Attribue les credits d'abonnement aux societes eligibles. php app/cron/ai_projection_subscription_credits_cron.php
ai_projection_history_cleanup_cron.php Purge l'historique expire et les fichiers associes. php app/cron/ai_projection_history_cleanup_cron.php
ai_projection_credit_notifications_cron.php Traite les notifications de credits en attente. php app/cron/ai_projection_credit_notifications_cron.php

ai_projection_subscription_credits_cron.php journalise :

  • AI_PROJECTION_CREDITS_CRON_START ;
  • AI_PROJECTION_CREDITS_CRON_SOCIETE_START ;
  • AI_PROJECTION_CREDITS_CRON_SUCCESS ;
  • AI_PROJECTION_CREDITS_CRON_ERROR ;
  • AI_PROJECTION_CREDITS_CRON_FINISH ;
  • AI_PROJECTION_CREDITS_CRON_CRITICAL_ERROR.

ai_projection_history_cleanup_cron.php journalise :

  • AI_PROJECTION_HISTORY_CLEANUP_START ;
  • AI_PROJECTION_HISTORY_CLEANUP_FINISH ;
  • AI_PROJECTION_HISTORY_CLEANUP_ERROR.

Migrations et compatibilite legacy

projection-ia.sql fait plus qu'une creation de tables :

  • cree ou met a jour la permission projection ia ;
  • migre projection porte entrée vers projection ia si necessaire ;
  • cree/met a jour la feature id 43 nommee Projection IA ;
  • lie la feature aux plans 17, 18, 19, 20 ;
  • renomme door_projection_usage_logs vers ai_projection_usage_logs si l'ancienne table existe et pas la nouvelle ;
  • convertit les anciens modes vides ou door_swap en image_merge ;
  • ajoute les colonnes manquantes de l'historique de facon idempotente ;
  • ajoute les colonnes de credits sur societes ;
  • cree les tables de credits.

Ne pas separer ces migrations sans verifier l'etat de la base cible : elles portent la compatibilite entre l'ancien module porte d'entree et le module Projection IA.

Frontend

app/views/projection-ia.php injecte notamment :

  • configuration des modes ;
  • resume credit ;
  • packs de credits ;
  • donnees pour les panneaux admin ;
  • la page currentPage = projection-ia.

public/assets/js/projection-ia.js gere :

  • selection du mode ;
  • uploads primary_image et secondary_image ;
  • appel projection-ia/generate ;
  • affichage du resultat ;
  • resume du solde et cout par generation ;
  • historique ;
  • parametres admin ;
  • packs ;
  • credit manuel de societes ;
  • tiroirs/modales et validations UI.

Points de vigilance

  1. Le debit credit se fait avant l'appel OpenAI. Garder le remboursement automatique en cas d'exception, sinon une erreur fournisseur consommera un credit.
  2. recordGeneration() retourne false sans bloquer la reponse si l'historique ne peut pas etre stocke. Une generation peut donc reussir sans apparaitre dans l'historique.
  3. Les images sources et resultats sont stockes temporairement 7 jours. Ne pas allonger la retention sans revoir volume disque, donnees personnelles et purge.
  4. Le routage Projection IA est duplique dans Router.php. Nettoyer ce doublon avant une refonte de routes reduirait le risque d'ecart.
  5. Le client OpenAI utilise aussi OPENAI_DOCUMENT_EXTRACTION_TIMEOUT. Modifier ce timeout impacte potentiellement d'autres usages du client catalogue.
  6. La table ai_projection_credit_notifications est creee par le service PHP, pas par projection-ia.sql. Sur un environnement strict, verifier que l'utilisateur SQL applicatif a le droit CREATE TABLE.
  7. Les variables legacy OPENAI_DOOR_PROJECTION_MODEL, DOOR_PROJECTION_MAX_IMAGE_SIZE_MB, DOOR_PROJECTION_MAX_IMAGE_DIMENSION sont encore lues comme fallback. Les supprimer demande une migration de configuration.

Verification conseillee

  1. Lancer php -l app/cron/ai_projection_subscription_credits_cron.php.
  2. Verifier .env : OPENAI_API_KEY, OPENAI_AI_PROJECTION_MODEL, limites image.
  3. En compte non admin avec credits, generer en single_edit puis verifier :
  4. solde decremente ;
  5. ligne ai_projection_credit_history source generation ;
  6. ligne ai_projection_usage_logs ;
  7. fichiers dans public/uploads/ai_projection/history/<societe_id>/.
  8. Simuler une erreur OpenAI apres debit et verifier le remboursement generation_refund.
  9. Tester image_merge avec deux images.
  10. Tester un solde insuffisant : pas d'appel IA attendu.
  11. En admin, charger settings, modifier credits par plan et packs.
  12. Lancer les trois crons en CLI et verifier les logs ActivityLogger.

Questions ouvertes

  • Aucun test automatise dedie au module n'a ete identifie dans les fichiers inspectes.
  • Le cycle d'achat reel des packs depend probablement d'un autre module de paiement ; ici, le modele expose les packs mais ne traite pas l'achat.
  • La creation dynamique de ai_projection_credit_notifications dans le service devrait idealement etre rapprochee des migrations SQL pour rendre les environnements plus previsibles.