mohamedhassan22 commited on
Commit
ef5aecf
·
verified ·
1 Parent(s): 7ed39ed

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +195 -4
app.py CHANGED
@@ -3,20 +3,36 @@ FastAPI Application for Multimodal RAG System
3
  US Army Medical Research Papers Q&A
4
  """
5
 
 
 
 
 
 
6
  import os
 
7
  import logging
8
  from typing import List, Dict, Optional, Union
9
  from contextlib import asynccontextmanager
 
10
 
11
  from fastapi import FastAPI, HTTPException
12
  from fastapi.middleware.cors import CORSMiddleware
13
- from fastapi.responses import FileResponse
14
  from fastapi.staticfiles import StaticFiles
15
  from pydantic import BaseModel, Field
16
 
17
  # Import from query_index (standalone)
18
  from query_index import MultimodalRAGSystem
19
 
 
 
 
 
 
 
 
 
 
20
  # Configure logging
21
  logging.basicConfig(
22
  level=logging.INFO,
@@ -87,7 +103,7 @@ class ImageSource(BaseModel):
87
  path: Optional[str]
88
  filename: Optional[str]
89
  score: Optional[float]
90
- page: Optional[Union[str, int]] # could be int or str depending on metadata
91
  file: Optional[str]
92
  link: Optional[str] = None
93
 
@@ -108,6 +124,16 @@ class HealthResponse(BaseModel):
108
  status: str
109
  rag_initialized: bool
110
 
 
 
 
 
 
 
 
 
 
 
111
  # API Endpoints
112
 
113
  @app.get("/", tags=["Root"])
@@ -132,7 +158,7 @@ async def query_rag(request: QueryRequest):
132
 
133
  try:
134
  result = rag_system.ask(
135
- query_str=request.question, # Fixed: changed from 'question=' to 'query_str='
136
  chat_history=chat_history
137
  )
138
 
@@ -155,7 +181,172 @@ async def query_rag(request: QueryRequest):
155
  logger.error(f"Error processing query: {str(e)}")
156
  raise HTTPException(status_code=500, detail=str(e))
157
 
158
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
  if __name__ == "__main__":
161
  import uvicorn
 
3
  US Army Medical Research Papers Q&A
4
  """
5
 
6
+ """
7
+ FastAPI Application for Multimodal RAG System
8
+ US Army Medical Research Papers Q&A
9
+ """
10
+
11
  import os
12
+ import io
13
  import logging
14
  from typing import List, Dict, Optional, Union
15
  from contextlib import asynccontextmanager
16
+ from datetime import datetime
17
 
18
  from fastapi import FastAPI, HTTPException
19
  from fastapi.middleware.cors import CORSMiddleware
20
+ from fastapi.responses import FileResponse, StreamingResponse
21
  from fastapi.staticfiles import StaticFiles
22
  from pydantic import BaseModel, Field
23
 
24
  # Import from query_index (standalone)
25
  from query_index import MultimodalRAGSystem
26
 
27
+ # PDF generation libraries
28
+ from reportlab.lib.pagesizes import letter
29
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
30
+ from reportlab.lib.units import inch
31
+ from reportlab.lib.enums import TA_CENTER, TA_JUSTIFY
32
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak, Image as RLImage, Table, TableStyle
33
+ from reportlab.lib import colors
34
+ import requests
35
+
36
  # Configure logging
