File size: 25,271 Bytes
fa1324b
 
 
59a3758
 
 
fa1324b
 
59a3758
71f4340
59a3758
 
 
 
fa1324b
c45083a
 
 
 
 
59a3758
c45083a
59a3758
 
c45083a
 
 
71f4340
 
 
 
 
 
 
 
c45083a
 
59a3758
c45083a
 
 
 
 
fa1324b
c45083a
59a3758
fa1324b
59a3758
c45083a
 
 
 
fa1324b
c45083a
 
 
59a3758
c45083a
 
 
59a3758
c45083a
 
59a3758
c45083a
 
b9712ac
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c45083a
 
fa1324b
c45083a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fa1324b
c45083a
fa1324b
 
59a3758
fa1324b
 
 
 
 
 
b9712ac
fa1324b
 
59a3758
 
 
 
 
b9712ac
 
 
 
fa1324b
 
59a3758
fa1324b
 
c45083a
 
 
 
 
 
 
 
 
 
 
 
 
59a3758
c45083a
59a3758
 
c45083a
 
59a3758
 
c45083a
 
59a3758
fa1324b
59a3758
c45083a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fa1324b
71f4340
 
 
 
 
 
 
 
 
 
 
 
 
 
59a3758
c45083a
fa1324b
 
59a3758
fa1324b
71f4340
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c45083a
71f4340
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59a3758
fa1324b
c45083a
 
fa1324b
59a3758
fa1324b
 
c45083a
59a3758
 
 
 
c45083a
59a3758
 
c45083a
fa1324b
c45083a
59a3758
c45083a
 
59a3758
 
c45083a
 
 
 
 
 
59a3758
 
 
 
 
 
c45083a
59a3758
71f4340
 
 
59a3758
 
 
 
 
 
 
 
 
c45083a
59a3758
 
c45083a
 
 
59a3758
c45083a
 
 
 
 
 
 
 
 
 
 
0e008be
c45083a
0e008be
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59a3758
 
 
 
 
c45083a
59a3758
 
 
 
 
c45083a
 
59a3758
 
c45083a
 
 
 
 
 
 
 
 
 
 
59a3758
c45083a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59a3758
c45083a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59a3758
c45083a
 
59a3758
c45083a
 
 
 
59a3758
c45083a
 
59a3758
c45083a
 
 
 
59a3758
c45083a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b9712ac
 
c45083a
b9712ac
59a3758
c45083a
 
 
59a3758
c45083a
fa1324b
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
import streamlit as st
import pandas as pd
from pocketgroq import GroqProvider
import time
import json
import uuid

# Configuration de la page
st.set_page_config(
    page_title="VisiPilot IFS Food 8",
    page_icon="🔍",
    layout="wide",
    initial_sidebar_state="expanded"
)

# Styles CSS
st.markdown("""
<style>
    .main-header {
        font-size: 28px;
        font-weight: bold;
        color: #0066cc;
        text-align: center;
        margin-bottom: 20px;
        padding: 10px 0;
        border-bottom: 2px solid #e0f7fa;
    }
    .banner {
        background-image: url('https://github.com/M00N69/BUSCAR/blob/main/logo%2002%20copie.jpg?raw=true');
        background-size: cover;
        height: 150px;
        background-position: center;
        margin-bottom: 20px;
        border-radius: 10px;
    }
    .card {
        padding: 15px;
        border-radius: 10px;
        background-color: #f9f9f9;
        margin-bottom: 15px;
        border-left: 5px solid #0066cc;
    }
    .status-badge {
        display: inline-block;
        padding: 3px 8px;
        font-size: 12px;
        font-weight: bold;
        border-radius: 10px;
        margin-left: 10px;
    }
    .status-completed {
        background-color: #28a745;
        color: white;
    }
    .status-progress {
        background-color: #ffc107;
        color: black;
    }
    .status-pending {
        background-color: #dc3545;
        color: white;
    }
    .button-container {
        display: flex;
        gap: 10px;
    }
    
    /* Style personnalisé pour les expanders de recommandation */
    .recommendation-expander {
        background-color: #e6f2ff !important;
        border-radius: 8px !important;
        border: 1px solid #b3d9ff !important;
        margin-top: 10px !important;
    }
    
    /* Modification du style des éléments d'expander de Streamlit */
    .st-emotion-cache-1abe2ax, .st-emotion-cache-ue6h4q, .st-emotion-cache-1y4p8pa {
        background-color: #e6f2ff !important;
    }
    /* Modification pour le header d'expander */
    .st-emotion-cache-19rxjzo {
        background-color: #cce5ff !important;
    }
</style>
""", unsafe_allow_html=True)

