import os import uuid import json from datetime import datetime from fastapi import FastAPI, UploadFile, File, Header, HTTPException, status, Form, Depends from fastapi.middleware.cors import CORSMiddleware from typing import Optional, List from sqlalchemy.orm import Session from backend.services.openai_service import extract_receipt_data, fill_form_with_template_v2 from backend.schemas import ExtractionResponse, ClaimRecord, UserAccount, ClaimSubmission, V2TemplateResponse from . import crud, models, schemas from .database import SessionLocal, engine from dotenv import load_dotenv load_dotenv() # Create tables models.Base.metadata.create_all(bind=engine) app = FastAPI(title="AI-Assisted Data Entry API") # Configure CORS app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Dependency def get_db(): db = SessionLocal() try: yield db finally: db.close() # Startup event for seeding @app.on_event("startup") def startup_event(): db = SessionLocal() try: crud.seed_demo_user(db) finally: db.close() @app.get("/health") async def health_check(): return {"status": "healthy"} @app.get("/api/v1/users", response_model=List[UserAccount]) async def get_users(db: Session = Depends(get_db)): db_users = crud.get_users(db) return [ UserAccount( id=u.id, name=u.name, department=u.department, medical_allowance=u.medical_allowance ) for u in db_users ] @app.post("/api/v1/users", response_model=UserAccount) async def create_user(user: UserAccount, db: Session = Depends(get_db)): db_user = crud.create_user(db, user) return UserAccount( id=db_user.id, name=db_user.name, department=db_user.department, medical_allowance=db_user.medical_allowance ) @app.post("/api/v1/extract", response_model=ExtractionResponse) async def extract_receipt( file: UploadFile = File(...), user_name: str = Form("Demo User"), department: str = Form("Operations") ): allowed_types = ["image/jpeg", "image/png", "application/pdf"] if file.content_type not in allowed_types: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Unsupported file type. Please upload a JPEG, PNG, or PDF." ) try: content = await file.read() extraction_result = await extract_receipt_data(content, file.content_type, user_name, department) if extraction_result is None: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Could not extract data from the provided image." ) return extraction_result except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"An error occurred during extraction: {str(e)}" ) @app.post("/api/v2/fill-template", response_model=V2TemplateResponse) async def fill_template( file: UploadFile = File(...), template_fields: str = Form(...), user_name: str = Form("Demo User"), department: str = Form("Operations") ): allowed_types = ["image/jpeg", "image/png", "application/pdf"] if file.content_type not in allowed_types: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Unsupported file type. Please upload a JPEG, PNG, or PDF." ) try: content = await file.read() template_dict = json.loads(template_fields) result = await fill_form_with_template_v2( content, file.content_type, template_dict, user_name, department ) return result except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"An error occurred during template filling: {str(e)}" ) @app.post("/api/v1/claims", response_model=ClaimRecord) async def submit_claim( submission_data: ClaimSubmission, user_id: str = Header(...), db: Session = Depends(get_db) ): # crud operation handles user lookup and allowance update db_claim = crud.create_claim(db, submission_data, user_id) if not db_claim: raise HTTPException(status_code=404, detail="User not found") return crud.to_pydantic_claim(db_claim) @app.delete("/api/v1/claims/{claim_id}") async def delete_claim(claim_id: str, db: Session = Depends(get_db)): success = crud.delete_claim(db, claim_id) if not success: raise HTTPException(status_code=404, detail="Claim not found") return {"status": "success", "message": "Claim deleted and allowance refunded"} @app.get("/api/v1/claims", response_model=List[ClaimRecord]) async def get_claims(db: Session = Depends(get_db)): db_claims = crud.get_claims(db) return [crud.to_pydantic_claim(c) for c in db_claims] if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)