Vivek0912 commited on
Commit
fe6855c
·
1 Parent(s): ab3dbe5

deploying code to hugging face

Browse files
backend/.env.prod ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ ENV=production
2
+ HUGGINGFACE_API_TOKEN=your_production_api_token
3
+ EMAIL_DIRECTORY_PATH=your_email_file_path
4
+
backend/.gitignore ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Ignore installed dependencies
36
+ /backend_env/
37
+
38
+ # Installer logs
39
+ pip-log.txt
40
+ pip-delete-this-directory.txt
41
+
42
+ # Unit test / coverage reports
43
+ htmlcov/
44
+ .tox/
45
+ .nox/
46
+ .coverage
47
+ .coverage.*
48
+ .cache
49
+ nosetests.xml
50
+ coverage.xml
51
+ *.cover
52
+ *.py,cover
53
+ .hypothesis/
54
+ .pytest_cache/
55
+ cover/
56
+
57
+ # Translations
58
+ *.mo
59
+ *.pot
60
+
61
+ # Django stuff:
62
+ *.log
63
+ local_settings.py
64
+ db.sqlite3
65
+ db.sqlite3-journal
66
+
67
+ # Flask stuff:
68
+ instance/
69
+ .webassets-cache
70
+
71
+ # Scrapy stuff:
72
+ .scrapy
73
+
74
+ # Sphinx documentation
75
+ docs/_build/
76
+
77
+ # PyBuilder
78
+ .pybuilder/
79
+ target/
80
+
81
+ # Jupyter Notebook
82
+ .ipynb_checkpoints
83
+
84
+ # IPython
85
+ profile_default/
86
+ ipython_config.py
87
+
88
+ # pyenv
89
+ # For a library or package, you might want to ignore these files since the code is
90
+ # intended to run in multiple environments; otherwise, check them in:
91
+ # .python-version
92
+
93
+ # pipenv
94
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
95
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
96
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
97
+ # install all needed dependencies.
98
+ #Pipfile.lock
99
+
100
+ # UV
101
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
102
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
103
+ # commonly ignored for libraries.
104
+ #uv.lock
105
+
106
+ # poetry
107
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
108
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
109
+ # commonly ignored for libraries.
110
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
111
+ #poetry.lock
112
+
113
+ # pdm
114
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
115
+ #pdm.lock
116
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
117
+ # in version control.
118
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
119
+ .pdm.toml
120
+ .pdm-python
121
+ .pdm-build/
122
+
123
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
124
+ __pypackages__/
125
+
126
+ # Celery stuff
127
+ celerybeat-schedule
128
+ celerybeat.pid
129
+
130
+ # SageMath parsed files
131
+ *.sage.py
132
+
133
+ # Environments
134
+ .env
135
+ .venv
136
+ env/
137
+ venv/
138
+ ENV/
139
+ env.bak/
140
+ venv.bak/
141
+
142
+ # Spyder project settings
143
+ .spyderproject
144
+ .spyproject
145
+
146
+ # Rope project settings
147
+ .ropeproject
148
+
149
+ # mkdocs documentation
150
+ /site
151
+
152
+ # mypy
153
+ .mypy_cache/
154
+ .dmypy.json
155
+ dmypy.json
156
+
157
+ # Pyre type checker
158
+ .pyre/
159
+
160
+ # pytype static type analyzer
161
+ .pytype/
162
+
163
+ # Cython debug symbols
164
+ cython_debug/
165
+
166
+ # PyCharm
167
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
168
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
169
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
170
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
171
+ #.idea/
172
+
173
+ # Ruff stuff:
174
+ .ruff_cache/
175
+
176
+ # PyPI configuration file
177
+ .pypirc
178
+
179
+
180
+ # See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
181
+
182
+ # Compiled output
183
+ /dist
184
+ /tmp
185
+ /out-tsc
186
+ /bazel-out
187
+
188
+ # Node
189
+ /node_modules
190
+ npm-debug.log
191
+ yarn-error.log
192
+
193
+ # IDEs and editors
194
+ .idea/
195
+ .project
196
+ .classpath
197
+ .c9/
198
+ *.launch
199
+ .settings/
200
+ *.sublime-workspace
201
+
202
+ # Visual Studio Code
203
+ .vscode/*
204
+ !.vscode/settings.json
205
+ !.vscode/tasks.json
206
+ !.vscode/launch.json
207
+ !.vscode/extensions.json
208
+ .history/*
209
+
210
+ # Miscellaneous
211
+ /.angular/cache
212
+ .sass-cache/
213
+ /connect.lock
214
+ /coverage
215
+ /libpeerconnection.log
216
+ testem.log
217
+ /typings
218
+
219
+ # System files
220
+ .DS_Store
221
+ Thumbs.db
222
+
backend/Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use the official Python 3.11.5 image
2
+ FROM python:3.11.5
3
+
4
+ # Set the working directory
5
+ WORKDIR /app
6
+
7
+ # Copy the backend folder into the container
8
+ COPY backend /app
9
+
10
+ # Install dependencies
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ # Expose the port FastAPI runs on
14
+ EXPOSE 7860
15
+
16
+ # Run FastAPI app
17
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
backend/README.md ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 📧 Gen AI Email Processing
2
+
3
+ This project is an AI-powered email processing system that reads, extracts, classifies, and analyzes emails and documents using LLaMA models. It supports OCR for images/PDFs and utilizes machine learning to classify emails.
4
+
5
+ ---
6
+
7
+ ## 🚀 Tech Stack
8
+ - **Python** (Backend Development)
9
+ - **FastAPI** (API Framework)
10
+ - **PyTorch & Transformers** (AI Model - LLaMA)
11
+ - **OCR Tools** (Tesseract, pdfplumber, python-docx)
12
+ - **Langchain & Sentence-Transformers** (Text Processing & Embeddings)
13
+ - **Uvicorn** (ASGI Server)
14
+ - **Scikit-learn & Pandas** (Data Processing)
15
+ - **LLama-CPP-Python** (LLaMA Model Integration)
16
+
17
+ ---
18
+
19
+ ## 📥 Installation & Setup
20
+ Follow these steps to set up the project on your local machine:
21
+
22
+ ### 1️⃣ Clone the Repository
23
+ ```sh
24
+ git clone https://github.com/your-username/gen_ai_email_processing.git
25
+ cd gen_ai_email_processing
26
+ ```
27
+
28
+ ### 2️⃣ Create a Virtual Environment
29
+ ```sh
30
+ python -m venv env
31
+ source env/bin/activate # On macOS/Linux
32
+ env\Scripts\activate # On Windows
33
+ ```
34
+
35
+ ### 3️⃣ Install Dependencies
36
+ ```sh
37
+ pip install --upgrade pip
38
+ pip install -r requirements.txt
39
+ ```
40
+
41
+ ### 4️⃣ Run the FastAPI Server
42
+ ```sh
43
+ uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
44
+ ```
45
+
46
+ ### 5️⃣ Access the API Documentation
47
+ - Open **Swagger UI**: [http://localhost:8000/docs](http://localhost:8000/docs)
48
+ - Open **ReDoc**: [http://localhost:8000/redoc](http://localhost:8000/redoc)
49
+
50
+ ---
51
+
52
+ ## 📬 API Endpoints
53
+ | Method | Endpoint | Description |
54
+ |--------|------------------|--------------------------|
55
+ | POST | `/upload-email` | Upload and process email |
56
+ | GET | `/health` | Check API status |
57
+
58
+ ---
59
+
60
+ ## 🛠 Environment Variables
61
+ Create a `.env` file in the **config/** directory and add the necessary settings:
62
+ ```ini
63
+ MODEL_PATH=/path/to/llama/model
64
+ OCR_LANGUAGE=eng
65
+ ```
66
+
67
+ ---
68
+
69
+ ## 🔄 Updating the Project
70
+ If you pull new changes from GitHub, remember to update dependencies:
71
+ ```sh
72
+ git pull origin main
73
+ pip install -r requirements.txt
74
+ ```
75
+
76
+ ---
77
+
78
+ ## 📤 Pushing the Project to GitHub
79
+ After making changes, push them to GitHub:
80
+ ```sh
81
+ git add .
82
+ git commit -m "Updated project files"
83
+ git push origin main
84
+ ```
85
+
86
+ ---
87
+
88
+ ## 📝 Contributing
89
+ Feel free to fork and contribute to this project! 😊
90
+
backend/app/api/endpoints.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ from fastapi import APIRouter, HTTPException, UploadFile, File, Depends
3
+ from typing import List, Optional
4
+ from pydantic import BaseModel
5
+ from datetime import datetime
6
+ import os
7
+ import email
8
+ from email import policy
9
+ from email.parser import BytesParser
10
+ import dateutil.parser
11
+ import io
12
+ from app.services.classify_prompt import classify_email_with_prompt
13
+ from app.services.email_reader import parse_email_bytes, read_emails_from_directory, parse_email
14
+ from app.services.duplicate_checker import check_duplicate
15
+ from app.models.email_model import EmailData
16
+ from app.models.request_type_model import RequestTypeModel
17
+ from app.services.gemeni_classification import analyze_intent, classify_email_gemeni, extract_text_from_attachment, get_primary_intent
18
+ from app.services.retrieve_email_process import process_single_email
19
+ from config import settings
20
+
21
+ router = APIRouter()
22
+
23
+
24
+ @router.post("/process-emails-upload/", response_model=List[EmailData])
25
+ async def process_email_files(files: List[UploadFile] = File(...)):
26
+ """Processes multiple email files uploaded from form data."""
27
+ results = []
28
+ for file in files:
29
+ try:
30
+ file_content = await file.read()
31
+ email_result = await process_single_email(file_content, file.filename)
32
+ if email_result:
33
+ email_resp = EmailData(
34
+ sender=email_result["sender"],
35
+ subject=email_result["subject"],
36
+ request_type=email_result["request_type"],
37
+ sub_request_type=email_result["sub_request_type"],
38
+ confidence_score=email_result["confidence_score"],
39
+ duplicate_flag=email_result["duplicate_flag"],
40
+ )
41
+ results.append(email_resp)
42
+ except Exception as e:
43
+ print(f"Error processing {file.filename}: {e}")
44
+ return results
45
+
46
+
47
+ @router.post("/process-email-directory/", response_model=List[EmailData])
48
+ async def process_email_directory():
49
+ """Processes email files from a directory specified in an environment variable."""
50
+ directory_path = settings.settings.directory_path
51
+
52
+ if not directory_path:
53
+ raise HTTPException(status_code=400, detail="EMAIL_DIRECTORY_PATH environment variable not set.")
54
+
55
+ if not os.path.exists(directory_path) or not os.path.isdir(directory_path):
56
+ raise HTTPException(status_code=400, detail="Invalid directory path.")
57
+
58
+ results = []
59
+ email_files = []
60
+ for file in os.listdir(directory_path):
61
+ if file.endswith(".eml") or file.endswith(".msg") or file.endswith(".txt"):
62
+ email_files.append(os.path.join(directory_path, file))
63
+
64
+ for file_path in email_files:
65
+ try:
66
+ with open(file_path, "rb") as f:
67
+ file_content = f.read()
68
+ filename = os.path.basename(file_path)
69
+ email_result = await process_single_email(file_content, filename)
70
+ if email_result:
71
+ email_resp = EmailData(
72
+ sender=email_result["sender"],
73
+ subject=email_result["subject"],
74
+ request_type=email_result["request_type"],
75
+ sub_request_type=email_result["sub_request_type"],
76
+ confidence_score=email_result["confidence_score"],
77
+ duplicate_flag=email_result["duplicate_flag"],
78
+ )
79
+ results.append(email_resp)
80
+ except Exception as e:
81
+ print(f"Error processing {file_path}: {e}")
82
+ return results
backend/app/models/email_model.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+ from typing import List, Optional
3
+
4
+ class EmailData(BaseModel):
5
+ sender: str
6
+ subject: str
7
+ request_type: Optional[str] = None
8
+ sub_request_type: Optional[str] = None
9
+ confidence_score: Optional[float] = None
10
+ duplicate_flag: bool = False
backend/app/models/request_type_model.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class RequestTypeModel:
2
+ requests_datasets = [
3
+ {
4
+ "Request Type": "Adjustment",
5
+ "Sub-Request Type": ["N/A"],
6
+ },
7
+ {
8
+ "Request Type": "AU Transfer",
9
+ "Sub-Request Type": ["N/A"],
10
+ },
11
+ {
12
+ "Request Type": "Closing Notice",
13
+ "Sub-Request Type": ["Reallocation Fees", "Amendment Fees", "Reallocation Principal"],
14
+ },
15
+ {
16
+ "Request Type": "Commitment Change",
17
+ "Sub-Request Type": ["Cashless Roll", "Decrease", "Increase"],
18
+ },
19
+ {
20
+ "Request Type": "Fee Payment",
21
+ "Sub-Request Type": ["Ongoing Fee", "Letter of Credit Fee"],
22
+ },
23
+ {
24
+ "Request Type": "Money Movement - Inbound",
25
+ "Sub-Request Type": ["Principal", "Interest", "Principal + Interest", "Principal + Interest + Fee"],
26
+ },
27
+ {
28
+ "Request Type": "Money Movement - Outbound",
29
+ "Sub-Request Type": ["Timebound", "Foreign Currency"],
30
+ },
31
+ {
32
+ "Request Type": "Account Opening",
33
+ "Sub-Request Type": ["Checking Account", "Savings Account", "Money Market Account", "Certificate of Deposit (CD)"],
34
+ },
35
+ {
36
+ "Request Type": "Account Closing",
37
+ "Sub-Request Type": ["Checking Account", "Savings Account", "Money Market Account", "Certificate of Deposit (CD)"],
38
+ },
39
+ {
40
+ "Request Type": "Balance Inquiry",
41
+ "Sub-Request Type": ["Checking Account", "Savings Account", "Loan Account", "Credit Card Account"],
42
+ },
43
+ {
44
+ "Request Type": "Statement Request",
45
+ "Sub-Request Type": ["Checking Account", "Savings Account", "Loan Account", "Credit Card Account"],
46
+ },
47
+ {
48
+ "Request Type": "Transaction History Request",
49
+ "Sub-Request Type": ["Checking Account", "Savings Account", "Loan Account", "Credit Card Account"],
50
+ },
51
+ {
52
+ "Request Type": "Funds Transfer",
53
+ "Sub-Request Type": ["Internal Transfer", "External Transfer (ACH, Wire)"],
54
+ },
55
+ {
56
+ "Request Type": "Stop Payment",
57
+ "Sub-Request Type": ["Check", "Electronic Transfer"],
58
+ },
59
+ {
60
+ "Request Type": "Credit Card Application",
61
+ "Sub-Request Type": ["Personal", "Business"],
62
+ },
63
+ {
64
+ "Request Type": "Credit Limit Change",
65
+ "Sub-Request Type": ["Increase", "Decrease"],
66
+ },
67
+ {
68
+ "Request Type": "Loan Application",
69
+ "Sub-Request Type": ["Mortgage", "Auto Loan", "Personal Loan", "Business Loan"],
70
+ },
71
+ {
72
+ "Request Type": "Loan Disbursement",
73
+ "Sub-Request Type": ["Initial Disbursement", "Subsequent Disbursement"],
74
+ },
75
+ {
76
+ "Request Type": "Loan Payoff",
77
+ "Sub-Request Type": ["Principal", "Interest", "Fees"],
78
+ },
79
+ {
80
+ "Request Type": "Customer Information Update",
81
+ "Sub-Request Type": ["Address Change", "Phone Number Change", "Email Address Change"],
82
+ },
83
+ {
84
+ "Request Type": "Online/Mobile Banking Access",
85
+ "Sub-Request Type": ["Enrollment", "Password Reset", "Access Removal"],
86
+ },
87
+ {
88
+ "Request Type": "Security Request",
89
+ "Sub-Request Type": ["Change PIN", "Report Lost/Stolen Card", "Fraud Alert"],
90
+ }
91
+ ]
92
+
93
+ @classmethod
94
+ def get_sub_types(cls, request_type):
95
+ """Retrieve sub-request types based on the request type"""
96
+ for item in cls.requests_datasets:
97
+ if item["Request Type"] == request_type:
98
+ return item["Sub-Request Type"]
99
+ return []
backend/app/services/classify_prompt.py ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import json
3
+ from app.models.request_type_model import RequestTypeModel
4
+ from config import settings
5
+
6
+ MODEL_NAME = settings.settings.MODEL_NAME # e.g., "tiiuae/falcon-7b-instruct"
7
+ HF_TOKEN = settings.settings.HUGGINGFACE_API_TOKEN # Hugging Face token
8
+
9
+ if not HF_TOKEN or HF_TOKEN == "YOUR_HUGGINGFACE_API_TOKEN":
10
+ print("Error: Hugging Face API token is missing. Please ensure you have a valid config.")
11
+ exit()
12
+
13
+ API_URL = f"https://router.huggingface.co/hf-inference/models/{MODEL_NAME}/v1/chat/completions"
14
+ HEADERS = {"Authorization": f"Bearer {HF_TOKEN}"}
15
+
16
+ PROMPT_OBJECTIVE_CLASSIFICATION_RULES = """
17
+ ### Task: Email Classification
18
+
19
+ #### **Objective:**
20
+ Analyze the given email and classify it into the most appropriate **Request Type** and **Sub Request Type** based on its primary intent.
21
+
22
+ #### **Instructions:**
23
+ - Identify the key intent of the email.
24
+ - Match it with one of the **Request Types** from the predefined categories.
25
+ - Select the most relevant **Sub Request Type** for the classification.
26
+ - If no exact match is found, choose the closest category.
27
+
28
+ Only return a JSON object with the classification results.
29
+ """
30
+ PROMPT_CATEGORIES = f"""
31
+ #### **Classification Categories & Definitions:**
32
+ {json.dumps(RequestTypeModel.requests_datasets, indent=2)}
33
+ """
34
+
35
+ PROMPT_OUTPUT_FORMAT = """
36
+ #### **Output Format:**
37
+ Return the classification result in **pure JSON format** (without extra text or markdown).
38
+
39
+ Example Output:
40
+ {
41
+ "request_type": "Commitment Change",
42
+ "sub_request_type": "Increase",
43
+ "confidence_score": 0.95,
44
+ "email_subject": "Request for Credit Line Increase"
45
+ }
46
+ """
47
+ PROMPT_TEMPLATE = """
48
+ ### Task: Email Classification
49
+ #### **Objective:**
50
+ Analyze the given email and classify it into the most appropriate **Request Type** and **Sub Request Type** based on its primary intent. Ensure the response is strictly in JSON format with the specified fields.
51
+
52
+ #### **Classification Categories:**
53
+ Each email must be categorized under one of the following **Request Types** and corresponding **Sub Request Types**:
54
+
55
+ | Request Type | Sub Request Type |
56
+ |---------------------------|------------------------------------------------------|
57
+ | Adjustment | N/A |
58
+ | AU Transfer | N/A |
59
+ | Closing Notice | Reallocation Fees, Amendment Fees, Reallocation Principal |
60
+ | Commitment Change | Cashless Roll, Decrease, Increase |
61
+ | Fee Payment | Ongoing Fee, Letter of Credit Fee |
62
+ | Money Movement - Inbound | Principal, Interest, Principal + Interest, Principal + Interest + Fee |
63
+ | Money Movement - Outbound | Timebound, Foreign Currency |
64
+
65
+ #### **Output Format:**
66
+ Return the classification result strictly in **JSON format** with the following fields:
67
+ ```json
68
+ {
69
+ "request_type": "Request Type",
70
+ "sub_request_type": "Sub Request Type",
71
+ "confidence_score": Confidence Score (between 0 and 1),
72
+ "email_subject": "Email Subject"
73
+ }
74
+ ### **🔹 Email for Classification:**
75
+ ```email
76
+ {{
77
+ QQA Bank, N.A.
78
+ Loan Agency Services
79
+
80
+
81
+ Date: 05-Feb-2025
82
+ TO: ABC BANK, NATIONAL ASSOCIATION
83
+ ATTN: RAMAKRISHNA KUNCHALA
84
+ Fax: 877-606-9426
85
+ Re: ABB MID-ATLANTIC LLC $171.3MM 11-4-2022, TERM LOAN A-2
86
+
87
+ Description: Facility Lender Share Adjustment
88
+
89
+ BORROWER: ABB MID-ATLANTIC LIC
90
+ DEAL NAME: ABB MID-ATLANTIC LIC $171. 3MM 11-4-2022
91
+
92
+ Effective 04-Feb-2025, the Lender Shares of facility TERM LOAN A-2 have been adjusted.
93
+ Your share of the commitment was USD 5,518,249.19. It has been Increased to USD 5,542,963.55.
94
+
95
+ For: ABC BANK, NA
96
+
97
+ Reference: ABIB MID-ATLANTIC LIC $171.3MM 11-4-2022,
98
+
99
+ If you have any questions, please call the undersigned.
100
+ ********************************************COMMENT***************************************
101
+ PLEASE FUND YOUR SHARE OF $24,714.36
102
+
103
+
104
+ Bank Name: QQA Bank NA
105
+ ABA # 011500120
106
+ Account #: 0026693011
107
+ Account Name: LIQ CLO Operating Account
108
+ Ref: ABTB Mid-Atlantic LLC
109
+
110
+ ********************************************************************************************
111
+ Regards,
112
+
113
+ SCOTT WALLACE
114
+ Telephone #:
115
+ Fax #:
116
+
117
+ QQA Commercial Banking is a brand name of QQA Bank, N.A. Member FDIC
118
+
119
+ }}
120
+
121
+ """
122
+
123
+ def extract_json_from_response(response_text):
124
+ """Extract JSON response from model output."""
125
+ try:
126
+ json_start = response_text.find('{')
127
+ json_end = response_text.rfind('}') + 1
128
+ json_string = response_text[json_start:json_end]
129
+ return json.loads(json_string)
130
+ except (ValueError, json.JSONDecodeError):
131
+ return {"error": "Could not extract JSON from model output"}
132
+
133
+ def send_to_huggingface_api(prompt):
134
+ """Send the prompt to Hugging Face API and get the response."""
135
+ try:
136
+ payload = {
137
+ "messages": [
138
+ {"role": "system", "content": PROMPT_OBJECTIVE_CLASSIFICATION_RULES + PROMPT_CATEGORIES + PROMPT_OUTPUT_FORMAT},
139
+ {"role": "user", "content": prompt}
140
+ ],
141
+ "max_tokens": 700,
142
+ "temperature": 0.2,
143
+ "top_p": 0.8,
144
+ "model": MODEL_NAME
145
+ }
146
+ response = requests.post(API_URL, headers=HEADERS, json=payload)
147
+ response.raise_for_status()
148
+ result = response.json()
149
+
150
+ if "choices" in result and result["choices"]:
151
+ return result["choices"][0]["message"]["content"]
152
+ return {"error": "Unexpected API response format"}
153
+
154
+ except requests.exceptions.RequestException as e:
155
+ return {"error": f"API request failed: {e}"}
156
+
157
+
158
+ def classify_email_with_prompt(email_text):
159
+
160
+
161
+ final_response = send_to_huggingface_api(email_text)
162
+
163
+ try:
164
+ classification = extract_json_from_response(final_response)
165
+ request_type = classification.get("request_type", "").strip()
166
+ if request_type:
167
+ valid_sub_types = RequestTypeModel.get_sub_types(request_type)
168
+ sub_request_type = classification.get("sub_request_type", "").strip()
169
+ if sub_request_type not in valid_sub_types:
170
+ classification["sub_request_type"] = valid_sub_types[0]
171
+ return classification
172
+ except Exception as e:
173
+ return {"error": str(e)}
174
+
175
+ # Example Usage
176
+ if __name__ == "__main__":
177
+ email_text = """
178
+ QQA Bank, N.A.
179
+ Loan Agency Services
180
+ Date: 05-Feb-2025
181
+ Description: Facility Lender Share Adjustment
182
+ """
183
+ classification = classify_email_with_prompt(email_text)
184
+ print(classification)
backend/app/services/duplicate_checker.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sklearn.feature_extraction.text import TfidfVectorizer
2
+ from sklearn.metrics.pairwise import cosine_similarity
3
+
4
+ vectorizer = TfidfVectorizer()
5
+ email_cache = []
6
+
7
+ def check_duplicate(email_text: str):
8
+ global email_cache
9
+ email_cache.append(email_text)
10
+
11
+ if len(email_cache) > 1: #check if there is more than 1 email in cache.
12
+ tfidf_matrix = vectorizer.fit_transform(email_cache)
13
+ similarity_matrix = cosine_similarity(tfidf_matrix[-1:], tfidf_matrix[:-1])
14
+
15
+ if len(similarity_matrix[0]) > 0 and max(similarity_matrix[0]) > 0.9:
16
+ return True, f"Similar email found with similarity {max(similarity_matrix[0])}"
17
+ else:
18
+ return False, None
19
+ else: #if only 1 email, then it is not a duplicate.
20
+ return False, None
backend/app/services/email_parser.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .email_reader import read_email
2
+ from ..models.email_model import EmailData
3
+
4
+ def parse_email(file_path: str) -> EmailData:
5
+ email_data = read_email(file_path)
6
+ return EmailData(
7
+ sender=email_data["sender"],
8
+ recipient=email_data["recipient"],
9
+ subject=email_data["subject"],
10
+ body=email_data["body"],
11
+ attachments=email_data["attachments"]
12
+ )
backend/app/services/email_reader.py ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import email
3
+ from email import policy
4
+ from email.parser import BytesParser
5
+ import re
6
+ from typing import Dict, List, Optional
7
+ import dateutil
8
+ from fastapi import HTTPException
9
+
10
+ def read_emails_from_directory(directory: str) -> List[str]:
11
+ """Reads email files from a given directory"""
12
+ emails = []
13
+ for file in os.listdir(directory):
14
+ if file.endswith(".eml") or file.endswith(".msg") or file.endswith(".txt"):
15
+ emails.append(os.path.join(directory, file))
16
+ return emails
17
+
18
+ def parse_email(file_path: str) -> dict:
19
+ """Parses email file and extracts metadata, body, and attachments."""
20
+ with open(file_path, "rb") as f:
21
+ msg = BytesParser(policy=policy.default).parse(f)
22
+
23
+ body = ""
24
+ attachments = []
25
+ for part in msg.walk():
26
+ if part.get_content_type() == "text/plain":
27
+ body += part.get_payload(decode=True).decode("utf-8", errors="ignore")
28
+ elif part.get_filename():
29
+ attachments.append(part.get_filename())
30
+
31
+ return {
32
+ "sender": msg["From"],
33
+ "subject": msg["Subject"],
34
+ "date": msg["Date"],
35
+ "body": body,
36
+ "attachments": attachments
37
+ }
38
+
39
+ def parse_email_bytes(file_content: bytes, filename: str) -> Optional[Dict]:
40
+ """Parses email bytes, extracts attachments, and handles email chains."""
41
+ try:
42
+ msg = email.message_from_bytes(file_content)
43
+ sender = msg["from"]
44
+ subject = msg["subject"]
45
+ body = ""
46
+ attachments = []
47
+ email_chain_text = ""
48
+
49
+ if msg.is_multipart():
50
+ for part in msg.walk():
51
+ content_type = part.get_content_type()
52
+ content_disposition = str(part.get("Content-Disposition"))
53
+
54
+ if content_type == "text/plain" and "attachment" not in content_disposition:
55
+ body += part.get_payload(decode=True).decode()
56
+ elif content_type == "text/html" and "attachment" not in content_disposition:
57
+ html = part.get_payload(decode=True).decode()
58
+ body += ''.join(c if ord(c) < 128 else ' ' for c in html.replace("<br>", "\n").replace("<p>", "\n").replace("</p>","\n").replace("<div>","\n").replace("</div>","\n").replace("<span>"," ").replace("</span>", " "))
59
+
60
+ elif "attachment" in content_disposition:
61
+ attachment_data = part.get_payload(decode=True)
62
+ attachments.append({
63
+ "filename": part.get_filename(),
64
+ "content": attachment_data,
65
+ })
66
+ email_chain_text = process_email_chain(msg) #only process if it is multipart.
67
+ else:
68
+ body = msg.get_payload(decode=True).decode()
69
+
70
+ # Handle email chains (add logic based on your needs)
71
+ # email_chain_text = process_email_chain(msg) #see full code in previous response.
72
+
73
+ return {
74
+ "sender": sender if sender else "Unknown Sender", #added default value.
75
+ "subject": subject if subject else "No Subject", #added default value.
76
+ "body": body,
77
+ "attachments": attachments,
78
+ "email_chain_text": email_chain_text
79
+ }
80
+
81
+ except Exception as e:
82
+ print(f"Error parsing email: {e}")
83
+ return None
84
+
85
+
86
+ def process_email_chain(email_message):
87
+ """
88
+ Detects and processes email chains, extracting text from each email.
89
+ """
90
+ try:
91
+ if isinstance(email_message, str):
92
+ msg = email.message_from_string(email_message)
93
+ else:
94
+ msg = email_message
95
+
96
+ chain = []
97
+ full_text = ""
98
+
99
+ # Check for nested email headers (From:, Date:) or quoted text
100
+ if has_nested_emails(msg):
101
+ # Parse the email and extract the chain
102
+ chain = extract_email_chain(msg)
103
+
104
+ for email_part in chain:
105
+ full_text += extract_text_from_email(email_part) + "\n"
106
+ else:
107
+ # No nested emails, just extract the text from the current email
108
+ full_text = extract_text_from_email(msg)
109
+
110
+ return full_text
111
+
112
+ except Exception as e:
113
+ print(f"Error processing email chain: {e}")
114
+ return extract_text_from_email(email_message) #default to just the email.
115
+
116
+ def has_nested_emails(msg):
117
+ """
118
+ Detects if an email contains nested emails based on headers or quoted text.
119
+ """
120
+ body = get_email_body_text(msg)
121
+
122
+ # Check for multiple 'From:' and 'Date:' headers in the body
123
+ if body:
124
+ if len(re.findall(r"^From:.*", body, re.MULTILINE)) > 1 or \
125
+ len(re.findall(r"^Date:.*", body, re.MULTILINE)) > 1 or \
126
+ len(re.findall(r"^>.*", body, re.MULTILINE)) > 5: #arbitrary number of quoted lines.
127
+ return True
128
+ return False
129
+
130
+ def extract_email_chain(msg):
131
+ """
132
+ Extracts the individual emails from a nested email chain.
133
+ """
134
+ chain = []
135
+ #this is a very basic attempt at parsing the email chain. It is not perfect, and will need to be improved based on specific email formatting.
136
+ body = get_email_body_text(msg)
137
+ if not body:
138
+ return [msg] #if no body, then return the message.
139
+
140
+ #basic email chain splitting.
141
+ emails = re.split(r"(^From:.*?\n^Date:.*?(\n\n|\r\n\r\n))", body, flags=re.MULTILINE | re.DOTALL)
142
+ if len(emails) > 1:
143
+ for i in range(1, len(emails), 2):
144
+ email_part = emails[i] + emails[i+1]
145
+ try:
146
+ chain.append(email.message_from_string(email_part))
147
+ except Exception as e:
148
+ print(f"Error parsing email part: {e}")
149
+ pass #if error, skip the email part.
150
+
151
+ if not chain:
152
+ chain = [msg] #if no chain, then return the original email.
153
+
154
+ return chain
155
+
156
+ def extract_text_from_email(email_message):
157
+ try:
158
+ text = ""
159
+ for part in email_message.walk():
160
+ if part.get_content_type() == "text/plain":
161
+ text += part.get_payload(decode=True).decode()
162
+ elif part.get_content_type() == "text/html":
163
+ html = part.get_payload(decode=True).decode()
164
+ text += ''.join(c if ord(c) < 128 else ' ' for c in html.replace("<br>", "\n").replace("<p>", "\n").replace("</p>","\n").replace("<div>","\n").replace("</div>","\n").replace("<span>"," ").replace("</span>", " "))
165
+
166
+ return text
167
+ except Exception as e:
168
+ print(f"Error parsing email: {e}")
169
+ return ""
170
+
171
+ def get_email_body_text(msg):
172
+ """
173
+ Gets the email body text.
174
+ """
175
+ body = ""
176
+ if msg.is_multipart():
177
+ for part in msg.walk():
178
+ if part.get_content_type() == "text/plain":
179
+ body += part.get_payload(decode=True).decode()
180
+ elif part.get_content_type() == "text/html":
181
+ body += part.get_payload(decode=True).decode()
182
+ else:
183
+ body = msg.get_payload(decode=True).decode()
184
+ return body
backend/app/services/gemeni_classification.py ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import google.generativeai as genai
2
+ import json
3
+ import email
4
+ import io
5
+ import PyPDF2
6
+ import docx
7
+ import mimetypes
8
+
9
+
10
+ genai.configure(api_key="AIzaSyBdZsC0DG4OYjitl-OmFHLVqYOrBSFkV-c")
11
+
12
+ # model = genai.GenerativeModel('gemini-pro')
13
+ model = genai.GenerativeModel("gemini-2.0-flash-lite")
14
+
15
+ classification_categories = [
16
+ {"request_type": "Adjustment", "sub_request_types": ["N/A"]},
17
+ {"request_type": "AU Transfer", "sub_request_types": ["N/A"]},
18
+ {"request_type": "Closing Notice", "sub_request_types": ["Reallocation Fees", "Amendment Fees", "Reallocation Principal"]},
19
+ {"request_type": "Commitment Change", "sub_request_types": ["Cashless Roll", "Decrease", "Increase"]},
20
+ {"request_type": "Fee Payment", "sub_request_types": ["Ongoing Fee", "Letter of Credit Fee"]},
21
+ {"request_type": "Money Movement - Inbound", "sub_request_types": ["Principal", "Interest", "Principal + Interest", "Principal + Interest + Fee"]},
22
+ {"request_type": "Money Movement - Outbound", "sub_request_types": ["Timebound", "Foreign Currency"]},
23
+ {"request_type": "Account Opening", "sub_request_types": ["Checking Account", "Savings Account", "Money Market Account", "Certificate of Deposit (CD)"]},
24
+ {"request_type": "Account Closing", "sub_request_types": ["Checking Account", "Savings Account", "Money Market Account", "Certificate of Deposit (CD)"]},
25
+ {"request_type": "Balance Inquiry", "sub_request_types": ["Checking Account", "Savings Account", "Loan Account", "Credit Card Account"]},
26
+ {"request_type": "Statement Request", "sub_request_types": ["Checking Account", "Savings Account", "Loan Account", "Credit Card Account"]},
27
+ {"request_type": "Transaction History Request", "sub_request_types": ["Checking Account", "Savings Account", "Loan Account", "Credit Card Account"]},
28
+ {"request_type": "Funds Transfer", "sub_request_types": ["Internal Transfer", "External Transfer (ACH, Wire)"]},
29
+ {"request_type": "Stop Payment", "sub_request_types": ["Check", "Electronic Transfer"]},
30
+ {"request_type": "Credit Card Application", "sub_request_types": ["Personal", "Business"]},
31
+ {"request_type": "Credit Limit Change", "sub_request_types": ["Increase", "Decrease"]},
32
+ {"request_type": "Loan Application", "sub_request_types": ["Mortgage", "Auto Loan", "Personal Loan", "Business Loan"]},
33
+ {"request_type": "Loan Disbursement", "sub_request_types": ["Initial Disbursement", "Subsequent Disbursement"]},
34
+ {"request_type": "Loan Payoff", "sub_request_types": ["Principal", "Interest", "Fees"]},
35
+ {"request_type": "Customer Information Update", "sub_request_types": ["Address Change", "Phone Number Change", "Email Address Change"]},
36
+ {"request_type": "Online/Mobile Banking Access", "sub_request_types": ["Enrollment", "Password Reset", "Access Removal"]},
37
+ {"request_type": "Security Request", "sub_request_types": ["Change PIN", "Report Lost/Stolen Card", "Fraud Alert"]}
38
+ ]
39
+
40
+
41
+ def extract_text_from_attachment(attachment_bytes, filename):
42
+ file_type, _ = mimetypes.guess_type(filename)
43
+
44
+ if file_type == 'application/pdf':
45
+ try:
46
+ pdf_file = io.BytesIO(attachment_bytes)
47
+ pdf_reader = PyPDF2.PdfReader(pdf_file)
48
+ text = ""
49
+ for page in pdf_reader.pages:
50
+ text += page.extract_text() or ""
51
+ return text
52
+ except Exception as e:
53
+ print(f"Error extracting PDF: {e}")
54
+ return ""
55
+ elif file_type == 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
56
+ try:
57
+ doc = docx.Document(io.BytesIO(attachment_bytes))
58
+ text = ""
59
+ for paragraph in doc.paragraphs:
60
+ text += paragraph.text + "\n"
61
+ return text
62
+ except Exception as e:
63
+ print(f"Error extracting Word: {e}")
64
+ return ""
65
+ else:
66
+ try:
67
+ return attachment_bytes.decode('utf-8')
68
+ except UnicodeDecodeError:
69
+ print("Unsupported attachment type or encoding.")
70
+ return ""
71
+
72
+
73
+ def extract_text_from_email(email_string):
74
+ try:
75
+ msg = email.message_from_string(email_string)
76
+ text = ""
77
+ for part in msg.walk():
78
+ if part.get_content_type() == "text/plain":
79
+ text += part.get_payload(decode=True).decode()
80
+ elif part.get_content_type() == "text/html":
81
+ #basic html removal, for more robust html, use BeautifulSoup.
82
+ html = part.get_payload(decode=True).decode()
83
+ text += ''.join(c if ord(c) < 128 else ' ' for c in html.replace("<br>", "\n").replace("<p>", "\n").replace("</p>","\n").replace("<div>","\n").replace("</div>","\n").replace("<span>"," ").replace("</span>", " "))
84
+
85
+ return text
86
+ except Exception as e:
87
+ print(f"Error parsing email: {e}")
88
+ return ""
89
+
90
+ def analyze_intent(text):
91
+ prompt = f"Analyze the following text: {text}. What is the primary intent?"
92
+ try:
93
+ response = model.generate_content(prompt)
94
+ return response.text
95
+ except Exception as e:
96
+ print(f"Gemini API error (Intent): {e}")
97
+ return ""
98
+
99
+
100
+ def classify_email_gemeni(subject, body):
101
+ """Classifies an email based on request type and sub-request type."""
102
+ results = []
103
+ categories_string = str(classification_categories)
104
+ PROMPT = f"""
105
+ Analyze the following email and classify it into the most appropriate Request Type and Sub Request Type based on its primary intent.
106
+
107
+ Classification Categories:
108
+ {categories_string}
109
+
110
+ Email Subject: {subject}
111
+ Email Content: {body}
112
+
113
+ Output Format:
114
+ {{
115
+ "request_type": "Request Type",
116
+ "sub_request_type": "Sub Request Type",
117
+ "confidence_score": "Confidence Score (between 0 and 1)",
118
+ }}
119
+ """
120
+
121
+
122
+
123
+ response = model.generate_content(PROMPT)
124
+
125
+ if response and hasattr(response, "_result"):
126
+ text_response = response._result.candidates[0].content.parts[0].text
127
+ text_response = text_response.strip().replace("```json", "").replace("```", "").strip()
128
+ try:
129
+ parsed_json = json.loads(text_response)
130
+
131
+ request_type = parsed_json.get("request_type", "Unknown")
132
+ sub_request_type = parsed_json.get("sub_request_type", "Unknown")
133
+ confidence_score = parsed_json.get("confidence_score", "Unknown")
134
+
135
+ results.extend([request_type,sub_request_type,confidence_score])
136
+ return results
137
+
138
+ except json.JSONDecodeError as e:
139
+ print(f"Exception during JSON Parsing: {e}")
140
+ else:
141
+ print("We did not get response.")
142
+
143
+
144
+
145
+
146
+ def get_primary_intent(email_content, attach_content):
147
+ """Detects primary intent when multiple requests are present."""
148
+
149
+ model = genai.GenerativeModel("gemini-2.0-pro-exp-02-05")
150
+
151
+ prompt = f"""
152
+ Analyze the following email and document, and determine which one has the primary intent.
153
+ email::{email_content}
154
+ document: {attach_content}
155
+ """
156
+ response = model.generate_content(prompt)
157
+ return response.text # Extracted primary intent
158
+
159
+
160
+
backend/app/services/ocr_processor.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytesseract
2
+ import pdfplumber
3
+ from PIL import Image
4
+
5
+ def extract_text_from_pdf(pdf_path: str) -> str:
6
+ """Extracts text from a PDF file."""
7
+ text = ""
8
+ with pdfplumber.open(pdf_path) as pdf:
9
+ for page in pdf.pages:
10
+ text += page.extract_text() + "\n"
11
+ return text.strip()
12
+
13
+ def extract_text_from_image(image_path: str) -> str:
14
+ """Extracts text from an image file using OCR."""
15
+ image = Image.open(image_path)
16
+ return pytesseract.image_to_string(image)
backend/app/services/retrieve_email_process.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ from typing import Optional
3
+ from app.services.duplicate_checker import check_duplicate
4
+ from app.services.email_reader import parse_email_bytes
5
+ from app.services.gemeni_classification import classify_email_gemeni, extract_text_from_attachment
6
+
7
+
8
+ async def process_single_email(file_content: bytes, filename: str) -> Optional[dict]:
9
+ """Processes a single email content."""
10
+ email_data = parse_email_bytes(file_content, filename)
11
+ if email_data:
12
+ attachment_text = ""
13
+ for attachment in email_data["attachments"]:
14
+ attachment_text += extract_text_from_attachment(attachment["content"], attachment["filename"])
15
+
16
+ email_chain_text = email_data["email_chain_text"]
17
+ email_body_text = email_data["body"]
18
+
19
+ # 1. Separate Classification:
20
+ document_result = classify_email_gemeni(email_data["subject"], attachment_text) if attachment_text else ("Unknown", "Unknown", "0")
21
+ email_chain_result = classify_email_gemeni(email_data["subject"], email_chain_text) if email_chain_text else ("Unknown", "Unknown", "0")
22
+ primary_email_result = classify_email_gemeni(email_data["subject"], email_body_text)
23
+
24
+ # 2. Confidence Score Comparison:
25
+ document_confidence = float(document_result[2])
26
+ email_chain_confidence = float(email_chain_result[2])
27
+ primary_email_confidence = float(primary_email_result[2])
28
+
29
+ best_result = primary_email_result # Default to email body
30
+ if document_confidence > primary_email_confidence and document_confidence > email_chain_confidence:
31
+ best_result = document_result
32
+ elif email_chain_confidence > primary_email_confidence and email_chain_confidence > document_confidence:
33
+ best_result = email_chain_result
34
+
35
+ # 3. Refined Classification:
36
+ request_type = best_result[0]
37
+ sub_request_type = best_result[1]
38
+ confidence_score = best_result[2]
39
+
40
+ duplicate_flag, duplicate_reason = check_duplicate(email_data["body"])
41
+ email_obj = {
42
+ "sender": email_data["sender"],
43
+ "subject": email_data["subject"],
44
+ "request_type": request_type,
45
+ "sub_request_type": sub_request_type,
46
+ "confidence_score": confidence_score,
47
+ "duplicate_flag": duplicate_flag,
48
+ }
49
+ return email_obj
50
+ else:
51
+ print(f"Parsing failed for file: {filename}")
52
+ return None
backend/config/settings.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+
4
+ # Load environment variables from .env file
5
+ load_dotenv()
6
+
7
+
8
+ class Settings:
9
+ ENV = os.getenv("ENV", "local") # Default to "local" if ENV is not set
10
+ # API Token
11
+ HUGGINGFACE_API_TOKEN = os.getenv("HUGGINGFACE_API_TOKEN") or os.environ.get("HUGGINGFACE_API_TOKEN")
12
+
13
+ # Model Path
14
+ MODEL_NAME = os.getenv("MODEL_NAME") or os.environ.get("MODEL_NAME") # Default if not set
15
+ OCR_LANGUAGE = os.getenv("OCR_LANGUAGE", "eng")
16
+ directory_path = os.getenv("EMAIL_DIRECTORY_PATH") or os.environ.get("EMAIL_DIRECTORY_PATH") # Make configurable
17
+ #MODEL_NAME = "meta-llama/Llama-2-7b"
18
+
19
+ # Ensure model path exists
20
+ # if not os.path.exists(MODEL_PATH):
21
+ # os.makedirs(MODEL_PATH, exist_ok=True)
22
+
23
+ # # Validate required variables
24
+ # if not HUGGINGFACE_API_TOKEN:
25
+ # raise ValueError("Missing HUGGINGFACE_API_TOKEN. Please set it in the environment variables or .env file.")
26
+
27
+ # Debugging info
28
+ print(f"Running in {ENV} mode with model path: {MODEL_NAME}")
29
+ print(f"Running in {ENV} mode with TOKEN: {HUGGINGFACE_API_TOKEN}")
30
+
31
+ settings = Settings()
32
+
33
+
34
+
backend/main.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, FastAPI
2
+ from app.api.endpoints import router
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+
5
+
6
+ app = FastAPI(title="Gen AI Email Processing API") # This creates the FastAPI app instance
7
+
8
+ #router = APIRouter()
9
+ app.add_middleware(
10
+ CORSMiddleware,
11
+ allow_origins=["*"], # Replace "*" with your frontend domain in production
12
+ allow_credentials=True,
13
+ allow_methods=["*"],
14
+ allow_headers=["*"],
15
+ )
16
+
17
+ # Include API routes from endpoints.py
18
+ app.include_router(router, prefix="/api") # You can remove prefix if not needed
19
+
20
+ @app.get("/")
21
+ async def root():
22
+ return {"message": "Hello from FastAPI"}
23
+
24
+ #app.include_router(router)
backend/requirements.txt ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ fastapi==0.115.11
3
+ uvicorn==0.34.0
4
+ pydantic==2.10.6
5
+ pdfplumber==0.11.5
6
+ python-docx==1.1.2
7
+ pytesseract==0.3.13
8
+ numpy==2.2.4
9
+ pandas==2.2.3
10
+ scikit-learn==1.6.1
11
+ tika==2.6.0
12
+ unstructured==0.17.2
13
+ pypdf==5.4.0
14
+ langchain==0.3.21
15
+ python-dotenv
16
+ PyPDF2