# Initialiser les états de session
if 'api_key' not in st.session_state:
    st.session_state['api_key'] = ""
if 'action_plan_df' not in st.session_state:
    st.session_state['action_plan_df'] = None
if 'recommendations' not in st.session_state:
    st.session_state['recommendations'] = {}
if 'responses' not in st.session_state:
    st.session_state['responses'] = {}
if 'active_item' not in st.session_state:
    st.session_state['active_item'] = None
if 'ask_questions' not in st.session_state:
    st.session_state['ask_questions'] = {}
if 'session_id' not in st.session_state:
    st.session_state['session_id'] = str(uuid.uuid4())

# Initialiser GroqProvider
def get_groq_provider():
    if not st.session_state.get('api_key'):
        st.error("⚠️ Veuillez entrer votre clé API Groq.")
        return None
    return GroqProvider(api_key=st.session_state.api_key)

# Charger le fichier Excel avec le plan d'action
def load_action_plan(uploaded_file):
    try:
        # Utiliser header=11 pour sauter les lignes d'en-tête
        action_plan_df = pd.read_excel(uploaded_file, header=11)
        action_plan_df = action_plan_df[["requirementNo", "requirementText", "requirementExplanation"]]
        action_plan_df.columns = ["Numéro d'exigence", "Exigence IFS Food 8", "Explication (par l'auditeur/l'évaluateur)"]
        
        # Ajouter une colonne de statut
        action_plan_df["Statut"] = "Non traité"
        
        # Supprimer les lignes vides (où le numéro d'exigence est NaN ou vide)
        action_plan_df = action_plan_df.dropna(subset=["Numéro d'exigence"])
        action_plan_df = action_plan_df[action_plan_df["Numéro d'exigence"].astype(str).str.strip() != ""]
        
        return action_plan_df
    except Exception as e:
        st.error(f"⚠️ Erreur lors de la lecture du fichier: {str(e)}")
        return None

# Générer des questions adaptées à la non-conformité
def generate_questions(non_conformity):
    req_text = non_conformity["Exigence IFS Food 8"]
    audit_comment = non_conformity["Explication (par l'auditeur/l'évaluateur)"]
    
    # Détecter les mots-clés pour personnaliser les questions
    keywords = {
        "formation": ["formation", "compétence", "qualification", "personnel"],
        "documentation": ["document", "procédure", "enregistrement", "contrôle"],
        "équipement": ["équipement", "matériel", "maintenance", "installation"],
        "hygiène": ["hygiène", "nettoyage", "désinfection", "contamination"],
        "traçabilité": ["traçabilité", "lot", "identification", "rappel"]
    }
    
    # Questions de base
    questions = [
        {
            "id": "context",
            "question": "Décrivez brièvement le contexte actuel lié à cette exigence dans votre entreprise."
        },
        {
            "id": "cause",
            "question": "Selon vous, quelle est la cause principale de cette non-conformité ?"
        }
    ]
    
    # Ajouter des questions spécifiques en fonction des mots-clés
    for category, terms in keywords.items():
        for term in terms:
            if term.lower() in req_text.lower() or term.lower() in audit_comment.lower():
                if category == "formation":
                    questions.append({
                        "id": "training",
                        "question": "Le personnel a-t-il reçu une formation spécifique sur ce sujet ? Si oui, quand était la dernière formation ?"
                    })
                    break
                elif category == "documentation":
                    questions.append({
                        "id": "documentation",
                        "question": "Disposez-vous d'une procédure ou d'instructions pour ce processus ? Est-elle à jour ?"
                    })
                    break
                elif category == "équipement":
                    questions.append({
                        "id": "equipment",
                        "question": "Les équipements concernés sont-ils adaptés et correctement entretenus ?"
                    })
                    break
                elif category == "hygiène":
                    questions.append({
                        "id": "hygiene",
                        "question": "Quelles sont vos pratiques actuelles de nettoyage/désinfection dans cette zone ?"
                    })
                    break
                elif category == "traçabilité":
                    questions.append({
                        "id": "traceability",
                        "question": "Comment assurez-vous actuellement la traçabilité dans ce processus ?"
                    })
                    break
    
    # Limiter à 3 questions maximum
    return questions[:3]

