Skip to content

Commit babf6cc

Browse files
committed
feat: fmea creation is interactive
1 parent 3e34c76 commit babf6cc

1 file changed

Lines changed: 115 additions & 11 deletions

File tree

failbot/failbotUI.py

Lines changed: 115 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -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:**\nNo 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
322385
async def start():
@@ -336,6 +399,8 @@ async def start():
336399

337400
@cl.action_callback("create_fmea")
338401
async 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")
441544
async 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

Comments
 (0)