TimeCapsuleX commited on
Commit
3914cd6
·
1 Parent(s): 434f17d

Add application file

Browse files
10000fmea_data.csv ADDED
The diff for this file is too large to render. See raw diff
 
__pycache__/app.cpython-311.pyc ADDED
Binary file (21.8 kB). View file
 
app.py ADDED
@@ -0,0 +1,315 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import re
4
+ import torch
5
+ import gradio as gr
6
+ import pandas as pd
7
+
8
+ # --- LangChain & Groq Imports ---
9
+ from langchain_groq import ChatGroq
10
+ from langchain_community.vectorstores import FAISS
11
+ from langchain_huggingface import HuggingFaceEmbeddings
12
+ from langchain_core.documents import Document
13
+ from langchain_core.prompts import PromptTemplate
14
+
15
+ # --- 1. Setup API Key for Groq ---
16
+ # Ensure you add GROQ_API_KEY to your Hugging Face Space Secrets
17
+ GROQ_API_KEY = os.environ.get('GROQ_API_KEY')
18
+ if not GROQ_API_KEY:
19
+ raise ValueError("🔴 GROQ_API_KEY not found. Please add it to your Hugging Face Space Secrets.")
20
+
21
+ # --- 2. Build the RAG Chain & Feedback System ---
22
+ FMEA_DATA_FILE = '10000fmea_data.csv'
23
+ FEEDBACK_FILE = 'fmea_feedback.csv'
24
+ QA_CHAIN = None
25
+ RETRIEVER = None
26
+ LLM = None
27
+ PROMPT = None
28
+ feedback_vector_store = None
29
+ embeddings = None
30
+
31
+ DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'
32
+ print(f"✅ Using device: {DEVICE}")
33
+
34
+ # --- FEEDBACK LOOP PART 1: Saving, Normalizing, and Loading Feedback ---
35
+ def normalize_action(text: str) -> str:
36
+ return re.sub(r'\s+', ' ', str(text).strip().lower())
37
+
38
+ def load_feedback_stats():
39
+ if not os.path.exists(FEEDBACK_FILE):
40
+ return {}
41
+ try:
42
+ feedback_df = pd.read_csv(FEEDBACK_FILE)
43
+ if feedback_df.empty:
44
+ return {}
45
+ stats = feedback_df.groupby('action')['rating'].agg(['mean', 'count']).to_dict('index')
46
+ return stats
47
+ except pd.errors.EmptyDataError:
48
+ return {}
49
+
50
+ def save_feedback(action, rating, display_df):
51
+ if not action:
52
+ return "Please select a recommendation from the table first.", display_df
53
+ norm_action = normalize_action(action)
54
+ new_feedback = pd.DataFrame([{'action': norm_action, 'rating': int(rating)}])
55
+ if not os.path.exists(FEEDBACK_FILE):
56
+ new_feedback.to_csv(FEEDBACK_FILE, index=False)
57
+ else:
58
+ new_feedback.to_csv(FEEDBACK_FILE, mode='a', header=False, index=False)
59
+ build_feedback_db()
60
+
61
+ msg = f"✅ Rating of {rating}/10 saved for: {action}"
62
+
63
+ # Update the displayed table dynamically
64
+ if display_df is not None and not display_df.empty:
65
+ try:
66
+ feedback_stats = load_feedback_stats()
67
+ default_stat = {'mean': 0, 'count': 0}
68
+ stats_list = [feedback_stats.get(normalize_action(act), default_stat) for act in display_df['Recommended Action']]
69
+ display_df['Avg. Feedback'] = [f"{stat['mean']:.2f}/10 ({int(stat['count'])})" for stat in stats_list]
70
+ except Exception as e:
71
+ print(f"Error updating display_df: {e}")
72
+
73
+ return msg, display_df
74
+
75
+ def build_feedback_db():
76
+ global feedback_vector_store
77
+ if not os.path.exists(FEEDBACK_FILE):
78
+ return
79
+ try:
80
+ feedback_df = pd.read_csv(FEEDBACK_FILE)
81
+ if feedback_df.empty:
82
+ return
83
+ except pd.errors.EmptyDataError:
84
+ return
85
+
86
+ avg_ratings = feedback_df.groupby('action')['rating'].mean()
87
+ highly_rated_actions = avg_ratings[avg_ratings > 7].index.tolist()
88
+
89
+ if highly_rated_actions and embeddings:
90
+ print(f"Found {len(highly_rated_actions)} highly-rated actions. Building feedback vector store...")
91
+ feedback_vector_store = FAISS.from_texts(highly_rated_actions, embeddings)
92
+ print("✅ Feedback vector store is ready.")
93
+
94
+ # --- build_rag_chain ---
95
+ def build_rag_chain():
96
+ global QA_CHAIN, RETRIEVER, LLM, PROMPT, embeddings
97
+ try:
98
+ print("Initializing local HuggingFace embedding model...")
99
+ embeddings = HuggingFaceEmbeddings(
100
+ model_name='all-MiniLM-L6-v2',
101
+ model_kwargs={'device': DEVICE}
102
+ )
103
+ print("✅ Local embedding model loaded.")
104
+
105
+ build_feedback_db()
106
+
107
+ print(f"Loading FMEA data from {FMEA_DATA_FILE}...")
108
+ fmea_df = pd.read_csv(FMEA_DATA_FILE).fillna("")
109
+ documents = []
110
+ for idx, row in fmea_df.iterrows():
111
+ page_content = "\n".join([f"{col}: {row[col]}" for col in fmea_df.columns])
112
+ metadata = {"row": int(idx)}
113
+ if "Failure_Mode" in fmea_df.columns:
114
+ metadata["source"] = str(row["Failure_Mode"])
115
+ documents.append(Document(page_content=page_content, metadata=metadata))
116
+ print(f"✅ Successfully loaded {len(documents)} records.")
117
+
118
+ print("Creating embeddings and building main FAISS vector store...")
119
+ main_vector_store = FAISS.from_documents(documents, embeddings)
120
+ print("✅ Main vector store created successfully.")
121
+
122
+ # --- UPDATED TO USE LLAMA 3.3 VIA GROQ ---
123
+ llm = ChatGroq(model_name="llama-3.3-70b-versatile", temperature=0.2)
124
+
125
+ prompt_template = """
126
+ You are an expert FMEA analyst. Your task is to generate the TOP 3 recommended actions for the given failure.
127
+ The user has provided their current S, O, and D scores.
128
+ For EACH recommendation, you must also estimate the revised S, O, and D scores (1-10) that would result *after* that action is successfully implemented.
129
+
130
+ - **new_S (Severity):** This score should *usually* stay the same as the original Severity.
131
+ - **new_O (Occurrence):** This score should be *lower* than the original Occurrence.
132
+ - **new_D (Detection):** This score should be *lower* than the original Detection (as the action makes the failure easier to detect).
133
+
134
+ CONTEXT (Historical data and user feedback):
135
+ {context}
136
+
137
+ QUESTION (The new failure and its current scores):
138
+ {question}
139
+
140
+ INSTRUCTIONS:
141
+ Format your entire response as a single, valid JSON object with a key "recommendations" which is a list of 3 objects.
142
+ Each object must have these keys: "rank", "action", "department", "ai_score", "new_S", "new_O", "new_D".
143
+
144
+ - "rank": The rank of the recommendation (1, 2, 3).
145
+ - "action": The recommended action text.
146
+ - "department": The most likely responsible department.
147
+ - "ai_score": Confidence score (1-100) for this recommendation.
148
+ - "new_S": Your estimated new Severity score (1-10).
149
+ - "new_O": Your estimated new Occurrence score (1-10).
150
+ - "new_D": Your estimated new Detection score (1-10).
151
+
152
+ CRITICAL: Output ONLY the raw JSON object. Do not include markdown formatting like ```json or any introductory text.
153
+ """
154
+ PROMPT = PromptTemplate(template=prompt_template, input_variables=["context", "question"])
155
+
156
+ # Included the token-saving "k": 2 limit
157
+ RETRIEVER = main_vector_store.as_retriever(search_kwargs={"k": 2})
158
+ LLM = llm
159
+ QA_CHAIN = True
160
+ print("✅ RAG model is ready.")
161
+ return True
162
+ except Exception as e:
163
+ print(f"🔴 An error occurred during RAG setup: {e}")
164
+ return False
165
+
166
+ # --- 3. Gradio Interface Logic ---
167
+ def fmea_rag_interface(mode, effect, cause, severity, occurrence, detection):
168
+ if QA_CHAIN is None or RETRIEVER is None or LLM is None or PROMPT is None:
169
+ return "RAG Model is not initialized.", pd.DataFrame(), ""
170
+
171
+ rpn = severity * occurrence * detection
172
+ rpn_text = f"Current RPN (S×O×D): {int(rpn)}"
173
+
174
+ query = (
175
+ f"For a failure with Failure Mode='{mode}', Effect='{effect}', and Cause='{cause}', "
176
+ f"what are the top 3 most appropriate recommended actions? "
177
+ f"The current scores are: Severity={severity}, Occurrence={occurrence}, Detection={detection}."
178
+ )
179
+
180
+ docs = RETRIEVER.invoke(query)
181
+ context_from_history = "\n---\n".join([doc.page_content for doc in docs])
182
+
183
+ context_from_feedback = ""
184
+ if feedback_vector_store:
185
+ feedback_docs = feedback_vector_store.similarity_search(query, k=3)
186
+ if feedback_docs:
187
+ feedback_actions = "\n".join([doc.page_content for doc in feedback_docs])
188
+ context_from_feedback = f"\n\n--- Highly-Rated Actions from User Feedback ---\n{feedback_actions}"
189
+
190
+ combined_context = f"--- Historical FMEA Entries ---\n{context_from_history}{context_from_feedback}"
191
+
192
+ try:
193
+ llm_input = PROMPT.format(context=combined_context, question=query)
194
+ llm_response = LLM.invoke(llm_input)
195
+
196
+ # --- IMPROVED JSON PARSING FOR LLAMA ---
197
+ raw_output = str(getattr(llm_response, "content", llm_response)).strip()
198
+ # Find everything between the first '{' and the last '}'
199
+ match = re.search(r'\{.*\}', raw_output, re.DOTALL)
200
+ if match:
201
+ json_text = match.group(0)
202
+ else:
203
+ # Fallback if the regex fails
204
+ json_text = raw_output.replace("```json", "").replace("```", "").strip()
205
+
206
+ data = json.loads(json_text)
207
+ output_df = pd.DataFrame(data['recommendations'])
208
+
209
+ feedback_stats = load_feedback_stats()
210
+ default_stat = {'mean': 0, 'count': 0}
211
+ stats_list = [feedback_stats.get(normalize_action(action), default_stat) for action in output_df['action']]
212
+ output_df['avg_feedback'] = [stat['mean'] for stat in stats_list]
213
+ output_df['feedback_count'] = [stat['count'] for stat in stats_list]
214
+
215
+ output_df['new_S'] = output_df['new_S'].astype(int)
216
+ output_df['new_O'] = output_df['new_O'].astype(int)
217
+ output_df['new_D'] = output_df['new_D'].astype(int)
218
+ output_df['new_RPN'] = output_df['new_S'] * output_df['new_O'] * output_df['new_D']
219
+
220
+ rpn_change_list = [f"{int(rpn)} ➔ {int(new_rpn)}" for new_rpn in output_df['new_RPN']]
221
+
222
+ display_df = pd.DataFrame({
223
+ "Rank": output_df['rank'],
224
+ "Recommended Action": output_df['action'],
225
+ "Department": output_df['department'],
226
+ "AI Confidence": [f"{score}%" for score in output_df['ai_score']],
227
+ "Avg. Feedback": [f"{avg:.2f}/10 ({int(count)})" for avg, count in zip(output_df['avg_feedback'], output_df['feedback_count'])],
228
+ "Revised RPN": rpn_change_list
229
+ })
230
+
231
+ except Exception as e:
232
+ print(f"Error parsing LLM output: {e}\nRaw Output was: {raw_output if 'raw_output' in locals() else 'None'}")
233
+ return rpn_text, pd.DataFrame({"Error": [f"Could not parse AI response: {e}"]}), None
234
+
235
+ return rpn_text, display_df, output_df
236
+
237
+ def get_level_info(val):
238
+ levels = {
239
+ 10: "Hazardous", 9: "Serious", 8: "Extreme", 7: "Major",
240
+ 6: "Significant", 5: "Moderate", 4: "Minor", 3: "Slight",
241
+ 2: "Very Slight", 1: "No Effect"
242
+ }
243
+ return gr.update(info=f"Level: {levels.get(val, '')}")
244
+
245
+ # --- 6. Main Application Execution ---
246
+ if build_rag_chain():
247
+ print("\n🚀 Launching Gradio Interface...")
248
+ with gr.Blocks(theme=gr.themes.Default(primary_hue=gr.themes.colors.blue)) as demo:
249
+ gr.Markdown("<h1>Pangun ReliAI-FMEA</h1>")
250
+
251
+ with gr.Group():
252
+ gr.Markdown("## FMEA Inputs ")
253
+ with gr.Row():
254
+ with gr.Column(scale=2):
255
+ f_mode = gr.Textbox(label="Failure Mode", placeholder="e.g., Engine Overheating")
256
+ f_effect = gr.Textbox(label="Effect", placeholder="e.g., Reduced vehicle performance")
257
+ f_cause = gr.Textbox(label="Cause", placeholder="e.g., Coolant leak")
258
+ with gr.Column(scale=1):
259
+ f_sev = gr.Slider(1, 10, value=5, step=1, label="Severity", info="Level: Moderate")
260
+ f_occ = gr.Slider(1, 10, value=5, step=1, label="Occurrence", info="Level: Moderate")
261
+ f_det = gr.Slider(1, 10, value=5, step=1, label="Detection", info="Level: Moderate")
262
+
263
+ f_sev.change(fn=get_level_info, inputs=f_sev, outputs=f_sev)
264
+ f_occ.change(fn=get_level_info, inputs=f_occ, outputs=f_occ)
265
+ f_det.change(fn=get_level_info, inputs=f_det, outputs=f_det)
266
+
267
+ submit_btn = gr.Button("Get AI Recommendations", variant="primary")
268
+
269
+ with gr.Group():
270
+ gr.Markdown("## 💡 Top 3 AI-Generated Recommendations")
271
+ rpn_output = gr.Textbox(label="Current RPN", interactive=False)
272
+ recommendations_output = gr.DataFrame(
273
+ headers=["Rank", "Recommended Action", "Department", "AI Confidence", "Avg. Feedback", "Revised RPN"],
274
+ datatype=["number", "str", "str", "str", "str", "str"]
275
+ )
276
+ df_state = gr.State()
277
+
278
+ with gr.Group():
279
+ gr.Markdown("## ⭐ Provide Feedback")
280
+ gr.Markdown("Click a row in the table above to select it, then submit your rating.")
281
+ selected_action_text = gr.Textbox(label="Selected for Feedback", interactive=False)
282
+ rating_slider = gr.Slider(minimum=1, maximum=10, step=1, value=8, label="Your Rating (1-10)")
283
+ submit_feedback_btn = gr.Button("Submit Rating")
284
+ feedback_status = gr.Textbox(label="Feedback Status", interactive=False)
285
+
286
+ # FIX 1: Safer update_selection function
287
+ def update_selection(table_df, evt: gr.SelectData):
288
+ if table_df is None or len(table_df) == 0:
289
+ return ""
290
+ row_idx = evt.index[0]
291
+ # "Recommended Action" is the 2nd column in your UI table (index 1)
292
+ selected_action = table_df.iloc[row_idx, 1]
293
+ return selected_action
294
+
295
+ submit_btn.click(
296
+ fn=fmea_rag_interface,
297
+ inputs=[f_mode, f_effect, f_cause, f_sev, f_occ, f_det],
298
+ outputs=[rpn_output, recommendations_output, df_state]
299
+ )
300
+
301
+ # FIX 2: Trigger relies on the visible table
302
+ recommendations_output.select(
303
+ fn=update_selection,
304
+ inputs=[recommendations_output],
305
+ outputs=[selected_action_text]
306
+ )
307
+
308
+ submit_feedback_btn.click(
309
+ fn=save_feedback,
310
+ inputs=[selected_action_text, rating_slider, recommendations_output],
311
+ outputs=[feedback_status, recommendations_output]
312
+ )
313
+
314
+ # Launch command for Hugging Face
315
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ gradio
2
+ pandas
3
+ langchain
4
+ langchain-community
5
+ langchain-core
6
+ faiss-cpu
7
+ sentence-transformers
8
+ langchain-huggingface
9
+ langchain-groq