# Détecter la langue du texte
def detect_language(text):
    # Liste de mots français courants pour détection simple
    french_words = ["et", "les", "des", "dans", "pour", "avec", "par", "sur", "en", "au", "aux", 
                   "de", "la", "le", "du", "un", "une", "cette", "est", "sont", "ont", "qui",
                   "non", "conformité", "exigence", "auditeur"]
    
    # Compter les mots français
    text_lower = text.lower()
    french_count = sum(1 for word in french_words if f" {word} " in f" {text_lower} ")
    
    # Si au moins 3 mots français sont détectés, considérer comme français
    return "fr" if french_count >= 3 else "en"

# Générer une recommandation avec Groq
def generate_ai_recommendation(non_conformity, responses=None, direct=False):
    groq = get_groq_provider()
    if not groq:
        return "Erreur: clé API non fournie."

    # Détecter la langue
    language = detect_language(non_conformity["Exigence IFS Food 8"] + " " + 
                              non_conformity["Explication (par l'auditeur/l'évaluateur)"])
    
    # Construire le prompt selon la langue détectée
    if language == "fr":
        if direct:
            prompt = f"""
            En tant qu'expert en IFS Food 8 et en sécurité alimentaire, analysez cette non-conformité et proposez un plan d'action complet.
            
            # NON-CONFORMITÉ
            - Exigence N°: {non_conformity["Numéro d'exigence"]}
            - Exigence IFS Food 8: {non_conformity["Exigence IFS Food 8"]}
            - Constat de l'auditeur: {non_conformity["Explication (par l'auditeur/l'évaluateur)"]}
            
            # VOTRE MISSION
            Fournir une analyse et un plan d'action structuré avec les sections suivantes:
            
            1. ANALYSE DE LA NON-CONFORMITÉ
            Analysez la situation et identifiez clairement le problème.
            
            2. ANALYSE DES CAUSES
            Identifiez et détaillez les causes racines probables.
            
            3. PLAN D'ACTION
               a) Actions immédiates (corrections)
               b) Type de preuves à fournir
               c) Actions correctives à long terme
            
            4. MÉTHODES DE VALIDATION DE L'EFFICACITÉ
            Comment vérifier que les actions mises en place sont efficaces.
            
            5. RECOMMANDATIONS COMPLÉMENTAIRES
            
            Rédigez l'ensemble de l'analyse en français et fournissez des recommandations spécifiques, réalistes et conformes à l'IFS Food 8.
            """
        else:
            prompt = f"""
            En tant qu'expert en IFS Food 8 et en sécurité alimentaire, analysez cette non-conformité et les informations fournies pour proposer un plan d'action adapté.
            
            # NON-CONFORMITÉ
            - Exigence N°: {non_conformity["Numéro d'exigence"]}
            - Exigence IFS Food 8: {non_conformity["Exigence IFS Food 8"]}
            - Constat de l'auditeur: {non_conformity["Explication (par l'auditeur/l'évaluateur)"]}
            
            # INFORMATIONS FOURNIES PAR L'UTILISATEUR
            {json.dumps(responses, indent=2)}
            
            # VOTRE MISSION
            Fournir une analyse et un plan d'action structuré avec les sections suivantes:
            
            1. ANALYSE DE LA NON-CONFORMITÉ
            Analysez la situation et identifiez clairement le problème en tenant compte des informations fournies.
            
            2. ANALYSE DES CAUSES
            Identifiez et détaillez les causes racines probables.
            
            3. PLAN D'ACTION
               a) Actions immédiates (corrections)
               b) Type de preuves à fournir
               c) Actions correctives à long terme
            
            4. MÉTHODES DE VALIDATION DE L'EFFICACITÉ
            Comment vérifier que les actions mises en place sont efficaces.
            
            5. RECOMMANDATIONS COMPLÉMENTAIRES
            
            Rédigez l'ensemble de l'analyse en français et fournissez des recommandations spécifiques, réalistes et conformes à l'IFS Food 8.
            """
    else:
        # En anglais
        if direct:
            prompt = f"""
            As an IFS Food 8 and food safety expert, analyze this non-conformity and provide a comprehensive action plan.
            
            # NON-CONFORMITY
            - Requirement No.: {non_conformity["Numéro d'exigence"]}
            - IFS Food 8 Requirement: {non_conformity["Exigence IFS Food 8"]}
            - Auditor's finding: {non_conformity["Explication (par l'auditeur/l'évaluateur)"]}
            
            # YOUR MISSION
            Provide an analysis and structured action plan with the following sections:
            
            1. ANALYSIS OF THE NON-CONFORMITY
            Analyze the situation and clearly identify the problem.
            
            2. ROOT CAUSE ANALYSIS
            Identify and detail the probable root causes.
            
            3. ACTION PLAN
               a) Immediate actions (corrections)
               b) Type of evidence to be provided
               c) Long-term corrective actions
            
            4. METHODS FOR VALIDATING EFFECTIVENESS
            How to verify that the implemented actions are effective.
            
            5. ADDITIONAL RECOMMENDATIONS
            
            Write the entire analysis in English and provide specific, realistic recommendations that comply with IFS Food 8.
            """
        else:
            prompt = f"""
            As an IFS Food 8 and food safety expert, analyze this non-conformity and the information provided to propose an adapted action plan.
            
            # NON-CONFORMITY
            - Requirement No.: {non_conformity["Numéro d'exigence"]}
            - IFS Food 8 Requirement: {non_conformity["Exigence IFS Food 8"]}
            - Auditor's finding: {non_conformity["Explication (par l'auditeur/l'évaluateur)"]}
            
            # INFORMATION PROVIDED BY THE USER
            {json.dumps(responses, indent=2)}
            
            # YOUR MISSION
            Provide an analysis and structured action plan with the following sections:
            
            1. ANALYSIS OF THE NON-CONFORMITY
            Analyze the situation and clearly identify the problem, taking into account the information provided.
            
            2. ROOT CAUSE ANALYSIS
            Identify and detail the probable root causes.
            
            3. ACTION PLAN
               a) Immediate actions (corrections)
               b) Type of evidence to be provided
               c) Long-term corrective actions
            
            4. METHODS FOR VALIDATING EFFECTIVENESS
            How to verify that the implemented actions are effective.
            
            5. ADDITIONAL RECOMMENDATIONS
            
            Write the entire analysis in English and provide specific, realistic recommendations that comply with IFS Food 8.
            """
    
    try:
        # Option: Utilisez Chain of Thought pour une analyse plus approfondie
        return groq.generate(prompt, max_tokens=1500, temperature=0.2, use_cot=True)
    except Exception as e:
        st.error(f"⚠️ Erreur lors de la génération de la recommandation : {str(e)}")
        return None