37
  logging.basicConfig(
38
  level=logging.INFO,
 
103
  path: Optional[str]
104
  filename: Optional[str]
105
  score: Optional[float]
106
+ page: Optional[Union[str, int]]
107
  file: Optional[str]
108
  link: Optional[str] = None
109
 
 
124
  status: str
125
  rag_initialized: bool
126
 
127
+ class ConversationItem(BaseModel):
128
+ question: str
129
+ answer: str
130
+ images: List[ImageSource]
131
+ texts: List[TextSource]
132
+ timestamp: str
133
+
134
+ class ReportRequest(BaseModel):
135
+ conversations: List[ConversationItem]
136
+
137
  # API Endpoints
138
 
139
  @app.get("/", tags=["Root"])
 
158
 
159
  try:
160
  result = rag_system.ask(
161
+ query_str=request.question,
162
  chat_history=chat_history
163
  )
164
 
 
181
  logger.error(f"Error processing query: {str(e)}")
182
  raise HTTPException(status_code=500, detail=str(e))
183
 
184
+ @app.post("/generate-report", tags=["Report"])
185
+ async def generate_report(request: ReportRequest):
186
+ """Generate a PDF report from conversation data"""
187
+ try:
188
+ buffer = io.BytesIO()
189
+
190
+ # Create PDF document
191
+ doc = SimpleDocTemplate(
192
+ buffer,
193
+ pagesize=letter,
194
+ rightMargin=0.75*inch,
195
+ leftMargin=0.75*inch,
196
+ topMargin=1*inch,
197
+ bottomMargin=0.75*inch
198
+ )
199
+
200
+ # Container for the 'Flowable' objects
201
+ story = []
202
+
203
+ # Define styles
204
+ styles = getSampleStyleSheet()
205
+
206
+ # Custom styles
207
+ title_style = ParagraphStyle(
208
+ 'CustomTitle',
209
+ parent=styles['Heading1'],
210
+ fontSize=24,
211
+ textColor=colors.HexColor('#3b82f6'),
212
+ spaceAfter=12,
213
+ alignment=TA_CENTER,
214
+ fontName='Helvetica-Bold'
215
+ )
216
+
217
+ subtitle_style = ParagraphStyle(
218
+ 'CustomSubtitle',
219
+ parent=styles['Normal'],
220
+ fontSize=12,
221
+ textColor=colors.HexColor('#64748b'),
222
+ spaceAfter=20,
223
+ alignment=TA_CENTER
224
+ )
225
+
226
+ question_style = ParagraphStyle(
227
+ 'QuestionStyle',
228
+ parent=styles['Heading2'],
229
+ fontSize=14,
230
+ textColor=colors.HexColor('#3b82f6'),
231
+ spaceAfter=10,
232
+ fontName='Helvetica-Bold'
233
+ )
234
+
235
+ answer_style = ParagraphStyle(
236
+ 'AnswerStyle',
237
+ parent=styles['Normal'],
238
+ fontSize=11,
239
+ alignment=TA_JUSTIFY,
240
+ spaceAfter=12
241
+ )
242
+
243
+ source_title_style = ParagraphStyle(
244
+ 'SourceTitle',
245
+ parent=styles['Heading3'],
246
+ fontSize=11,
247
+ textColor=colors.HexColor('#8b5cf6'),
248
+ spaceAfter=6,
249
+ fontName='Helvetica-Bold'
250
+ )
251
+
252
+ # Add title
253
+ story.append(Paragraph("WHEC Research Assistant", title_style))
254
+ story.append(Paragraph("Conversation Report", title_style))
255
+ story.append(Spacer(1, 0.2*inch))
256
+
257
+ # Add metadata
258
+ current_time = datetime.now().strftime("%B %d, %Y at %I:%M %p")
259
+ story.append(Paragraph(f"Generated: {current_time}", subtitle_style))
260
+ story.append(Paragraph("Source: WHEC (Warrior Heat- and Exertion-Related Events Collaborative)", subtitle_style))
261
+ story.append(Paragraph(f"Total Questions: {len(request.conversations)}", subtitle_style))
262
+
263
+ story.append(Spacer(1, 0.3*inch))
264
+
265
+ # Add horizontal line
266
+ story.append(Spacer(1, 0.1*inch))
267
+
268
+ # Process each conversation
269
+ for idx, conv in enumerate(request.conversations, 1):
270
+ # Question
271
+ story.append(Paragraph(f"Question {idx}", question_style))
272
+ story.append(Paragraph(conv.question, answer_style))
273
+ story.append(Spacer(1, 0.15*inch))
274
+
275
+ # Answer
276
+ story.append(Paragraph("Answer", source_title_style))
277
+ story.append(Paragraph(conv.answer, answer_style))
278
+ story.append(Spacer(1, 0.15*inch))
279
+
280
+ # Text Sources
281
+ if conv.texts:
282
+ story.append(Paragraph("Referenced Text Sources", source_title_style))
283
+ for i, txt in enumerate(conv.texts, 1):
284
+ source_text = f"[{i}] {txt.file or 'Unknown Document'} (Page {txt.page or 'N/A'}, {round((txt.score or 0) * 100)}% match)"
285
+ story.append(Paragraph(source_text, styles['Normal']))
286
+
287
+ excerpt = f'<i>"{txt.text[:300]}..."</i>'
288
+ story.append(Paragraph(excerpt, styles['Normal']))
289
+ story.append(Spacer(1, 0.1*inch))
290
+
291
+ story.append(Spacer(1, 0.1*inch))
292
+
293
+ # Image Sources
294
+ if conv.images:
295
+ relevant_images = [img for img in conv.images if (img.score or 0) >= 0.3]
296
+ if relevant_images:
297
+ story.append(Paragraph("Referenced Images", source_title_style))
298
+
299
+ for i, img in enumerate(relevant_images[:3], 1): # Limit to 3 images
300
+ # Add image metadata
301
+ img_text = f"[{i}] {img.filename or 'Unknown'} from {img.file or 'Unknown Document'} (Page {img.page or 'N/A'}, {round((img.score or 0) * 100)}% match)"
302
+ story.append(Paragraph(img_text, styles['Normal']))
303
+
304
+ # Try to add the actual image
305
+ if img.path:
306
+ try:
307
+ # Construct the full path to the image
308
+ img_path = img.path.replace('/extracted_images/', '')
309
+ full_img_path = os.path.join('extracted_images', img_path)
310
+
311
+ if os.path.exists(full_img_path):
312
+ # Add image with max width of 4 inches
313
+ rl_img = RLImage(full_img_path, width=4*inch, height=3*inch, kind='proportional')
314
+ story.append(rl_img)
315
+ story.append(Spacer(1, 0.1*inch))
316
+ except Exception as e:
317
+ logger.warning(f"Could not add image to PDF: {e}")
318
+
319
+ story.append(Spacer(1, 0.15*inch))
320
+
321
+ # Add separator between conversations (except after last one)
322
+ if idx < len(request.conversations):
323
+ story.append(Spacer(1, 0.2*inch))
324
+ # Add a horizontal line as separator
325
+ line_table = Table([['']], colWidths=[6.5*inch])
326
+ line_table.setStyle(TableStyle([
327
+ ('LINEABOVE', (0, 0), (-1, 0), 1, colors.HexColor('#334155')),
328
+ ]))
329
+ story.append(line_table)
330
+ story.append(Spacer(1, 0.2*inch))
331
+
332
+ # Build PDF
333
+ doc.build(story)
334
+
335
+ # Get PDF from buffer
336
+ buffer.seek(0)
337
+
338
+ # Return PDF as download
339
+ return StreamingResponse(
340
+ buffer,
341
+ media_type="application/pdf",
342
+ headers={
343
+ "Content-Disposition": f"attachment; filename=WHEC_Report_{datetime.now().strftime('%Y-%m-%d')}.pdf"
344
+ }
345
+ )
346
+
347
+ except Exception as e:
348
+ logger.error(f"Error generating PDF report: {str(e)}")
349
+ raise HTTPException(status_code=500, detail=f"Error generating report: {str(e)}")
350
 
351
  if __name__ == "__main__":
352
  import uvicorn