main.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import os
  2. import uuid
  3. import json
  4. from datetime import datetime
  5. from fastapi import FastAPI, UploadFile, File, Header, HTTPException, status, Form, Depends
  6. from fastapi.middleware.cors import CORSMiddleware
  7. from typing import Optional, List
  8. from sqlalchemy.orm import Session
  9. from backend.services.openai_service import extract_receipt_data, fill_form_with_template_v2
  10. from backend.schemas import ExtractionResponse, ClaimRecord, UserAccount, ClaimSubmission, V2TemplateResponse
  11. from . import crud, models, schemas
  12. from .database import SessionLocal, engine
  13. from dotenv import load_dotenv
  14. load_dotenv()
  15. # Create tables
  16. models.Base.metadata.create_all(bind=engine)
  17. app = FastAPI(title="AI-Assisted Data Entry API")
  18. # Configure CORS
  19. app.add_middleware(
  20. CORSMiddleware,
  21. allow_origins=["*"],
  22. allow_credentials=True,
  23. allow_methods=["*"],
  24. allow_headers=["*"],
  25. )
  26. # Dependency
  27. def get_db():
  28. db = SessionLocal()
  29. try:
  30. yield db
  31. finally:
  32. db.close()
  33. # Startup event for seeding
  34. @app.on_event("startup")
  35. def startup_event():
  36. db = SessionLocal()
  37. try:
  38. crud.seed_demo_user(db)
  39. finally:
  40. db.close()
  41. @app.get("/health")
  42. async def health_check():
  43. return {"status": "healthy"}
  44. @app.get("/api/v1/users", response_model=List[UserAccount])
  45. async def get_users(db: Session = Depends(get_db)):
  46. db_users = crud.get_users(db)
  47. return [
  48. UserAccount(
  49. id=u.id,
  50. name=u.name,
  51. department=u.department,
  52. medical_allowance=u.medical_allowance
  53. ) for u in db_users
  54. ]
  55. @app.post("/api/v1/users", response_model=UserAccount)
  56. async def create_user(user: UserAccount, db: Session = Depends(get_db)):
  57. db_user = crud.create_user(db, user)
  58. return UserAccount(
  59. id=db_user.id,
  60. name=db_user.name,
  61. department=db_user.department,
  62. medical_allowance=db_user.medical_allowance
  63. )
  64. @app.post("/api/v1/extract", response_model=ExtractionResponse)
  65. async def extract_receipt(
  66. file: UploadFile = File(...),
  67. user_name: str = Form("Demo User"),
  68. department: str = Form("Operations")
  69. ):
  70. if not file.content_type.startswith("image/"):
  71. raise HTTPException(
  72. status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
  73. detail="File provided is not an image."
  74. )
  75. try:
  76. content = await file.read()
  77. extraction_result = await extract_receipt_data(content, user_name, department)
  78. if extraction_result is None:
  79. raise HTTPException(
  80. status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
  81. detail="Could not extract data from the provided image."
  82. )
  83. return extraction_result
  84. except Exception as e:
  85. raise HTTPException(
  86. status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
  87. detail=f"An error occurred during extraction: {str(e)}"
  88. )
  89. @app.post("/api/v2/fill-template", response_model=V2TemplateResponse)
  90. async def fill_template(
  91. file: UploadFile = File(...),
  92. template_fields: str = Form(...),
  93. user_name: str = Form("Demo User"),
  94. department: str = Form("Operations")
  95. ):
  96. if not file.content_type.startswith("image/"):
  97. raise HTTPException(
  98. status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
  99. detail="File provided is not an image."
  100. )
  101. try:
  102. content = await file.read()
  103. template_dict = json.loads(template_fields)
  104. result = await fill_form_with_template_v2(
  105. content,
  106. template_dict,
  107. user_name,
  108. department
  109. )
  110. return result
  111. except Exception as e:
  112. raise HTTPException(
  113. status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
  114. detail=f"An error occurred during template filling: {str(e)}"
  115. )
  116. @app.post("/api/v1/claims", response_model=ClaimRecord)
  117. async def submit_claim(
  118. submission_data: ClaimSubmission,
  119. user_id: str = Header(...),
  120. db: Session = Depends(get_db)
  121. ):
  122. # crud operation handles user lookup and allowance update
  123. db_claim = crud.create_claim(db, submission_data, user_id)
  124. if not db_claim:
  125. raise HTTPException(status_code=404, detail="User not found")
  126. return crud.to_pydantic_claim(db_claim)
  127. @app.delete("/api/v1/claims/{claim_id}")
  128. async def delete_claim(claim_id: str, db: Session = Depends(get_db)):
  129. success = crud.delete_claim(db, claim_id)
  130. if not success:
  131. raise HTTPException(status_code=404, detail="Claim not found")
  132. return {"status": "success", "message": "Claim deleted and allowance refunded"}
  133. @app.get("/api/v1/claims", response_model=List[ClaimRecord])
  134. async def get_claims(db: Session = Depends(get_db)):
  135. db_claims = crud.get_claims(db)
  136. return [crud.to_pydantic_claim(c) for c in db_claims]
  137. if __name__ == "__main__":
  138. import uvicorn
  139. uvicorn.run(app, host="0.0.0.0", port=8000)