# Sauvegarde et chargement de session
def save_session_data():
    session_data = {
        "recommendations": st.session_state['recommendations'],
        "responses": st.session_state['responses'],
        "action_plan_status": st.session_state['action_plan_df']["Statut"].to_dict() if st.session_state['action_plan_df'] is not None else {}
    }
    
    return json.dumps(session_data)

def load_session_data(session_json):
    try:
        data = json.loads(session_json)
        
        st.session_state['recommendations'] = data.get("recommendations", {})
        st.session_state['responses'] = data.get("responses", {})
        
        # Mettre à jour les statuts si le plan d'action est déjà chargé
        if st.session_state['action_plan_df'] is not None and "action_plan_status" in data:
            for idx, status in data["action_plan_status"].items():
                if int(idx) in st.session_state['action_plan_df'].index:
                    st.session_state['action_plan_df'].loc[int(idx), "Statut"] = status
        
        return True
    except Exception as e:
        st.error(f"⚠️ Erreur lors du chargement de la session : {str(e)}")
        return False

# Interface principale
def main():
    # Ajouter la bannière VisiPilot
    st.markdown('<div class="banner"></div>', unsafe_allow_html=True)
    st.markdown('<div class="main-header">📊 VisiPilot - Assistant Plan d\'Actions IFS Food 8</div>', unsafe_allow_html=True)
    
    # Sidebar pour la configuration
    with st.sidebar:
        st.markdown("### ⚙️ Configuration")
        
        # API Key Groq
        api_key = st.text_input("Clé API Groq :", type="password", value=st.session_state.get('api_key', ''))
        if api_key:
            st.session_state.api_key = api_key
        
        st.markdown("---")
        
        # Téléchargement du fichier Excel
        st.markdown("### 📤 Plan d'Action")
        uploaded_file = st.file_uploader("Fichier Excel du plan d'action :", type=["xlsx"])
        
        if uploaded_file:
            action_plan_df = load_action_plan(uploaded_file)
            if action_plan_df is not None:
                st.session_state['action_plan_df'] = action_plan_df
                st.success("✅ Plan d'action chargé avec succès")
        
        st.markdown("---")
        
        # Sauvegarde/Chargement de session
        st.markdown("### 💾 Session")
        
        col1, col2 = st.columns(2)
        
        with col1:
            if st.button("💾 Télécharger sauvegarde"):
                if 'action_plan_df' in st.session_state and st.session_state['action_plan_df'] is not None:
                    session_data = save_session_data()
                    # Créer un lien de téléchargement pour le fichier JSON
                    timestamp = time.strftime("%Y%m%d-%H%M%S")
                    st.download_button(
                        label="📥 Télécharger le fichier",
                        data=session_data,
                        file_name=f"visipilot_session_{timestamp}.json",
                        mime="application/json"
                    )
                else:
                    st.warning("Chargez d'abord un plan d'action.")
        
        with col2:
            session_file = st.file_uploader("📂 Charger une sauvegarde", type=["json"])
            if session_file is not None:
                session_content = session_file.getvalue().decode("utf-8")
                if load_session_data(session_content):
                    st.success("✅ Session restaurée avec succès")
        
        st.markdown("---")
        
        # Statistiques
        if 'action_plan_df' in st.session_state and st.session_state['action_plan_df'] is not None:
            st.markdown("### 📈 Progression")
            
            total = len(st.session_state['action_plan_df'])
            completed = sum(st.session_state['action_plan_df']["Statut"] == "Complété")
            in_progress = sum(st.session_state['action_plan_df']["Statut"] == "En cours")
            
            st.progress(completed / total if total > 0 else 0)
            st.markdown(f"**{completed}/{total}** complétées ({int((completed/total)*100) if total > 0 else 0}%)")
    
    # Section principale
    if 'action_plan_df' not in st.session_state or st.session_state['action_plan_df'] is None:
        st.info("👈 Commencez par configurer votre clé API Groq et charger votre plan d'action dans la barre latérale.")
    else:
        # Filtres
        col1, col2 = st.columns([1, 2])
        with col1:
            status_filter = st.multiselect("Filtrer par statut", 
                                         options=["Tous", "Non traité", "En cours", "Complété"], 
                                         default=["Tous"])
        with col2:
            search_term = st.text_input("Rechercher une exigence", "")
        
        # Appliquer les filtres
        filtered_df = st.session_state['action_plan_df'].copy()
        
        if "Tous" not in status_filter:
            filtered_df = filtered_df[filtered_df["Statut"].isin(status_filter)]
        
        if search_term:
            filtered_df = filtered_df[
                filtered_df["Exigence IFS Food 8"].str.contains(search_term, case=False) |
                filtered_df["Numéro d'exigence"].astype(str).str.contains(search_term, case=False)
            ]
        
        # Afficher les non-conformités
        for index, row in filtered_df.iterrows():
            # Déterminer la classe du badge de statut
            status_class = "status-pending"
            if row["Statut"] == "Complété":
                status_class = "status-completed"
            elif row["Statut"] == "En cours":
                status_class = "status-progress"
            
            # Afficher la carte de non-conformité
            with st.container():
                st.markdown(f"""
                <div class="card">
                    <div>
                        <strong>Exigence {row["Numéro d'exigence"]}</strong>
                        <span class="status-badge {status_class}">{row["Statut"]}</span>
                    </div>
                    <div style="margin-top: 5px;">
                        {row["Exigence IFS Food 8"]}
                    </div>
                    <div style="font-style: italic; margin-top: 5px; color: #666;">
                        <strong>Constat de l'auditeur:</strong> {row["Explication (par l'auditeur/l'évaluateur)"]}
                    </div>
                </div>
                """, unsafe_allow_html=True)
                
                # Options d'action
                col1, col2, col3 = st.columns([1, 1, 1])
                
                with col1:
                    if st.button("💡 Questions ciblées", key=f"questions_{index}"):
                        st.session_state['active_item'] = index
                        st.session_state['ask_questions'][index] = True
                
                with col2:
                    if st.button("🤖 Recommandation directe", key=f"direct_{index}"):
                        with st.spinner("Génération en cours..."):
                            recommendation = generate_ai_recommendation(row, direct=True)
                            if recommendation:
                                st.session_state['recommendations'][index] = recommendation
                                st.session_state['active_item'] = index
                                # Mettre à jour le statut
                                st.session_state['action_plan_df'].loc[index, "Statut"] = "En cours"
                                st.rerun()
                
                with col3:
                    status_options = ["Non traité", "En cours", "Complété"]
                    new_status = st.selectbox("Statut", options=status_options, 
                                            index=status_options.index(row["Statut"]),
                                            key=f"status_{index}")
                    
                    if new_status != row["Statut"]:
                        st.session_state['action_plan_df'].loc[index, "Statut"] = new_status
                
                # Si cet élément est actif pour les questions
                if index == st.session_state.get('active_item') and st.session_state['ask_questions'].get(index, False):
                    with st.container():
                        st.markdown("#### Questions pour analyse ciblée")
                        
                        # Générer des questions adaptées
                        questions = generate_questions(row)
                        
                        with st.form(key=f"questions_form_{index}"):
                            responses = {}
                            for q in questions:
                                responses[q["id"]] = st.text_area(q["question"], key=f"q_{index}_{q['id']}")
                            
                            submit_btn = st.form_submit_button("Générer recommandation")
                            if submit_btn:
                                with st.spinner("Génération en cours..."):
                                    recommendation = generate_ai_recommendation(row, responses)
                                    if recommendation:
                                        st.session_state['recommendations'][index] = recommendation
                                        st.session_state['responses'][index] = responses
                                        # Mettre à jour le statut
                                        st.session_state['action_plan_df'].loc[index, "Statut"] = "En cours"
                                        st.rerun()
                
                # Afficher la recommandation si elle existe
                if index == st.session_state.get('active_item') or index in st.session_state.get('recommendations', {}):
                    if index in st.session_state.get('recommendations', {}):
                        with st.expander("📋 Recommandation IA", expanded=True if index == st.session_state.get('active_item') else False):
                            # Appliquer la classe personnalisée à l'expander via HTML
                            st.markdown('<div class="recommendation-expander">', unsafe_allow_html=True)
                            st.markdown(st.session_state['recommendations'][index])
                            st.markdown('</div>', unsafe_allow_html=True)
                            
                            if st.button("✅ Marquer comme complété", key=f"complete_{index}"):
                                st.session_state['action_plan_df'].loc[index, "Statut"] = "Complété"
                                st.rerun()
                
                st.markdown("---")

if __name__ == "__main__":
    main()