Spaces:
Sleeping
Sleeping
| from fastapi import APIRouter, Depends, HTTPException, UploadFile, File | |
| from sqlalchemy.orm import Session | |
| from typing import List | |
| from app.database import get_db | |
| from app.models import UploadedRFP, Question | |
| from app.schemas import ProcessQuestionRequest, AIAnswerResponse, SimilarQuestion | |
| from app.services.pdf_service import pdf_service | |
| from app.services.similarity_service import similarity_service | |
| from app.services.groq_service import groq_service | |
| router = APIRouter(prefix="/api/rfp-processing", tags=["RFP Processing"]) | |
| async def upload_rfp(file: UploadFile = File(...), db: Session = Depends(get_db)): | |
| """Upload a new RFP PDF and extract text from first 3 pages""" | |
| if not file.filename.endswith('.pdf'): | |
| raise HTTPException(status_code=400, detail="Only PDF files are allowed") | |
| uploaded_rfp = await pdf_service.save_and_extract_pdf(file, db, extract_pages=3) | |
| return { | |
| "id": uploaded_rfp.id, | |
| "filename": uploaded_rfp.filename, | |
| "uploaded_at": uploaded_rfp.uploaded_at, | |
| "text_preview": uploaded_rfp.text_content[:500] + "..." if len(uploaded_rfp.text_content) > 500 else uploaded_rfp.text_content | |
| } | |
| def list_uploaded_rfps(skip: int = 0, limit: int = 50, db: Session = Depends(get_db)): | |
| """List all uploaded RFPs""" | |
| rfps = db.query(UploadedRFP).offset(skip).limit(limit).all() | |
| return [ | |
| { | |
| "id": rfp.id, | |
| "filename": rfp.filename, | |
| "rfp_name": rfp.rfp_name if hasattr(rfp, 'rfp_name') and rfp.rfp_name else rfp.filename.rsplit('.', 1)[0], | |
| "uploaded_at": rfp.uploaded_at | |
| } | |
| for rfp in rfps | |
| ] | |
| def delete_uploaded_rfp(rfp_id: int, db: Session = Depends(get_db)): | |
| """Delete an uploaded RFP""" | |
| uploaded_rfp = db.query(UploadedRFP).filter(UploadedRFP.id == rfp_id).first() | |
| if not uploaded_rfp: | |
| raise HTTPException(status_code=404, detail="Uploaded RFP not found") | |
| # Delete the file from disk if it exists | |
| import os | |
| if uploaded_rfp.file_path and os.path.exists(uploaded_rfp.file_path): | |
| try: | |
| os.remove(uploaded_rfp.file_path) | |
| except Exception as e: | |
| print(f"Error deleting file: {e}") | |
| db.delete(uploaded_rfp) | |
| db.commit() | |
| return {"message": "Uploaded RFP deleted successfully"} | |
| async def process_question(request: ProcessQuestionRequest, db: Session = Depends(get_db)): | |
| """ | |
| Process a question against a new RFP: | |
| 1. Find similar questions from database | |
| 2. Get new RFP content (if provided) | |
| 3. Generate AI answer combining both | |
| 4. Provide gap analysis | |
| """ | |
| # Get uploaded RFP (optional) | |
| uploaded_rfp = None | |
| new_rfp_content = "" | |
| if request.uploaded_rfp_id: | |
| uploaded_rfp = db.query(UploadedRFP).filter( | |
| UploadedRFP.id == request.uploaded_rfp_id | |
| ).first() | |
| if not uploaded_rfp: | |
| raise HTTPException(status_code=404, detail="Uploaded RFP not found") | |
| new_rfp_content = uploaded_rfp.text_content | |
| # Check for exact match first | |
| exact_match = similarity_service.check_exact_match(db, request.question_text) | |
| if exact_match: | |
| # Return exact match answer | |
| return AIAnswerResponse( | |
| ai_answer=exact_match.answer_text, | |
| gap_analysis="Exact match found in database. This is the stored answer from previous RFP." if not new_rfp_content else "Exact match found. No new information in current RFP.", | |
| similar_questions=[ | |
| SimilarQuestion( | |
| question_id=exact_match.id, | |
| question_text=exact_match.question_text, | |
| answer_text=exact_match.answer_text, | |
| page_start=exact_match.page_start, | |
| page_end=exact_match.page_end, | |
| rfp_title=exact_match.rfp.title, | |
| similarity_score=1.0 | |
| ) | |
| ], | |
| new_rfp_content=new_rfp_content[:1000] + "..." if new_rfp_content else "No new RFP uploaded" | |
| ) | |
| # Find similar questions - get more matches for AI to consider | |
| similar_questions = similarity_service.find_similar_questions( | |
| db, | |
| request.question_text, | |
| top_k=10, # Get more answers for AI to reference | |
| threshold=0.15 # Lower threshold to include more potential matches | |
| ) | |
| # Format similar questions for response | |
| similar_questions_list = [ | |
| SimilarQuestion( | |
| question_id=q.id, | |
| question_text=q.question_text, | |
| answer_text=q.answer_text, | |
| page_start=q.page_start, | |
| page_end=q.page_end, | |
| rfp_title=q.rfp.title, | |
| similarity_score=score | |
| ) | |
| for q, score in similar_questions | |
| ] | |
| print(f"Found {len(similar_questions_list)} similar questions") | |
| for sq in similar_questions_list: | |
| print(f" - {sq.question_text} (score: {sq.similarity_score:.2f})") | |
| # Prepare previous answers for AI | |
| previous_answers = [ | |
| { | |
| "question_text": q.question_text, | |
| "answer_text": q.answer_text, | |
| "page_start": q.page_start, | |
| "page_end": q.page_end, | |
| "rfp_title": q.rfp.title, | |
| "similarity_score": score | |
| } | |
| for q, score in similar_questions | |
| ] | |
| # Generate AI answer with HuggingFace | |
| prompt = f"""Question: {request.question_text} | |
| New RFP Content: | |
| {new_rfp_content if new_rfp_content else "No new RFP content provided"} | |
| Previous Similar Answers: | |
| {chr(10).join([f"- {ans['answer_text']} (from {ans['rfp_title']}, score: {ans['similarity_score']:.2f})" for ans in previous_answers[:5]])} | |
| {request.additional_context if request.additional_context else ""} | |
| Provide a comprehensive answer and gap analysis.""" | |
| ai_answer = groq_service.generate_chat_response(prompt) | |
| ai_response = { | |
| 'ai_answer': ai_answer, | |
| 'gap_analysis': "Generated using HuggingFace Mistral-7B model based on available context." | |
| } | |
| return AIAnswerResponse( | |
| ai_answer=ai_response['ai_answer'], | |
| gap_analysis=ai_response['gap_analysis'], | |
| similar_questions=similar_questions_list, | |
| new_rfp_content=new_rfp_content[:1000] + "..." if new_rfp_content else "No new RFP uploaded" | |
| ) | |
| def get_rfp_content(rfp_id: int, db: Session = Depends(get_db)): | |
| """Get the full extracted content of an uploaded RFP""" | |
| uploaded_rfp = db.query(UploadedRFP).filter(UploadedRFP.id == rfp_id).first() | |
| if not uploaded_rfp: | |
| raise HTTPException(status_code=404, detail="Uploaded RFP not found") | |
| return { | |
| "id": uploaded_rfp.id, | |
| "filename": uploaded_rfp.filename, | |
| "content": uploaded_rfp.text_content | |
| } | |