TimeCapsuleX commited on
Commit
fcb0e45
·
verified ·
1 Parent(s): 4fcb7d2

Upload 4 files

Browse files
Files changed (4) hide show
  1. 10000fmea_data.csv +0 -0
  2. Dockerfile +31 -0
  3. backend_api.py +270 -0
  4. requirements.txt +14 -0
10000fmea_data.csv ADDED
The diff for this file is too large to render. See raw diff
 
Dockerfile ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ # Set timezone and noninteractive to avoid prompts during apt-get
4
+ ENV DEBIAN_FRONTEND=noninteractive
5
+ ENV TZ=UTC
6
+
7
+ # Install system dependencies if required for some python packages
8
+ RUN apt-get update && apt-get install -y --no-install-recommends \
9
+ build-essential \
10
+ && rm -rf /var/lib/apt/lists/*
11
+
12
+ WORKDIR /code
13
+
14
+ COPY ./requirements.txt /code/requirements.txt
15
+
16
+ # Install dependencies (CPU versions of torch are much smaller and better for free tier)
17
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
18
+
19
+ # Create a non-root user (Required for Hugging Face Spaces)
20
+ RUN useradd -m -u 1000 user
21
+ USER user
22
+
23
+ ENV HOME=/home/user \
24
+ PATH=/home/user/.local/bin:$PATH
25
+
26
+ WORKDIR $HOME/app
27
+
28
+ COPY --chown=user . $HOME/app
29
+
30
+ # Hugging Face Spaces expose port 7860
31
+ CMD ["uvicorn", "backend_api:app", "--host", "0.0.0.0", "--port", "7860"]
backend_api.py ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import re
4
+ from datetime import datetime, timezone
5
+ import torch
6
+ import pandas as pd
7
+ from fastapi import FastAPI, HTTPException
8
+ from fastapi.middleware.cors import CORSMiddleware
9
+ from pydantic import BaseModel
10
+ from dotenv import load_dotenv
11
+ from supabase import create_client, Client
12
+
13
+ load_dotenv()
14
+
15
+ # --- LangChain & Groq Imports ---
16
+ from langchain_groq import ChatGroq
17
+ from langchain_community.vectorstores import FAISS
18
+ from langchain_huggingface import HuggingFaceEmbeddings
19
+ from langchain_core.documents import Document
20
+ from langchain_core.prompts import PromptTemplate
21
+
22
+ # --- 1. Setup API Keys & Clients ---
23
+ GROQ_API_KEY = os.environ.get('GROQ_API_KEY')
24
+ SUPABASE_URL = os.environ.get("VITE_SUPABASE_URL")
25
+ SUPABASE_KEY = os.environ.get("VITE_SUPABASE_PUBLISHABLE_KEY")
26
+
27
+ supabase: Client | None = None
28
+ if SUPABASE_URL and SUPABASE_KEY:
29
+ supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
30
+ else:
31
+ print("⚠️ Supabase credentials not found. Real-time feedback disabled.")
32
+
33
+ app = FastAPI(title="Pangun ReliAI Backend", description="AI Backend for FMEA Recommendations")
34
+
35
+ # Allow CORS for local React development
36
+ app.add_middleware(
37
+ CORSMiddleware,
38
+ allow_origins=["*"],
39
+ allow_credentials=True,
40
+ allow_methods=["*"],
41
+ allow_headers=["*"],
42
+ )
43
+
44
+ # --- 2. Build the RAG Chain ---
45
+ FMEA_DATA_FILE = '10000fmea_data.csv'
46
+ QA_CHAIN = None
47
+ RETRIEVER = None
48
+ LLM = None
49
+ PROMPT = None
50
+ FMEA_DF = None
51
+ DOCUMENTS = None
52
+ embeddings = None
53
+ feedback_vector_store = None
54
+
55
+ DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'
56
+ print(f"✅ Using device: {DEVICE}")
57
+
58
+ def build_feedback_db():
59
+ global feedback_vector_store
60
+ if not supabase or not embeddings:
61
+ return
62
+ try:
63
+ # Fetch highly rated feedback from Supabase
64
+ response = supabase.table("fmea_feedback").select("action, rating").gte("rating", 7).execute()
65
+ if not response.data:
66
+ return
67
+
68
+ highly_rated_actions = [item['action'] for item in response.data if item.get('action')]
69
+
70
+ # Deduplicate
71
+ highly_rated_actions = list(set(highly_rated_actions))
72
+
73
+ if highly_rated_actions:
74
+ print(f"Found {len(highly_rated_actions)} highly-rated actions from Supabase. Building feedback DB...")
75
+ feedback_vector_store = FAISS.from_texts(highly_rated_actions, embeddings)
76
+ print("✅ Supabase Feedback vector store is ready.")
77
+ except Exception as e:
78
+ print(f"Failed to build feedback DB from Supabase: {e}")
79
+
80
+ def keyword_retrieve_documents(search_query: str, k: int = 2):
81
+ if FMEA_DF is None or DOCUMENTS is None or FMEA_DF.empty:
82
+ return []
83
+
84
+ tokens = [tok for tok in re.findall(r"[a-z0-9]+", str(search_query).lower()) if len(tok) >= 3]
85
+ if not tokens:
86
+ return DOCUMENTS[:k]
87
+
88
+ scores = []
89
+ for idx, text in enumerate(FMEA_DF["__search_text"]):
90
+ token_hits = sum(1 for tok in tokens if tok in text)
91
+ if token_hits:
92
+ scores.append((token_hits, idx))
93
+
94
+ if not scores:
95
+ return DOCUMENTS[:k]
96
+
97
+ scores.sort(key=lambda x: x[0], reverse=True)
98
+ top_indices = [idx for _, idx in scores[:k]]
99
+ return [DOCUMENTS[idx] for idx in top_indices]
100
+
101
+ def build_rag_chain():
102
+ global QA_CHAIN, RETRIEVER, LLM, PROMPT, FMEA_DF, DOCUMENTS, embeddings
103
+ try:
104
+ print(f"Loading FMEA data from {FMEA_DATA_FILE}...")
105
+ if not os.path.exists(FMEA_DATA_FILE):
106
+ print(f"⚠️ {FMEA_DATA_FILE} not found. Skipping document loading.")
107
+ else:
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
+
117
+ search_cols = [c for c in ["Failure_Mode", "Effect", "Cause", "Recommended_Action", "Responsible_Department"] if c in fmea_df.columns]
118
+ if len(search_cols) > 0:
119
+ fmea_df["__search_text"] = fmea_df[search_cols].astype(str).agg(" ".join, axis=1).str.lower()
120
+ else:
121
+ fmea_df["__search_text"] = ""
122
+
123
+ FMEA_DF = fmea_df
124
+ DOCUMENTS = documents
125
+ print(f"✅ Successfully loaded {len(documents)} records.")
126
+
127
+ print("Initializing local HuggingFace embedding model...")
128
+ try:
129
+ embeddings = HuggingFaceEmbeddings(
130
+ model_name='all-MiniLM-L6-v2',
131
+ model_kwargs={'device': DEVICE}
132
+ )
133
+ print("✅ Local embedding model loaded.")
134
+
135
+ print("Creating embeddings and building main FAISS vector store...")
136
+ main_vector_store = FAISS.from_documents(documents, embeddings)
137
+ RETRIEVER = main_vector_store.as_retriever(search_kwargs={"k": 2})
138
+ print("✅ Main vector store created successfully.")
139
+
140
+ # Build real-time feedback DB
141
+ build_feedback_db()
142
+ except Exception as embed_error:
143
+ embeddings = None
144
+ RETRIEVER = None
145
+ print(f"⚠️ Embedding setup failed, using keyword retrieval fallback. Details: {embed_error}")
146
+
147
+ if not GROQ_API_KEY:
148
+ print("⚠️ GROQ_API_KEY not found. AI requests will fail until set.")
149
+ else:
150
+ llm = ChatGroq(model_name="llama-3.3-70b-versatile", temperature=0.1, api_key=GROQ_API_KEY)
151
+
152
+ prompt_template = """
153
+ You are an expert FMEA analyst. Your task is to generate the TOP 3 recommended actions for the given failure.
154
+ The user has provided their current S, O, and D scores.
155
+ For EACH recommendation, you must estimate the revised S, O, and D scores (1-10) that would result *after* that action is successfully implemented.
156
+
157
+ - **new_S (Severity):** This score MUST usually stay the exact same as the original Severity. Do not lower it unless the action physically changes the design to mitigate the effect completely.
158
+ - **new_O (Occurrence):** This score MUST be lower than or equal to the original Occurrence.
159
+ - **new_D (Detection):** This score MUST be lower than or equal to the original Detection (as the action makes the failure easier to detect).
160
+
161
+ CONTEXT (Historical data and highly-rated user feedback):
162
+ {context}
163
+
164
+ QUESTION (The new failure and its current scores):
165
+ {question}
166
+
167
+ INSTRUCTIONS:
168
+ Format your entire response as a single, valid JSON object with a key "recommendations" which is a list of 3 objects.
169
+ Each object must have these exactly keys: "rank", "action", "action_details", "department", "ai_score", "new_S", "new_O", "new_D".
170
+
171
+ - "rank": The rank of the recommendation (1, 2, 3).
172
+ - "action": The most effective recommended action text. Focus on actions present in the highly-rated user feedback if applicable.
173
+ - "action_details": 2-3 sentences explaining why this action works and practical implementation notes.
174
+ - "department": The most likely responsible department.
175
+ - "ai_score": Confidence score (1-100) for this recommendation.
176
+ - "new_S": Your estimated new Severity score (1-10). Must be an integer.
177
+ - "new_O": Your estimated new Occurrence score (1-10). Must be an integer.
178
+ - "new_D": Your estimated new Detection score (1-10). Must be an integer.
179
+
180
+ CRITICAL: Output ONLY the raw JSON object. Do not calculate the RPN. Do not include markdown formatting like ```json or any introductory text.
181
+ """
182
+ PROMPT = PromptTemplate(template=prompt_template, input_variables=["context", "question"])
183
+
184
+ LLM = llm
185
+ QA_CHAIN = True
186
+ print("✅ RAG model is ready.")
187
+
188
+ return True
189
+ except Exception as e:
190
+ print(f"🔴 An error occurred during RAG setup: {e}")
191
+ return False
192
+
193
+ # Initialize RAG on startup
194
+ build_rag_chain()
195
+
196
+ class FMEARequest(BaseModel):
197
+ mode: str
198
+ effect: str
199
+ cause: str
200
+ severity: int
201
+ occurrence: int
202
+ detection: int
203
+
204
+ @app.post("/api/recommend")
205
+ async def get_recommendations(req: FMEARequest):
206
+ if QA_CHAIN is None or LLM is None or PROMPT is None:
207
+ raise HTTPException(status_code=500, detail="AI Model is not initialized or GROQ_API_KEY is missing.")
208
+
209
+ # Refresh feedback DB on every request to ensure real-time learning
210
+ build_feedback_db()
211
+
212
+ query = (
213
+ f"For a failure with Failure Mode='{req.mode}', Effect='{req.effect}', and Cause='{req.cause}', "
214
+ f"what are the top 3 most appropriate recommended actions? "
215
+ f"The current scores are: Severity={req.severity}, Occurrence={req.occurrence}, Detection={req.detection}."
216
+ )
217
+
218
+ if RETRIEVER is not None:
219
+ docs = RETRIEVER.invoke(query)
220
+ else:
221
+ docs = keyword_retrieve_documents(f"{req.mode} {req.effect} {req.cause}", k=2)
222
+
223
+ context_from_history = "\n---\n".join([doc.page_content for doc in docs])
224
+
225
+ context_from_feedback = ""
226
+ if feedback_vector_store:
227
+ feedback_docs = feedback_vector_store.similarity_search(query, k=3)
228
+ if feedback_docs:
229
+ feedback_actions = "\n".join([doc.page_content for doc in feedback_docs])
230
+ context_from_feedback = f"\n\n--- Highly-Rated Actions from Real-Time User Feedback Database ---\n{feedback_actions}"
231
+
232
+ combined_context = f"--- Historical FMEA Entries ---\n{context_from_history}{context_from_feedback}"
233
+
234
+ try:
235
+ llm_input = PROMPT.format(context=combined_context, question=query)
236
+ llm_response = LLM.invoke(llm_input)
237
+
238
+ raw_output = str(getattr(llm_response, "content", llm_response)).strip()
239
+ match = re.search(r'\{.*\}', raw_output, re.DOTALL)
240
+ if match:
241
+ json_text = match.group(0)
242
+ else:
243
+ json_text = raw_output.replace("```json", "").replace("```", "").strip()
244
+
245
+ data = json.loads(json_text)
246
+
247
+ # Ensure correct types and math (Deterministic RPN Logic)
248
+ for r in data.get('recommendations', []):
249
+ if 'action_details' not in r:
250
+ r['action_details'] = "No additional details provided."
251
+ # Force types to integers
252
+ r['new_S'] = int(r.get('new_S', req.severity))
253
+ r['new_O'] = int(r.get('new_O', req.occurrence))
254
+ r['new_D'] = int(r.get('new_D', req.detection))
255
+ # Deterministic math done by Python
256
+ r['new_RPN'] = r['new_S'] * r['new_O'] * r['new_D']
257
+
258
+ return {"recommendations": data.get("recommendations", [])}
259
+
260
+ except Exception as e:
261
+ print(f"Error parsing LLM output: {e}\nRaw Output was: {raw_output if 'raw_output' in locals() else 'None'}")
262
+ raise HTTPException(status_code=500, detail=f"Could not parse AI response: {str(e)}")
263
+
264
+ @app.get("/health")
265
+ def health_check():
266
+ return {"status": "ok", "message": "Pangun ReliAI Backend is running."}
267
+
268
+ if __name__ == "__main__":
269
+ import uvicorn
270
+ uvicorn.run("backend_api:app", host="0.0.0.0", port=8000, reload=True)
requirements.txt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi>=0.110.0
2
+ uvicorn>=0.27.1
3
+ pydantic>=2.6.4
4
+ python-dotenv>=1.0.1
5
+ pandas>=2.0.0
6
+ torch>=2.0.0
7
+ faiss-cpu>=1.7.4
8
+ langchain>=0.1.13
9
+ langchain-groq>=0.0.1
10
+ langchain-community>=0.0.29
11
+ langchain-huggingface>=0.0.1
12
+ sentence-transformers>=2.2.2
13
+ supabase>=2.4.1
14
+ httpx>=0.27.0