@@ -317,6 +317,69 @@ async def generate_fmea_from_articles(incidents, user_description):
317317 return response
318318
319319
320+ async def revise_fmea (existing_fmea , incidents , user_description , user_feedback ):
321+ """
322+ Revises an existing Software FMEA using user feedback while keeping it grounded in relevant incidents.
323+ """
324+ incidents_json = json .dumps (incidents , indent = 2 )
325+
326+ prompt = (
327+ "You previously created a Software FMEA for a user-provided system.\n \n "
328+ "System description:\n "
329+ f"{ user_description } \n \n "
330+ "Relevant incidents:\n "
331+ f"{ incidents_json } \n \n "
332+ "Current FMEA:\n "
333+ f"{ existing_fmea } \n \n "
334+ "User feedback for improving the FMEA:\n "
335+ f"{ user_feedback } \n \n "
336+ "Revise the FMEA to address the feedback.\n "
337+ "Keep the same core columns: Item/Function, Potential Failure Mode, Causes, Effects, Severity (S), Occurrence (O), Detection (D), RPN, RPN Rationale, Mitigations.\n "
338+ "Ground the revised FMEA in the relevant incidents and cite incident ID(s) where appropriate.\n "
339+ "Improve completeness, clarity, and prioritization where the feedback indicates changes are needed.\n "
340+ "Return only the revised FMEA."
341+ )
342+
343+ logging .info ("Revising FMEA using user feedback..." )
344+ response = (await conversation_chain .ainvoke ({"input" : prompt }))["response" ]
345+ logging .info (f"Revised FMEA Response:\n { response } " )
346+
347+ return response
348+
349+
350+ def _fmea_iteration_actions ():
351+ return [
352+ cl .Action (name = "fmea_done" , value = "done" , label = "✅ Looks Good" , payload = {}),
353+ cl .Action (name = "restart" , value = "restart" , label = "🔄 Start Over" , payload = {}),
354+ ]
355+
356+
357+ def _filtered_incidents_message (filtered_incidents ):
358+ if not filtered_incidents :
359+ return "📋 **Filtered Incidents:**\n No incidents remained after filtering."
360+
361+ filtered_incidents_str = "\n " .join (
362+ [f"- ID: { inc ['ID' ]} , Title: { inc ['Title' ]} " for inc in filtered_incidents ]
363+ )
364+ return f"📋 **Filtered Incidents:**\n { filtered_incidents_str } "
365+
366+
367+ def _is_fmea_iteration_complete (user_message ):
368+ normalized = user_message .strip ().lower ()
369+ return normalized in {
370+ "done" ,
371+ "looks good" ,
372+ "this looks good" ,
373+ "good" ,
374+ "approved" ,
375+ "ship it" ,
376+ "finalize" ,
377+ "finalise" ,
378+ "no changes" ,
379+ "no more changes" ,
380+ }
381+
382+
320383# --- Chainlit App ---
321384@cl .on_chat_start
322385async def start ():
@@ -336,6 +399,8 @@ async def start():
336399
337400@cl .action_callback ("create_fmea" )
338401async def on_create_fmea (action ):
402+ cl .user_session .set ("fmea_context" , None )
403+ cl .user_session .set ("filtered_incidents" , None )
339404 cl .user_session .set ("state" , "awaiting_fmea_description" )
340405 await cl .Message (
341406 content = "To get started, please describe the system you're designing:"
@@ -362,9 +427,9 @@ async def on_message(message: cl.Message):
362427
363428 await cl .Message (content = f"🔎 Found { len (incidents )} incidents. Filtering with LLM for most relevant incidents..." ).send ()
364429 filtered_incidents = await filter_relevant_incidents_with_llm (incidents , system_description )
430+ cl .user_session .set ("filtered_incidents" , filtered_incidents )
365431
366- filtered_incidents_str = "\n " .join ([f"- ID: { inc ['ID' ]} , Title: { inc ['Title' ]} " for inc in filtered_incidents ])
367- await cl .Message (content = f"📋 **Filtered Incidents:**\n { filtered_incidents_str } " ).send ()
432+ await cl .Message (content = _filtered_incidents_message (filtered_incidents )).send ()
368433
369434 await cl .Message (content = f"📊 Generating FMEA from { len (filtered_incidents )} filtered incidents..." ).send ()
370435 fmea_output = await generate_fmea_from_articles (filtered_incidents , system_description )
@@ -373,8 +438,12 @@ async def on_message(message: cl.Message):
373438 cl .user_session .set ("state" , "fmea_generated" )
374439
375440 await cl .Message (
376- content = f"📋 **Generated FMEA:**\n \n { fmea_output } " ,
377- actions = [cl .Action (name = "restart" , value = "restart" , label = "🔄 Start Over" , payload = {})]
441+ content = (
442+ f"📋 **Generated FMEA:**\n \n { fmea_output } \n \n "
443+ "Tell me what you want to improve and I will revise this FMEA. "
444+ "For example: add more failure modes, adjust severity rankings, expand mitigations, or tighten the rationale."
445+ ),
446+ actions = _fmea_iteration_actions (),
378447 ).send ()
379448
380449 elif state == "chat_mode" :
@@ -394,19 +463,19 @@ async def on_message(message: cl.Message):
394463 await cl .Message (content = f"🔎 Found { len (incidents )} incidents. Filtering with LLM for most relevant incidents..." ).send ()
395464 system_description = cl .user_session .get ("system_description" , message .content )
396465 filtered_incidents = await filter_relevant_incidents_with_llm (incidents , system_description )
466+ cl .user_session .set ("filtered_incidents" , filtered_incidents )
397467
398- filtered_incidents_str = "\n " .join ([f"- ID: { inc ['ID' ]} , Title: { inc ['Title' ]} " for inc in filtered_incidents ])
399- await cl .Message (content = f"📋 **Filtered Incidents:**\n { filtered_incidents_str } " ).send ()
468+ await cl .Message (content = _filtered_incidents_message (filtered_incidents )).send ()
400469
401- await cl .Message (content = f"🔍 Found { len (incidents )} relevant incidents." ).send ()
470+ await cl .Message (content = f"🔍 Found { len (filtered_incidents )} relevant incidents after filtering ." ).send ()
402471
403- if not incidents :
472+ if not filtered_incidents :
404473 await cl .Message (content = f"🔍 No relevant incidents found." ).send ()
405474 response = (await conversation_chain .ainvoke ({"input" : message .content }))["response" ]
406475 await cl .Message (content = response ).send ()
407476 return
408477
409- incidents_json = json .dumps (incidents , indent = 2 )
478+ incidents_json = json .dumps (filtered_incidents , indent = 2 )
410479 prompt = (
411480 "You are a chatbot assistant for a database of software failures. "
412481 "A user has asked a question. Use the following relevant incidents from the database to answer the user's question.\n "
@@ -424,8 +493,34 @@ async def on_message(message: cl.Message):
424493 elif state == "fmea_generated" :
425494 follow_up = message .content
426495 await _maybe_update_thread_title (follow_up )
427- response = (await conversation_chain .ainvoke ({"input" : follow_up }))["response" ]
428- await cl .Message (content = response ).send ()
496+ if _is_fmea_iteration_complete (follow_up ):
497+ cl .user_session .set ("state" , "chat_mode" )
498+ await cl .Message (
499+ content = "FMEA iteration complete. You can now chat with the Failures database or start another FMEA whenever you want." ,
500+ actions = [cl .Action (name = "restart" , value = "restart" , label = "🔄 Start Over" , payload = {})],
501+ ).send ()
502+ return
503+
504+ system_description = cl .user_session .get ("system_description" )
505+ filtered_incidents = cl .user_session .get ("filtered_incidents" , [])
506+ current_fmea = cl .user_session .get ("fmea_context" )
507+
508+ await cl .Message (content = "🛠 Revising the FMEA based on your feedback..." ).send ()
509+ revised_fmea = await revise_fmea (
510+ existing_fmea = current_fmea ,
511+ incidents = filtered_incidents ,
512+ user_description = system_description ,
513+ user_feedback = follow_up ,
514+ )
515+
516+ cl .user_session .set ("fmea_context" , revised_fmea )
517+ await cl .Message (
518+ content = (
519+ f"📋 **Revised FMEA:**\n \n { revised_fmea } \n \n "
520+ "Tell me what else to improve, or click Looks Good if you want to keep this version."
521+ ),
522+ actions = _fmea_iteration_actions (),
523+ ).send ()
429524
430525 else : # state is "initial" or None
431526 actions = [
@@ -437,10 +532,19 @@ async def on_message(message: cl.Message):
437532 actions = actions
438533 ).send ()
439534
535+ @cl .action_callback ("fmea_done" )
536+ async def on_fmea_done (action ):
537+ cl .user_session .set ("state" , "chat_mode" )
538+ await cl .Message (
539+ content = "FMEA iteration complete. You can keep chatting with the Failures database or start another FMEA whenever you want." ,
540+ actions = [cl .Action (name = "restart" , value = "restart" , label = "🔄 Start Over" , payload = {})],
541+ ).send ()
542+
440543@cl .action_callback ("restart" )
441544async def on_restart (action ):
442545 cl .user_session .set ("system_description" , None )
443546 cl .user_session .set ("fmea_context" , None )
547+ cl .user_session .set ("filtered_incidents" , None )
444548 memory .clear ()
445549 cl .user_session .set ("state" , "initial" )
446550 actions = [
0 commit comments