Sibi Krishnamoorthy commited on
Commit
c9ed90a
·
1 Parent(s): d5b86e1

Add application file

Browse files
.dockerignore ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.pyd
5
+ venv/
6
+ .env
7
+ .git/
8
+ .gitignore
9
+ .DS_Store
10
+ *.pdf
11
+ *.zip
12
+ data/
.gitignore ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ data/
2
+ dep/
3
+ # Byte-compiled / optimized / DLL files
4
+ __pycache__/
5
+ *.py[cod]
6
+ *$py.class
7
+
8
+ # C extensions
9
+ *.so
10
+
11
+ # Distribution / packaging
12
+ .Python
13
+ build/
14
+ develop-eggs/
15
+ dist/
16
+ downloads/
17
+ eggs/
18
+ .eggs/
19
+ lib/
20
+ lib64/
21
+ parts/
22
+ sdist/
23
+ var/
24
+ wheels/
25
+ share/python-wheels/
26
+ *.egg-info/
27
+ .installed.cfg
28
+ *.egg
29
+ MANIFEST
30
+
31
+ # PyInstaller
32
+ # Usually these files are written by a python script from a template
33
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
34
+ *.manifest
35
+ *.spec
36
+
37
+ # Installer logs
38
+ pip-log.txt
39
+ pip-delete-this-directory.txt
40
+
41
+ # Unit test / coverage reports
42
+ htmlcov/
43
+ .tox/
44
+ .nox/
45
+ .coverage
46
+ .coverage.*
47
+ .cache
48
+ nosetests.xml
49
+ coverage.xml
50
+ *.cover
51
+ *.py,cover
52
+ .hypothesis/
53
+ .pytest_cache/
54
+ cover/
55
+
56
+ # Translations
57
+ *.mo
58
+ *.pot
59
+
60
+ # Django stuff:
61
+ *.log
62
+ local_settings.py
63
+ db.sqlite3
64
+ db.sqlite3-journal
65
+
66
+ # Flask stuff:
67
+ instance/
68
+ .webassets-cache
69
+
70
+ # Scrapy stuff:
71
+ .scrapy
72
+
73
+ # Sphinx documentation
74
+ docs/_build/
75
+
76
+ # PyBuilder
77
+ .pybuilder/
78
+ target/
79
+
80
+ # Jupyter Notebook
81
+ .ipynb_checkpoints
82
+
83
+ # IPython
84
+ profile_default/
85
+ ipython_config.py
86
+
87
+ # pyenv
88
+ # For a library or package, you might want to ignore these files since the code is
89
+ # intended to run in multiple environments; otherwise, check them in:
90
+ # .python-version
91
+
92
+ # pipenv
93
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
94
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
95
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
96
+ # install all needed dependencies.
97
+ #Pipfile.lock
98
+
99
+ # UV
100
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
101
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
102
+ # commonly ignored for libraries.
103
+ #uv.lock
104
+
105
+ # poetry
106
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
107
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
108
+ # commonly ignored for libraries.
109
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
110
+ #poetry.lock
111
+
112
+ # pdm
113
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
114
+ #pdm.lock
115
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
116
+ # in version control.
117
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
118
+ .pdm.toml
119
+ .pdm-python
120
+ .pdm-build/
121
+
122
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
123
+ __pypackages__/
124
+
125
+ # Celery stuff
126
+ celerybeat-schedule
127
+ celerybeat.pid
128
+
129
+ # SageMath parsed files
130
+ *.sage.py
131
+
132
+ # Environments
133
+ .env
134
+ .venv
135
+ env/
136
+ venv/
137
+ ENV/
138
+ env.bak/
139
+ venv.bak/
140
+
141
+ # Spyder project settings
142
+ .spyderproject
143
+ .spyproject
144
+
145
+ # Rope project settings
146
+ .ropeproject
147
+
148
+ # mkdocs documentation
149
+ /site
150
+
151
+ # mypy
152
+ .mypy_cache/
153
+ .dmypy.json
154
+ dmypy.json
155
+
156
+ # Pyre type checker
157
+ .pyre/
158
+
159
+ # pytype static type analyzer
160
+ .pytype/
161
+
162
+ # Cython debug symbols
163
+ cython_debug/
164
+
165
+ # PyCharm
166
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
167
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
168
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
169
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
170
+ #.idea/
171
+
172
+ # PyPI configuration file
173
+ .pypirc
.python-version ADDED
@@ -0,0 +1 @@
 
 
1
+ 3.13
Dockerfile ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use official Python image
2
+ FROM python:3.11-slim
3
+
4
+ # Set environment variables
5
+ ENV PYTHONDONTWRITEBYTECODE=1
6
+ ENV PYTHONUNBUFFERED=1
7
+
8
+ # Set work directory
9
+ WORKDIR /app
10
+
11
+ # Install system dependencies
12
+ RUN apt-get update && apt-get install -y \
13
+ build-essential \
14
+ && rm -rf /var/lib/apt/lists/*
15
+
16
+ # Install Python dependencies
17
+ COPY requirements.txt .
18
+ RUN pip install --upgrade pip && pip install -r requirements.txt
19
+
20
+ # Copy project
21
+ COPY . .
22
+
23
+ # Expose port
24
+ EXPOSE 8000
25
+
26
+ # Run the application
27
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
app/api/models/chat.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+ from typing import List, Optional
3
+ from datetime import datetime
4
+
5
+ class ChatMessage(BaseModel):
6
+ role: str = Field(..., description="Role of the message sender (user/assistant)")
7
+ content: str = Field(..., description="Content of the message")
8
+ timestamp: datetime = Field(default_factory=datetime.utcnow)
9
+
10
+ class ChatRequest(BaseModel):
11
+ query: str = Field(..., description="User's query about invoice reimbursements")
12
+ chat_history: Optional[List[ChatMessage]] = Field(default=None, description="Previous chat messages for context")
13
+
14
+ class ChatResponse(BaseModel):
15
+ response: str = Field(..., description="Assistant's response in markdown format")
16
+ relevant_invoices: Optional[List[str]] = Field(default=None, description="List of relevant invoice IDs referenced in the response")
17
+ chat_history: List[ChatMessage] = Field(..., description="Updated chat history including the new exchange")
app/api/models/invoice.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+ from typing import List, Optional
3
+ from enum import Enum
4
+ from datetime import datetime
5
+
6
+ class ReimbursementStatus(str, Enum):
7
+ FULLY_REIMBURSED = "FULLY_REIMBURSED"
8
+ PARTIALLY_REIMBURSED = "PARTIALLY_REIMBURSED"
9
+ DECLINED = "DECLINED"
10
+
11
+ class InvoiceAnalysis(BaseModel):
12
+ invoice_id: str = Field(..., description="Unique identifier for the invoice")
13
+ employee_name: str = Field(..., description="Name of the employee")
14
+ invoice_date: datetime = Field(..., description="Date of the invoice")
15
+ total_amount: float = Field(..., description="Total amount of the invoice")
16
+ reimbursable_amount: float = Field(..., description="Amount that can be reimbursed")
17
+ status: ReimbursementStatus = Field(..., description="Reimbursement status")
18
+ reason: str = Field(..., description="Detailed reason for the reimbursement status")
19
+ policy_violations: Optional[List[str]] = Field(default=None, description="List of policy violations if any")
20
+ created_at: datetime = Field(default_factory=datetime.utcnow)
21
+
22
+ class InvoiceAnalysisResponse(BaseModel):
23
+ success: bool = Field(..., description="Whether the analysis was successful")
24
+ message: str = Field(..., description="Response message")
25
+ analysis: Optional[InvoiceAnalysis] = Field(default=None, description="Analysis results if successful")
26
+
27
+ class InvoiceAnalysisRequest(BaseModel):
28
+ employee_name: str = Field(..., description="Name of the employee")
29
+ policy_text: str = Field(..., description="Text content of the reimbursement policy")
30
+ invoice_text: str = Field(..., description="Text content of the invoice")
app/api/routes/chat.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException
2
+ from app.api.models.chat import ChatRequest, ChatResponse
3
+ from app.services.chatbot import Chatbot
4
+ from app.services.vector_store import VectorStore
5
+
6
+ router = APIRouter()
7
+ vector_store = VectorStore()
8
+ chatbot = Chatbot(vector_store)
9
+
10
+ @router.post("/chat", response_model=ChatResponse)
11
+ async def chat(request: ChatRequest):
12
+ try:
13
+ response = await chatbot.process_query(
14
+ query=request.query,
15
+ chat_history=request.chat_history
16
+ )
17
+ return response
18
+ except Exception as e:
19
+ raise HTTPException(
20
+ status_code=500,
21
+ detail=f"Error processing chat request: {str(e)}"
22
+ )
app/api/routes/invoice.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, UploadFile, File, Form, HTTPException
2
+ from app.api.models.invoice import InvoiceAnalysisRequest, InvoiceAnalysisResponse
3
+ from app.services.invoice_processor import InvoiceProcessor
4
+ from app.services.vector_store import VectorStore
5
+ from app.utils.pdf_processor import extract_text_from_pdf
6
+ import zipfile
7
+ import io
8
+ from typing import List
9
+
10
+ router = APIRouter()
11
+ invoice_processor = InvoiceProcessor()
12
+ vector_store = VectorStore()
13
+
14
+ @router.post("/analyze-invoice", response_model=InvoiceAnalysisResponse)
15
+ async def analyze_invoice(
16
+ policy_file: UploadFile = File(...),
17
+ invoice_files: UploadFile = File(...),
18
+ employee_name: str = Form(...)
19
+ ):
20
+ try:
21
+ # Read and process policy file
22
+ policy_content = await policy_file.read()
23
+ policy_text = extract_text_from_pdf(policy_content)
24
+
25
+ # Process invoice files (ZIP)
26
+ invoice_analyses = []
27
+ with zipfile.ZipFile(io.BytesIO(await invoice_files.read())) as zip_ref:
28
+ for filename in zip_ref.namelist():
29
+ if filename.endswith('.pdf'):
30
+ # Extract and process each PDF
31
+ with zip_ref.open(filename) as pdf_file:
32
+ invoice_text = extract_text_from_pdf(pdf_file.read())
33
+
34
+ # Analyze invoice
35
+ analysis = await invoice_processor.analyze_invoice(
36
+ employee_name=employee_name,
37
+ policy_text=policy_text,
38
+ invoice_text=invoice_text
39
+ )
40
+
41
+ # Store in vector database
42
+ vector_store.store_analysis(analysis)
43
+ invoice_analyses.append(analysis)
44
+
45
+ return InvoiceAnalysisResponse(
46
+ success=True,
47
+ message=f"Successfully analyzed {len(invoice_analyses)} invoices",
48
+ analysis=invoice_analyses[0] if invoice_analyses else None
49
+ )
50
+
51
+ except Exception as e:
52
+ raise HTTPException(
53
+ status_code=500,
54
+ detail=f"Error processing invoices: {str(e)}"
55
+ )
app/core/config.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic_settings import BaseSettings
2
+ from typing import Optional
3
+ import os
4
+ from dotenv import load_dotenv
5
+
6
+ load_dotenv()
7
+
8
+ class Settings(BaseSettings):
9
+ # API Settings
10
+ API_V1_STR: str = "/api/v1"
11
+ PROJECT_NAME: str = "Invoice Reimbursement System"
12
+
13
+ # OpenAI Settings
14
+ OPENAI_API_KEY: str = os.getenv("OPENAI_API_KEY", "")
15
+ OPENAI_BASE_URL: str = os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1")
16
+
17
+ # Vector Store Settings
18
+ VECTOR_STORE_PATH: str = "data/vector_store"
19
+
20
+ # LLM Settings
21
+ LLM_MODEL_NAME: str = "mistral-large-latest"
22
+ EMBEDDING_MODEL_NAME: str = "text-embedding-3-small"
23
+
24
+ # File Upload Settings
25
+ MAX_UPLOAD_SIZE: int = 10 * 1024 * 1024 # 10MB
26
+ ALLOWED_EXTENSIONS: set = {"pdf", "zip"}
27
+
28
+ # Chat Settings
29
+ MAX_CHAT_HISTORY: int = 10
30
+
31
+ class Config:
32
+ case_sensitive = True
33
+
34
+ settings = Settings()
app/main.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from app.core.config import settings
4
+ from app.api.routes import invoice, chat
5
+ import logging
6
+
7
+ logging.basicConfig(level=logging.DEBUG)
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+
12
+ app = FastAPI(
13
+ title=settings.PROJECT_NAME,
14
+ openapi_url=f"{settings.API_V1_STR}/openapi.json"
15
+ )
16
+
17
+ # Configure CORS
18
+ app.add_middleware(
19
+ CORSMiddleware,
20
+ allow_origins=["*"], # In production, replace with specific origins
21
+ allow_credentials=True,
22
+ allow_methods=["*"],
23
+ allow_headers=["*"],
24
+ )
25
+
26
+ # Include routers
27
+ app.include_router(invoice.router, prefix=settings.API_V1_STR, tags=["invoice"])
28
+ app.include_router(chat.router, prefix=settings.API_V1_STR, tags=["chat"])
29
+
30
+ @app.exception_handler(Exception)
31
+ async def validation_exception_handler(request, exc):
32
+ logger.error(f"FastAPI error: {exc}")
33
+ return JSONResponse(
34
+ status_code=500,
35
+ content={"message": "Internal server error"},
36
+ )
37
+
38
+ @app.get("/")
39
+ async def root():
40
+ return {
41
+ "message": "Welcome to the Invoice Reimbursement System API",
42
+ "docs_url": "/docs",
43
+ "redoc_url": "/redoc"
44
+ }
45
+ if __name__ == "__main__":
46
+ import uvicorn
47
+ uvicorn.run(app, host="0.0.0.0", port=8000)
app/services/chatbot.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_openai import ChatOpenAI
2
+ from langchain.prompts import ChatPromptTemplate
3
+ from app.api.models.chat import ChatMessage, ChatResponse
4
+ from app.api.models.invoice import InvoiceAnalysis
5
+ from app.services.vector_store import VectorStore
6
+ from typing import List, Optional
7
+ from app.core.config import settings
8
+ from datetime import datetime
9
+
10
+ class Chatbot:
11
+ def __init__(self, vector_store: VectorStore):
12
+ self.llm = ChatOpenAI(
13
+ model_name=settings.LLM_MODEL_NAME,
14
+ base_url=settings.OPENAI_BASE_URL,
15
+ temperature=0.7
16
+ )
17
+ self.vector_store = vector_store
18
+
19
+ self.chat_prompt = ChatPromptTemplate.from_messages([
20
+ ("system", """You are an AI assistant specialized in helping users query and understand invoice reimbursement analyses.
21
+ You have access to a database of invoice analyses and can provide detailed information about them.
22
+
23
+ When responding:
24
+ 1. Use markdown formatting for better readability
25
+ 2. Be concise but informative
26
+ 3. If you reference specific invoices, mention their IDs
27
+ 4. If you're unsure about something, say so
28
+ 5. Use the provided context to answer questions accurately
29
+
30
+ Previous conversation:
31
+ {chat_history}
32
+
33
+ Relevant invoice analyses:
34
+ {invoice_analyses}
35
+
36
+ User query: {query}
37
+ """),
38
+ ])
39
+
40
+ async def process_query(
41
+ self,
42
+ query: str,
43
+ chat_history: Optional[List[ChatMessage]] = None
44
+ ) -> ChatResponse:
45
+ # Search for relevant invoice analyses
46
+ relevant_analyses = self.vector_store.search_analyses(query)
47
+
48
+ # Format chat history
49
+ formatted_history = ""
50
+ if chat_history:
51
+ formatted_history = "\n".join([
52
+ f"{msg.role}: {msg.content}"
53
+ for msg in chat_history
54
+ ])
55
+
56
+ # Format invoice analyses
57
+ formatted_analyses = "\n\n".join([
58
+ f"Invoice ID: {analysis.invoice_id}\n"
59
+ f"Employee: {analysis.employee_name}\n"
60
+ f"Status: {analysis.status}\n"
61
+ f"Amount: ${analysis.total_amount}\n"
62
+ f"Reimbursable: ${analysis.reimbursable_amount}\n"
63
+ f"Reason: {analysis.reason}"
64
+ for analysis in relevant_analyses
65
+ ])
66
+
67
+ # Prepare the prompt
68
+ prompt = self.chat_prompt.format_messages(
69
+ chat_history=formatted_history,
70
+ invoice_analyses=formatted_analyses,
71
+ query=query
72
+ )
73
+
74
+ # Get LLM response
75
+ response = await self.llm.ainvoke(prompt)
76
+
77
+ # Create new chat messages
78
+ new_messages = []
79
+ if chat_history:
80
+ new_messages.extend(chat_history)
81
+
82
+ new_messages.extend([
83
+ ChatMessage(role="user", content=query),
84
+ ChatMessage(role="assistant", content=response.content)
85
+ ])
86
+
87
+ # Create response
88
+ return ChatResponse(
89
+ response=response.content,
90
+ relevant_invoices=[analysis.invoice_id for analysis in relevant_analyses],
91
+ chat_history=new_messages
92
+ )
app/services/invoice_processor.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_openai import ChatOpenAI
2
+ from langchain.prompts import ChatPromptTemplate
3
+ from langchain.output_parsers import PydanticOutputParser
4
+ from app.api.models.invoice import InvoiceAnalysis, ReimbursementStatus
5
+ from app.core.config import settings
6
+ import uuid
7
+ from datetime import datetime
8
+ import json
9
+
10
+ class InvoiceProcessor:
11
+ def __init__(self):
12
+ self.llm = ChatOpenAI(
13
+ model_name=settings.LLM_MODEL_NAME,
14
+ temperature=0
15
+ )
16
+ self.parser = PydanticOutputParser(pydantic_object=InvoiceAnalysis)
17
+
18
+ self.analysis_prompt = ChatPromptTemplate.from_messages([
19
+ ("system", """You are an expert at analyzing expense invoices against company reimbursement policies.
20
+ Your task is to analyze the given invoice against the provided policy and determine:
21
+ 1. The reimbursement status (FULLY_REIMBURSED, PARTIALLY_REIMBURSED, or DECLINED)
22
+ 2. The reimbursable amount
23
+ 3. Detailed reasons for the decision
24
+ 4. Any policy violations found
25
+
26
+ Policy:
27
+ {policy_text}
28
+
29
+ Invoice:
30
+ {invoice_text}
31
+
32
+ {format_instructions}
33
+ """),
34
+ ])
35
+
36
+ async def analyze_invoice(self, employee_name: str, policy_text: str, invoice_text: str) -> InvoiceAnalysis:
37
+ # Generate a unique invoice ID
38
+ invoice_id = str(uuid.uuid4())
39
+
40
+ # Prepare the prompt
41
+ prompt = self.analysis_prompt.format_messages(
42
+ policy_text=policy_text,
43
+ invoice_text=invoice_text,
44
+ format_instructions=self.parser.get_format_instructions()
45
+ )
46
+
47
+ # Get LLM response
48
+ response = await self.llm.ainvoke(prompt)
49
+
50
+ # Parse the response
51
+ try:
52
+ analysis = self.parser.parse(response.content)
53
+ # Ensure the invoice_id is set
54
+ analysis.invoice_id = invoice_id
55
+ analysis.employee_name = employee_name
56
+ return analysis
57
+ except Exception as e:
58
+ # If parsing fails, create a default analysis
59
+ return InvoiceAnalysis(
60
+ invoice_id=invoice_id,
61
+ employee_name=employee_name,
62
+ invoice_date=datetime.utcnow(),
63
+ total_amount=0.0,
64
+ reimbursable_amount=0.0,
65
+ status=ReimbursementStatus.DECLINED,
66
+ reason=f"Error analyzing invoice: {str(e)}",
67
+ policy_violations=["Failed to parse invoice"]
68
+ )
app/services/vector_store.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import chromadb
2
+ from chromadb.config import Settings
3
+ from app.core.config import settings
4
+ from app.api.models.invoice import InvoiceAnalysis
5
+ import json
6
+ from typing import List, Dict, Any
7
+ import os
8
+
9
+ class VectorStore:
10
+ def __init__(self):
11
+ # Ensure the vector store directory exists
12
+ os.makedirs(settings.VECTOR_STORE_PATH, exist_ok=True)
13
+
14
+ # Initialize ChromaDB client
15
+ self.client = chromadb.PersistentClient(
16
+ path=settings.VECTOR_STORE_PATH,
17
+ settings=Settings(
18
+ anonymized_telemetry=False
19
+ )
20
+ )
21
+
22
+ # Create or get the collection
23
+ self.collection = self.client.get_or_create_collection(
24
+ name="invoice_analyses",
25
+ metadata={"hnsw:space": "cosine"}
26
+ )
27
+
28
+ def store_analysis(self, analysis: InvoiceAnalysis) -> None:
29
+ """Store an invoice analysis in the vector store."""
30
+ metadata = analysis.dict()
31
+ # Remove None values
32
+ metadata = {k: v for k, v in metadata.items() if v is not None}
33
+
34
+ # Convert all datetime fields to ISO strings and lists/dicts to JSON strings
35
+ for key, value in metadata.items():
36
+ if isinstance(value, (list, dict)):
37
+ metadata[key] = json.dumps(value)
38
+ elif hasattr(value, 'isoformat'):
39
+ metadata[key] = value.isoformat()
40
+
41
+ # Create document text for embedding
42
+ doc_text = f"""
43
+ Invoice Analysis for {analysis.employee_name}
44
+ Status: {analysis.status}
45
+ Total Amount: {analysis.total_amount}
46
+ Reimbursable Amount: {analysis.reimbursable_amount}
47
+ Reason: {analysis.reason}
48
+ Policy Violations: {', '.join(analysis.policy_violations) if analysis.policy_violations else 'None'}
49
+ """
50
+
51
+ # Store in vector database
52
+ self.collection.add(
53
+ ids=[analysis.invoice_id],
54
+ documents=[doc_text],
55
+ metadatas=[metadata]
56
+ )
57
+
58
+ def search_analyses(
59
+ self,
60
+ query: str,
61
+ n_results: int = 5,
62
+ where: Dict[str, Any] = None
63
+ ) -> List[InvoiceAnalysis]:
64
+ """Search for invoice analyses using semantic search and metadata filtering."""
65
+ # Perform the search
66
+ results = self.collection.query(
67
+ query_texts=[query],
68
+ n_results=n_results,
69
+ where=where
70
+ )
71
+
72
+ # Convert results back to InvoiceAnalysis objects
73
+ analyses = []
74
+ for metadata in results['metadatas'][0]:
75
+ # Parse JSON string fields back to Python objects
76
+ for key, value in metadata.items():
77
+ if isinstance(value, str):
78
+ try:
79
+ parsed = json.loads(value)
80
+ # Only replace if parsed is list or dict
81
+ if isinstance(parsed, (list, dict)):
82
+ metadata[key] = parsed
83
+ except (json.JSONDecodeError, TypeError):
84
+ pass
85
+ analysis = InvoiceAnalysis(**metadata)
86
+ analyses.append(analysis)
87
+
88
+ return analyses
89
+
90
+ def get_analysis_by_id(self, invoice_id: str) -> InvoiceAnalysis:
91
+ """Retrieve a specific invoice analysis by ID."""
92
+ result = self.collection.get(
93
+ ids=[invoice_id]
94
+ )
95
+
96
+ if not result['metadatas']:
97
+ raise ValueError(f"No analysis found for invoice ID: {invoice_id}")
98
+ metadata = result['metadatas'][0]
99
+ # Parse JSON string fields back to Python objects
100
+ for key, value in metadata.items():
101
+ if isinstance(value, str):
102
+ try:
103
+ parsed = json.loads(value)
104
+ if isinstance(parsed, (list, dict)):
105
+ metadata[key] = parsed
106
+ except (json.JSONDecodeError, TypeError):
107
+ pass
108
+ return InvoiceAnalysis(**metadata)
app/utils/pdf_processor.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pypdf import PdfReader
2
+ from io import BytesIO
3
+ from typing import Union
4
+
5
+ def extract_text_from_pdf(pdf_content: Union[bytes, BytesIO]) -> str:
6
+ """
7
+ Extract text content from a PDF file.
8
+
9
+ Args:
10
+ pdf_content: PDF file content as bytes or BytesIO object
11
+
12
+ Returns:
13
+ str: Extracted text content
14
+ """
15
+ try:
16
+ # Convert bytes to BytesIO if necessary
17
+ if isinstance(pdf_content, bytes):
18
+ pdf_content = BytesIO(pdf_content)
19
+
20
+ # Create PDF reader
21
+ pdf_reader = PdfReader(pdf_content)
22
+
23
+ # Extract text from all pages
24
+ text_content = []
25
+ for page in pdf_reader.pages:
26
+ text_content.append(page.extract_text())
27
+
28
+ return "\n".join(text_content)
29
+
30
+ except Exception as e:
31
+ raise ValueError(f"Error extracting text from PDF: {str(e)}")
docker-compose.yml ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ app:
5
+ build: .
6
+ container_name: invoice-reimbursement-system
7
+ env_file:
8
+ - .env
9
+ ports:
10
+ - "8000:8000"
11
+ volumes:
12
+ - ./data:/app/data
13
+ restart: unless-stopped
main.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ def main():
2
+ print("Hello from rembuiresment-system!")
3
+
4
+
5
+ if __name__ == "__main__":
6
+ main()
pyproject.toml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "invoice-reimbursement-system"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ dependencies = [
8
+ "bcrypt==4.1.2",
9
+ "chromadb==0.4.22",
10
+ "fastapi==0.109.2",
11
+ "langchain==0.1.9",
12
+ "langchain-openai==0.0.8",
13
+ "passlib==1.7.4",
14
+ "pydantic>=2.7.0",
15
+ "pydantic-settings==2.9.1",
16
+ "pypdf==4.0.1",
17
+ "python-dotenv==1.0.1",
18
+ "python-jose[cryptography]==3.3.0",
19
+ "python-multipart==0.0.9",
20
+ "sentence-transformers==2.5.1",
21
+ "uvicorn==0.27.1",
22
+ ]
requirements.txt ADDED
@@ -0,0 +1,495 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file was autogenerated by uv via the following command:
2
+ # uv pip compile pyproject.toml -o requirements.txt
3
+ aiohappyeyeballs==2.6.1
4
+ # via aiohttp
5
+ aiohttp==3.12.9
6
+ # via
7
+ # langchain
8
+ # langchain-community
9
+ aiosignal==1.3.2
10
+ # via aiohttp
11
+ annotated-types==0.7.0
12
+ # via pydantic
13
+ anyio==4.9.0
14
+ # via
15
+ # httpx
16
+ # openai
17
+ # starlette
18
+ # watchfiles
19
+ asgiref==3.8.1
20
+ # via opentelemetry-instrumentation-asgi
21
+ attrs==25.3.0
22
+ # via aiohttp
23
+ backoff==2.2.1
24
+ # via posthog
25
+ bcrypt==4.1.2
26
+ # via
27
+ # invoice-reimbursement-system (pyproject.toml)
28
+ # chromadb
29
+ build==1.2.2.post1
30
+ # via chromadb
31
+ cachetools==5.5.2
32
+ # via google-auth
33
+ certifi==2025.4.26
34
+ # via
35
+ # httpcore
36
+ # httpx
37
+ # kubernetes
38
+ # pulsar-client
39
+ # requests
40
+ cffi==1.17.1
41
+ # via cryptography
42
+ charset-normalizer==3.4.2
43
+ # via requests
44
+ chroma-hnswlib==0.7.3
45
+ # via chromadb
46
+ chromadb==0.4.22
47
+ # via invoice-reimbursement-system (pyproject.toml)
48
+ click==8.2.1
49
+ # via
50
+ # typer
51
+ # uvicorn
52
+ coloredlogs==15.0.1
53
+ # via onnxruntime
54
+ cryptography==45.0.3
55
+ # via python-jose
56
+ dataclasses-json==0.6.7
57
+ # via
58
+ # langchain
59
+ # langchain-community
60
+ distro==1.9.0
61
+ # via
62
+ # openai
63
+ # posthog
64
+ durationpy==0.10
65
+ # via kubernetes
66
+ ecdsa==0.19.1
67
+ # via python-jose
68
+ fastapi==0.109.2
69
+ # via
70
+ # invoice-reimbursement-system (pyproject.toml)
71
+ # chromadb
72
+ filelock==3.18.0
73
+ # via
74
+ # huggingface-hub
75
+ # torch
76
+ # transformers
77
+ flatbuffers==25.2.10
78
+ # via onnxruntime
79
+ frozenlist==1.6.2
80
+ # via
81
+ # aiohttp
82
+ # aiosignal
83
+ fsspec==2025.5.1
84
+ # via
85
+ # huggingface-hub
86
+ # torch
87
+ google-auth==2.40.3
88
+ # via kubernetes
89
+ googleapis-common-protos==1.70.0
90
+ # via opentelemetry-exporter-otlp-proto-grpc
91
+ greenlet==3.2.3
92
+ # via sqlalchemy
93
+ grpcio==1.72.1
94
+ # via
95
+ # chromadb
96
+ # opentelemetry-exporter-otlp-proto-grpc
97
+ h11==0.16.0
98
+ # via
99
+ # httpcore
100
+ # uvicorn
101
+ hf-xet==1.1.3
102
+ # via huggingface-hub
103
+ httpcore==1.0.9
104
+ # via httpx
105
+ httptools==0.6.4
106
+ # via uvicorn
107
+ httpx==0.28.1
108
+ # via
109
+ # langsmith
110
+ # openai
111
+ huggingface-hub==0.32.4
112
+ # via
113
+ # sentence-transformers
114
+ # tokenizers
115
+ # transformers
116
+ humanfriendly==10.0
117
+ # via coloredlogs
118
+ idna==3.10
119
+ # via
120
+ # anyio
121
+ # httpx
122
+ # requests
123
+ # yarl
124
+ importlib-metadata==8.7.0
125
+ # via opentelemetry-api
126
+ importlib-resources==6.5.2
127
+ # via chromadb
128
+ jinja2==3.1.6
129
+ # via torch
130
+ jiter==0.10.0
131
+ # via openai
132
+ joblib==1.5.1
133
+ # via scikit-learn
134
+ jsonpatch==1.33
135
+ # via
136
+ # langchain
137
+ # langchain-core
138
+ jsonpointer==3.0.0
139
+ # via jsonpatch
140
+ kubernetes==32.0.1
141
+ # via chromadb
142
+ langchain==0.1.9
143
+ # via invoice-reimbursement-system (pyproject.toml)
144
+ langchain-community==0.0.38
145
+ # via langchain
146
+ langchain-core==0.1.53
147
+ # via
148
+ # langchain
149
+ # langchain-community
150
+ # langchain-openai
151
+ langchain-openai==0.0.8
152
+ # via invoice-reimbursement-system (pyproject.toml)
153
+ langsmith==0.1.147
154
+ # via
155
+ # langchain
156
+ # langchain-community
157
+ # langchain-core
158
+ markdown-it-py==3.0.0
159
+ # via rich
160
+ markupsafe==3.0.2
161
+ # via jinja2
162
+ marshmallow==3.26.1
163
+ # via dataclasses-json
164
+ mdurl==0.1.2
165
+ # via markdown-it-py
166
+ mmh3==5.1.0
167
+ # via chromadb
168
+ mpmath==1.3.0
169
+ # via sympy
170
+ multidict==6.4.4
171
+ # via
172
+ # aiohttp
173
+ # yarl
174
+ mypy-extensions==1.1.0
175
+ # via typing-inspect
176
+ networkx==3.5
177
+ # via torch
178
+ numpy==1.26.4
179
+ # via
180
+ # chroma-hnswlib
181
+ # chromadb
182
+ # langchain
183
+ # langchain-community
184
+ # onnxruntime
185
+ # scikit-learn
186
+ # scipy
187
+ # sentence-transformers
188
+ # transformers
189
+ nvidia-cublas-cu12==12.6.4.1
190
+ # via
191
+ # nvidia-cudnn-cu12
192
+ # nvidia-cusolver-cu12
193
+ # torch
194
+ nvidia-cuda-cupti-cu12==12.6.80
195
+ # via torch
196
+ nvidia-cuda-nvrtc-cu12==12.6.77
197
+ # via torch
198
+ nvidia-cuda-runtime-cu12==12.6.77
199
+ # via torch
200
+ nvidia-cudnn-cu12==9.5.1.17
201
+ # via torch
202
+ nvidia-cufft-cu12==11.3.0.4
203
+ # via torch
204
+ nvidia-cufile-cu12==1.11.1.6
205
+ # via torch
206
+ nvidia-curand-cu12==10.3.7.77
207
+ # via torch
208
+ nvidia-cusolver-cu12==11.7.1.2
209
+ # via torch
210
+ nvidia-cusparse-cu12==12.5.4.2
211
+ # via
212
+ # nvidia-cusolver-cu12
213
+ # torch
214
+ nvidia-cusparselt-cu12==0.6.3
215
+ # via torch
216
+ nvidia-nccl-cu12==2.26.2
217
+ # via torch
218
+ nvidia-nvjitlink-cu12==12.6.85
219
+ # via
220
+ # nvidia-cufft-cu12
221
+ # nvidia-cusolver-cu12
222
+ # nvidia-cusparse-cu12
223
+ # torch
224
+ nvidia-nvtx-cu12==12.6.77
225
+ # via torch
226
+ oauthlib==3.2.2
227
+ # via
228
+ # kubernetes
229
+ # requests-oauthlib
230
+ onnxruntime==1.22.0
231
+ # via chromadb
232
+ openai==1.84.0
233
+ # via langchain-openai
234
+ opentelemetry-api==1.34.0
235
+ # via
236
+ # chromadb
237
+ # opentelemetry-exporter-otlp-proto-grpc
238
+ # opentelemetry-instrumentation
239
+ # opentelemetry-instrumentation-asgi
240
+ # opentelemetry-instrumentation-fastapi
241
+ # opentelemetry-sdk
242
+ # opentelemetry-semantic-conventions
243
+ opentelemetry-exporter-otlp-proto-common==1.34.0
244
+ # via opentelemetry-exporter-otlp-proto-grpc
245
+ opentelemetry-exporter-otlp-proto-grpc==1.34.0
246
+ # via chromadb
247
+ opentelemetry-instrumentation==0.55b0
248
+ # via
249
+ # opentelemetry-instrumentation-asgi
250
+ # opentelemetry-instrumentation-fastapi
251
+ opentelemetry-instrumentation-asgi==0.55b0
252
+ # via opentelemetry-instrumentation-fastapi
253
+ opentelemetry-instrumentation-fastapi==0.55b0
254
+ # via chromadb
255
+ opentelemetry-proto==1.34.0
256
+ # via
257
+ # opentelemetry-exporter-otlp-proto-common
258
+ # opentelemetry-exporter-otlp-proto-grpc
259
+ opentelemetry-sdk==1.34.0
260
+ # via
261
+ # chromadb
262
+ # opentelemetry-exporter-otlp-proto-grpc
263
+ opentelemetry-semantic-conventions==0.55b0
264
+ # via
265
+ # opentelemetry-instrumentation
266
+ # opentelemetry-instrumentation-asgi
267
+ # opentelemetry-instrumentation-fastapi
268
+ # opentelemetry-sdk
269
+ opentelemetry-util-http==0.55b0
270
+ # via
271
+ # opentelemetry-instrumentation-asgi
272
+ # opentelemetry-instrumentation-fastapi
273
+ orjson==3.10.18
274
+ # via langsmith
275
+ overrides==7.7.0
276
+ # via chromadb
277
+ packaging==23.2
278
+ # via
279
+ # build
280
+ # huggingface-hub
281
+ # langchain-core
282
+ # marshmallow
283
+ # onnxruntime
284
+ # opentelemetry-instrumentation
285
+ # transformers
286
+ passlib==1.7.4
287
+ # via invoice-reimbursement-system (pyproject.toml)
288
+ pillow==11.2.1
289
+ # via sentence-transformers
290
+ posthog==4.2.0
291
+ # via chromadb
292
+ propcache==0.3.1
293
+ # via
294
+ # aiohttp
295
+ # yarl
296
+ protobuf==5.29.5
297
+ # via
298
+ # googleapis-common-protos
299
+ # onnxruntime
300
+ # opentelemetry-proto
301
+ pulsar-client==3.7.0
302
+ # via chromadb
303
+ pyasn1==0.6.1
304
+ # via
305
+ # pyasn1-modules
306
+ # python-jose
307
+ # rsa
308
+ pyasn1-modules==0.4.2
309
+ # via google-auth
310
+ pycparser==2.22
311
+ # via cffi
312
+ pydantic==2.11.5
313
+ # via
314
+ # invoice-reimbursement-system (pyproject.toml)
315
+ # chromadb
316
+ # fastapi
317
+ # langchain
318
+ # langchain-core
319
+ # langsmith
320
+ # openai
321
+ # pydantic-settings
322
+ pydantic-core==2.33.2
323
+ # via pydantic
324
+ pydantic-settings==2.9.1
325
+ # via invoice-reimbursement-system (pyproject.toml)
326
+ pygments==2.19.1
327
+ # via rich
328
+ pypdf==4.0.1
329
+ # via invoice-reimbursement-system (pyproject.toml)
330
+ pypika==0.48.9
331
+ # via chromadb
332
+ pyproject-hooks==1.2.0
333
+ # via build
334
+ python-dateutil==2.9.0.post0
335
+ # via
336
+ # kubernetes
337
+ # posthog
338
+ python-dotenv==1.0.1
339
+ # via
340
+ # invoice-reimbursement-system (pyproject.toml)
341
+ # pydantic-settings
342
+ # uvicorn
343
+ python-jose==3.3.0
344
+ # via invoice-reimbursement-system (pyproject.toml)
345
+ python-multipart==0.0.9
346
+ # via invoice-reimbursement-system (pyproject.toml)
347
+ pyyaml==6.0.2
348
+ # via
349
+ # chromadb
350
+ # huggingface-hub
351
+ # kubernetes
352
+ # langchain
353
+ # langchain-community
354
+ # langchain-core
355
+ # transformers
356
+ # uvicorn
357
+ regex==2024.11.6
358
+ # via
359
+ # tiktoken
360
+ # transformers
361
+ requests==2.32.3
362
+ # via
363
+ # chromadb
364
+ # huggingface-hub
365
+ # kubernetes
366
+ # langchain
367
+ # langchain-community
368
+ # langsmith
369
+ # posthog
370
+ # requests-oauthlib
371
+ # requests-toolbelt
372
+ # tiktoken
373
+ # transformers
374
+ requests-oauthlib==2.0.0
375
+ # via kubernetes
376
+ requests-toolbelt==1.0.0
377
+ # via langsmith
378
+ rich==14.0.0
379
+ # via typer
380
+ rsa==4.9.1
381
+ # via
382
+ # google-auth
383
+ # python-jose
384
+ safetensors==0.5.3
385
+ # via transformers
386
+ scikit-learn==1.7.0
387
+ # via sentence-transformers
388
+ scipy==1.15.3
389
+ # via
390
+ # scikit-learn
391
+ # sentence-transformers
392
+ sentence-transformers==2.5.1
393
+ # via invoice-reimbursement-system (pyproject.toml)
394
+ setuptools==80.9.0
395
+ # via
396
+ # torch
397
+ # triton
398
+ shellingham==1.5.4
399
+ # via typer
400
+ six==1.17.0
401
+ # via
402
+ # ecdsa
403
+ # kubernetes
404
+ # posthog
405
+ # python-dateutil
406
+ sniffio==1.3.1
407
+ # via
408
+ # anyio
409
+ # openai
410
+ sqlalchemy==2.0.41
411
+ # via
412
+ # langchain
413
+ # langchain-community
414
+ starlette==0.36.3
415
+ # via fastapi
416
+ sympy==1.14.0
417
+ # via
418
+ # onnxruntime
419
+ # torch
420
+ tenacity==8.5.0
421
+ # via
422
+ # chromadb
423
+ # langchain
424
+ # langchain-community
425
+ # langchain-core
426
+ threadpoolctl==3.6.0
427
+ # via scikit-learn
428
+ tiktoken==0.9.0
429
+ # via langchain-openai
430
+ tokenizers==0.21.1
431
+ # via
432
+ # chromadb
433
+ # transformers
434
+ torch==2.7.1
435
+ # via sentence-transformers
436
+ tqdm==4.67.1
437
+ # via
438
+ # chromadb
439
+ # huggingface-hub
440
+ # openai
441
+ # sentence-transformers
442
+ # transformers
443
+ transformers==4.52.4
444
+ # via sentence-transformers
445
+ triton==3.3.1
446
+ # via torch
447
+ typer==0.16.0
448
+ # via chromadb
449
+ typing-extensions==4.14.0
450
+ # via
451
+ # chromadb
452
+ # fastapi
453
+ # huggingface-hub
454
+ # openai
455
+ # opentelemetry-api
456
+ # opentelemetry-exporter-otlp-proto-grpc
457
+ # opentelemetry-sdk
458
+ # opentelemetry-semantic-conventions
459
+ # pydantic
460
+ # pydantic-core
461
+ # sqlalchemy
462
+ # torch
463
+ # typer
464
+ # typing-inspect
465
+ # typing-inspection
466
+ typing-inspect==0.9.0
467
+ # via dataclasses-json
468
+ typing-inspection==0.4.1
469
+ # via
470
+ # pydantic
471
+ # pydantic-settings
472
+ urllib3==2.4.0
473
+ # via
474
+ # kubernetes
475
+ # requests
476
+ uvicorn==0.27.1
477
+ # via
478
+ # invoice-reimbursement-system (pyproject.toml)
479
+ # chromadb
480
+ uvloop==0.21.0
481
+ # via uvicorn
482
+ watchfiles==1.0.5
483
+ # via uvicorn
484
+ websocket-client==1.8.0
485
+ # via kubernetes
486
+ websockets==15.0.1
487
+ # via uvicorn
488
+ wrapt==1.17.2
489
+ # via opentelemetry-instrumentation
490
+ yarl==1.20.0
491
+ # via aiohttp
492
+ zipp==3.22.0
493
+ # via importlib-metadata
494
+ pytest
495
+ reportlab
uv.lock ADDED
The diff for this file is too large to render. See raw diff