Jashan1 commited on
Commit
c004cea
·
verified ·
1 Parent(s): 0b3e3d5

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +953 -0
app.py ADDED
@@ -0,0 +1,953 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import streamlit as st
3
+ from openai import OpenAI
4
+ from PyPDF2 import PdfReader
5
+ import requests
6
+ from youtube_transcript_api import YouTubeTranscriptApi, NoTranscriptFound, TranscriptsDisabled
7
+ from urllib.parse import urlparse, parse_qs
8
+ from pinecone import Pinecone
9
+ import uuid
10
+ from dotenv import load_dotenv
11
+ import time
12
+ from concurrent.futures import ThreadPoolExecutor, as_completed
13
+ from itertools import islice
14
+ import unicodedata
15
+ import re
16
+ from tiktoken import encoding_for_model
17
+ import json
18
+ import datetime
19
+ import tiktoken
20
+ import pandas as pd
21
+ import io
22
+ from fpdf import FPDF
23
+ import tempfile
24
+ from PyPDF2 import PdfReader
25
+ import base64
26
+ from pathlib import Path
27
+ import numpy as np
28
+ from pymongo import MongoClient
29
+ import traceback
30
+ from docx import Document
31
+ import pandas as pd
32
+ import io
33
+ import time
34
+ import traceback
35
+
36
+ load_dotenv()
37
+
38
+ # Set up OpenAI client
39
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
40
+
41
+ # Set up Pinecone
42
+ pc = Pinecone(api_key=os.getenv("PINECONE_API_KEY"))
43
+
44
+ # Get index name and URL from .env
45
+ index_name = os.getenv("PINECONE_INDEX_NAME")
46
+ index_url = os.getenv("PINECONE_INDEX_URL")
47
+
48
+ index = pc.Index(index_name, url=index_url)
49
+
50
+ # Set up MongoDB connection
51
+ mongo_client = MongoClient(os.getenv("MONGODB_URI"))
52
+ db = mongo_client.get_database("finance")
53
+ users_collection = db["users"]
54
+
55
+ def get_embedding(text):
56
+ response = client.embeddings.create(input=text, model="text-embedding-3-large")
57
+ return response.data[0].embedding
58
+
59
+ def chunk_text(text, content_type):
60
+ sanitized_text = sanitize_text(text)
61
+ if content_type == "YouTube":
62
+ chunk_size = 2000 # Adjust this value as needed
63
+ content_length = len(sanitized_text)
64
+ return [sanitized_text[i:i+chunk_size] for i in range(0, content_length, chunk_size)]
65
+ else: # Default for PDF and Web Link
66
+ chunk_size = 2000
67
+ content_length = len(sanitized_text)
68
+ return [sanitized_text[i:i+chunk_size] for i in range(0, content_length, chunk_size)]
69
+
70
+ def process_pdf(file):
71
+ reader = PdfReader(file)
72
+ text = []
73
+ for page in reader.pages:
74
+ text.append(page.extract_text())
75
+ return " ".join(text) # Join all pages into a single string
76
+
77
+ def process_web_link(url):
78
+ response = requests.get(url)
79
+ return chunk_text(response.text, "Web")
80
+
81
+ def process_youtube_link(url):
82
+ try:
83
+ video_id = extract_video_id(url)
84
+ transcript = YouTubeTranscriptApi.get_transcript(video_id)
85
+ full_text = " ".join([entry['text'] for entry in transcript])
86
+ print("Transcript obtained from YouTube API")
87
+ return chunk_text(full_text, "YouTube") # Use chunk_text function to split large transcripts
88
+ except NoTranscriptFound:
89
+ print("No transcript found for this YouTube video.")
90
+ return []
91
+ except TranscriptsDisabled:
92
+ print("Transcripts are disabled for this YouTube video.")
93
+ return []
94
+ except Exception as e:
95
+ print(f"An error occurred while processing the YouTube link: {str(e)}")
96
+ return []
97
+
98
+ def extract_video_id(url):
99
+ parsed_url = urlparse(url)
100
+ if parsed_url.hostname == 'youtu.be':
101
+ return parsed_url.path[1:]
102
+ if parsed_url.hostname in ('www.youtube.com', 'youtube.com'):
103
+ if parsed_url.path == '/watch':
104
+ return parse_qs(parsed_url.query)['v'][0]
105
+ if parsed_url.path[:7] == '/embed/':
106
+ return parsed_url.path.split('/')[2]
107
+ if parsed_url.path[:3] == '/v/':
108
+ return parsed_url.path.split('/')[2]
109
+ return None
110
+
111
+ def process_upload(upload_type, file_or_link, file_name=None):
112
+ print(f"Starting process_upload for {upload_type}")
113
+ doc_id = str(uuid.uuid4())
114
+ print(f"Generated doc_id: {doc_id}")
115
+
116
+ if upload_type == "PDF":
117
+ chunks = process_pdf(file_or_link)
118
+ doc_name = file_name or "Uploaded PDF"
119
+ elif upload_type == "Web Link":
120
+ chunks = process_web_link(file_or_link)
121
+ doc_name = file_or_link
122
+ elif upload_type == "YouTube Link":
123
+ chunks = process_youtube_link(file_or_link)
124
+ doc_name = f"YouTube: {file_or_link}"
125
+ else:
126
+ print("Invalid upload type")
127
+ return "Invalid upload type"
128
+
129
+ vectors = []
130
+ with ThreadPoolExecutor() as executor:
131
+ futures = [executor.submit(process_chunk, chunk, doc_id, i, upload_type, doc_name) for i, chunk in enumerate(chunks)]
132
+
133
+ for future in as_completed(futures):
134
+ vectors.append(future.result())
135
+ # Update progress
136
+ progress = len(vectors) / len(chunks)
137
+ st.session_state.upload_progress.progress(progress)
138
+
139
+ print(f"Generated {len(vectors)} vectors")
140
+
141
+ # Upsert vectors in batches
142
+ batch_size = 100 # Adjust this value as needed
143
+ for i in range(0, len(vectors), batch_size):
144
+ batch = list(islice(vectors, i, i + batch_size))
145
+ index.upsert(vectors=batch)
146
+ print(f"Upserted batch {i//batch_size + 1} of {len(vectors)//batch_size + 1}")
147
+
148
+ print("All vectors upserted to Pinecone")
149
+
150
+ return f"Processing complete for {upload_type}. Document Name: {doc_name}"
151
+
152
+ def process_chunk(chunk, doc_id, i, upload_type, doc_name):
153
+ # Sanitize the chunk text
154
+ sanitized_chunk = sanitize_text(chunk)
155
+ embedding = get_embedding(sanitized_chunk)
156
+ return (f"{doc_id}_{i}", embedding, {
157
+ "text": sanitized_chunk,
158
+ "type": upload_type,
159
+ "doc_id": doc_id,
160
+ "doc_name": doc_name,
161
+ "chunk_index": i
162
+ })
163
+
164
+ def sanitize_text(text):
165
+ # Remove control characters and normalize Unicode
166
+ return ''.join(ch for ch in unicodedata.normalize('NFKD', text) if unicodedata.category(ch)[0] != 'C')
167
+
168
+ def get_relevant_context(query, top_k=5):
169
+ print(f"Getting relevant context for query: {query}")
170
+ query_embedding = get_embedding(query)
171
+
172
+ search_results = index.query(vector=query_embedding, top_k=top_k, include_metadata=True)
173
+ print(f"Found {len(search_results['matches'])} relevant results")
174
+
175
+ # Sort results by similarity score (higher is better)
176
+ sorted_results = sorted(search_results['matches'], key=lambda x: x['score'], reverse=True)
177
+
178
+ context = "\n".join([result['metadata']['text'] for result in sorted_results])
179
+ return context, sorted_results
180
+
181
+ def truncate_context(context, max_tokens):
182
+ enc = encoding_for_model("gpt-4o-mini")
183
+ encoded = enc.encode(context)
184
+ if len(encoded) > max_tokens:
185
+ return enc.decode(encoded[:max_tokens])
186
+ return context
187
+
188
+ def chat_with_ai(message):
189
+ print(f"Chatting with AI, message: {message}")
190
+ context, results = get_relevant_context(message)
191
+ print(f"Retrieved context, length: {len(context)}")
192
+
193
+ # Truncate context if it's too long
194
+ max_tokens = 7000 # Leave some room for the system message and user query
195
+ context = truncate_context(context, max_tokens)
196
+
197
+ messages = [
198
+ {"role": "system", "content": "You are a helpful assistant. Use the following information to answer the user's question, but don't mention the context directly in your response. If the information isn't in the context, say you don't know."},
199
+ {"role": "system", "content": f"Context: {context}"},
200
+ {"role": "user", "content": message}
201
+ ]
202
+
203
+ response = client.chat.completions.create(
204
+ model="gpt-4o-mini",
205
+ messages=messages
206
+ )
207
+ print("Received response from OpenAI")
208
+
209
+ ai_response = response.choices[0].message.content
210
+
211
+ # Prepare source information
212
+ sources = [
213
+ {
214
+ "doc_id": result['metadata']['doc_id'],
215
+ "doc_name": result['metadata']['doc_name'],
216
+ "chunk_index": result['metadata']['chunk_index'],
217
+ "text": result['metadata']['text'],
218
+ "type": result['metadata']['type'],
219
+ "score": result['score']
220
+ }
221
+ for result in results
222
+ ]
223
+
224
+ return ai_response, sources
225
+
226
+ def process_youtube_links(links):
227
+ results = []
228
+ for link in links:
229
+ result = process_upload("YouTube Link", link.strip())
230
+ results.append(result)
231
+ return results
232
+
233
+ def process_excel(file):
234
+ dfs = pd.read_excel(file, sheet_name=None) # Read all sheets
235
+ for sheet_name, df in dfs.items():
236
+ df.columns = df.columns.astype(str)
237
+ # Remove any unnamed columns
238
+ df = df.loc[:, ~df.columns.str.contains('^Unnamed')]
239
+ return dfs
240
+
241
+ def analyze_and_generate_formulas(main_df, other_dfs):
242
+ # Focus on the 'DETAILS' column and the month columns
243
+ details_column = main_df.columns[0]
244
+ month_columns = main_df.columns[1:-1] # Exclude the 'Total' column
245
+
246
+ main_summary = f"DETAILS column data: {main_df[details_column].tolist()}\n"
247
+ for month in month_columns:
248
+ main_summary += f"{month} column data: {main_df[month].tolist()}\n"
249
+
250
+ other_sheets_summary = ""
251
+ for name, df in other_dfs.items():
252
+ if len(df.columns) > 0:
253
+ other_sheets_summary += f"\nSheet '{name}' structure:\n"
254
+ other_sheets_summary += f"Columns: {df.columns.tolist()}\n"
255
+ other_sheets_summary += f"First few rows:\n{df.head().to_string()}\n"
256
+
257
+ prompt = f"""Analyze the following Excel data and generate Python formulas to fill missing values:
258
+
259
+ Main Sheet Structure:
260
+ {main_summary}
261
+
262
+ Other Sheets:
263
+ {other_sheets_summary}
264
+
265
+ Provide Python formulas using pandas to fill missing values in the month columns of the main sheet.
266
+ Use pandas and numpy functions where appropriate. If a value cannot be determined, use None.
267
+ Return a dictionary with column names as keys and formulas as values.
268
+ Example of the expected format: {{'Apr'24': "df['Apr'24'].fillna(method='ffill')"}}
269
+ """
270
+
271
+ try:
272
+ response = client.chat.completions.create(
273
+ model="gpt-4o-mini",
274
+ messages=[
275
+ {"role": "system", "content": "You are a data analysis expert skilled in creating concise formulas for data filling."},
276
+ {"role": "user", "content": prompt}
277
+ ]
278
+ )
279
+
280
+ formulas = eval(response.choices[0].message.content.strip())
281
+ print(f"Generated formulas: {formulas}")
282
+
283
+ return formulas
284
+
285
+ except Exception as e:
286
+ print(f"Error in analyze_and_generate_formulas: {str(e)}")
287
+ print(traceback.format_exc())
288
+ return {}
289
+
290
+ def apply_formulas(main_df, other_dfs, formulas):
291
+ filled_df = main_df.copy()
292
+
293
+ for column, formula in formulas.items():
294
+ if column in filled_df.columns:
295
+ try:
296
+ print(f"Applying formula for column {column}: {formula}")
297
+
298
+ # Create a local namespace with all dataframes
299
+ namespace = {'df': filled_df, 'np': np, 'pd': pd}
300
+
301
+ # Execute the formula in the namespace
302
+ exec(f"result = {formula}", namespace)
303
+
304
+ # Apply the result to the column
305
+ filled_df[column] = namespace['result']
306
+ print(f"Successfully applied formula for column {column}")
307
+ except Exception as e:
308
+ print(f"Error applying formula for column {column}: {str(e)}")
309
+ print(traceback.format_exc())
310
+
311
+ return filled_df
312
+
313
+ def excel_to_pdf(df):
314
+ pdf = FPDF(orientation='L', unit='mm', format='A4')
315
+ pdf.add_page()
316
+ pdf.set_font("Arial", size=8)
317
+
318
+ # Calculate column widths
319
+ col_widths = [pdf.get_string_width(str(col)) + 6 for col in df.columns]
320
+
321
+ # Write header
322
+ for i, col in enumerate(df.columns):
323
+ pdf.cell(col_widths[i], 10, str(col), border=1)
324
+ pdf.ln()
325
+
326
+ # Write data
327
+ for _, row in df.iterrows():
328
+ for i, value in enumerate(row):
329
+ pdf.cell(col_widths[i], 10, str(value), border=1)
330
+ pdf.ln()
331
+
332
+ # Save to a temporary file
333
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as temp_file:
334
+ pdf.output(temp_file.name)
335
+ return temp_file.name
336
+
337
+ def pdf_to_text(pdf_path):
338
+ with open(pdf_path, 'rb') as file:
339
+ pdf = PdfReader(file)
340
+ text = []
341
+ for page in pdf.pages:
342
+ text.append(page.extract_text())
343
+ return text
344
+
345
+ def get_user_feedback(user_id):
346
+ user = users_collection.find_one({"_id": user_id})
347
+ return user.get("feedback", "") if user else ""
348
+
349
+ def get_category_reports():
350
+ return {
351
+ "Default": [], # Changed from "None" to "Default"
352
+ "Sales KPI": [
353
+ "Monthwise Sales Table", "Customer-wise Sales Table (top 10)", "Qty Sales",
354
+ "Customer-wise Churn", "Avg Sales per Customer", "Product-wise Sales Rate",
355
+ "Geography-wise", "Trend Analysis", "Graphs", "Month-wise Comparison"
356
+ ],
357
+ "Expenses KPI": [
358
+ "Vendor-wise Comparison", "Year on Year Monthwise", "Division-wise"
359
+ ],
360
+ "Purchase Register": [
361
+ "Vendor-wise Monthwise", "Monthwise", "Material-wise Purchase Rate Analysis"
362
+ ],
363
+ "Balance Sheet": [
364
+ "Year on Year Comparison", "Ratios"
365
+ ]
366
+ }
367
+
368
+ def analyze_excel_with_gpt(df, sheet_name, user_feedback, category, reports_needed, use_assistants_api=False):
369
+ if use_assistants_api:
370
+ return process_excel_with_assistant(df, category, reports_needed, user_feedback)
371
+ else:
372
+ # Existing OCR-based analysis code
373
+ prompt = f"""Analyze the following Excel data from sheet '{sheet_name}':
374
+
375
+ {df.to_string()}
376
+
377
+ User's previous feedback and insights:
378
+ {user_feedback}
379
+
380
+ """
381
+
382
+ if category != "general":
383
+ prompt += f"""Please provide analysis and insights based on the following required reports for the category '{category}':
384
+ {', '.join(reports_needed)}
385
+
386
+ Please provide:
387
+ 1. A comprehensive overview of the data focusing on the {category} category
388
+ 2. Key observations and trends related to the required reports
389
+ 3. Any anomalies, interesting patterns, or correlations relevant to the {category}
390
+ 4. Suggestions for further analysis or visualization based on the required reports
391
+ 5. Address any previous feedback or insights mentioned above, if applicable
392
+
393
+ Focus on providing a thorough analysis of all aspects of the data relevant to the {category} and the specified reports."""
394
+ else:
395
+ prompt += """Please provide a general analysis of the data, including:
396
+ 1. A comprehensive overview of the data
397
+ 2. Key observations and trends
398
+ 3. Any anomalies, interesting patterns, or correlations
399
+ 4. Suggestions for further analysis or visualization
400
+ 5. Address any previous feedback or insights mentioned above, if applicable
401
+
402
+ Focus on providing a thorough analysis of all aspects of the data."""
403
+
404
+ response = client.chat.completions.create(
405
+ model="gpt-4o-mini",
406
+ messages=[
407
+ {"role": "system", "content": f"You are a data analyst expert in interpreting Excel data for {'general' if category == 'general' else category} analysis."},
408
+ {"role": "user", "content": prompt}
409
+ ]
410
+ )
411
+
412
+ return response.choices[0].message.content
413
+
414
+ def analyze_document_with_gpt(document_content, user_feedback, category, reports_needed, use_assistants_api=False, file_id=None):
415
+ if use_assistants_api:
416
+ assistant = client.beta.assistants.create(
417
+ name="Document Analyzer",
418
+ instructions=f"You are a document analysis expert. Analyze the uploaded document and provide insights based on the category: {category}.",
419
+ model="gpt-4-1106-preview"
420
+ )
421
+
422
+ thread = client.beta.threads.create()
423
+
424
+ message = client.beta.threads.messages.create(
425
+ thread_id=thread.id,
426
+ role="user",
427
+ content=f"Analyze the document with file ID: {file_id}. Category: {category}. Required reports: {', '.join(reports_needed)}. User feedback: {user_feedback}",
428
+ file_ids=[file_id]
429
+ )
430
+
431
+ run = client.beta.threads.runs.create(
432
+ thread_id=thread.id,
433
+ assistant_id=assistant.id
434
+ )
435
+
436
+ while run.status != "completed":
437
+ run = client.beta.threads.runs.retrieve(thread_id=thread.id, run_id=run.id)
438
+ time.sleep(1)
439
+
440
+ messages = client.beta.threads.messages.list(thread_id=thread.id)
441
+ return messages.data[0].content[0].text.value
442
+ else:
443
+ # Existing OCR-based analysis code
444
+ prompt = f"""Analyze the following document content:
445
+
446
+ {document_content}
447
+
448
+ User's previous feedback and insights:
449
+ {user_feedback}
450
+
451
+ """
452
+
453
+ if category != "general":
454
+ prompt += f"""Please provide analysis and insights based on the following required reports for the category '{category}':
455
+ {', '.join(reports_needed)}
456
+
457
+ Please provide:
458
+ 1. A comprehensive overview of the content focusing on the {category} category
459
+ 2. Key points and main ideas related to the required reports
460
+ 3. Any interesting patterns or unique aspects relevant to the {category}
461
+ 4. Suggestions for further analysis or insights based on the required reports
462
+ 5. Any limitations of the analysis due to the document format or OCR process
463
+ 6. Address any previous feedback or insights mentioned above, if applicable
464
+
465
+ Focus on providing a thorough analysis of all aspects of the content relevant to the {category} and the specified reports."""
466
+ else:
467
+ prompt += """Please provide a general analysis of the document content, including:
468
+ 1. A comprehensive overview of the content
469
+ 2. Key points and main ideas
470
+ 3. Any interesting patterns or unique aspects
471
+ 4. Suggestions for further analysis or insights
472
+ 5. Any limitations of the analysis due to the document format or OCR process
473
+ 6. Address any previous feedback or insights mentioned above, if applicable
474
+
475
+ Focus on providing a thorough analysis of all aspects of the content."""
476
+
477
+ response = client.chat.completions.create(
478
+ model="gpt-4o-mini",
479
+ messages=[
480
+ {"role": "system", "content": f"You are a data analyst expert in interpreting complex document content for {'general' if category == 'general' else category} analysis."},
481
+ {"role": "user", "content": prompt}
482
+ ]
483
+ )
484
+
485
+ return response.choices[0].message.content
486
+
487
+ def process_uploaded_file(uploaded_file):
488
+ file_type = uploaded_file.type
489
+ if file_type in ["application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.ms-excel"]:
490
+ # Process Excel file
491
+ dfs = process_excel(uploaded_file)
492
+ return "excel", dfs
493
+ elif file_type == "application/pdf":
494
+ # Process PDF file using PyPDF2
495
+ try:
496
+ pdf_reader = PdfReader(uploaded_file)
497
+ text = ""
498
+ for page in pdf_reader.pages:
499
+ text += page.extract_text()
500
+ return "text", text
501
+ except Exception as e:
502
+ st.error(f"Error processing PDF: {str(e)}")
503
+ print(f"Error processing PDF: {str(e)}")
504
+ print(traceback.format_exc())
505
+ return None, None
506
+ elif file_type in ["application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/msword"]:
507
+ # Process Word document
508
+ try:
509
+ doc = Document(io.BytesIO(uploaded_file.read()))
510
+ text = "\n".join([paragraph.text for paragraph in doc.paragraphs])
511
+ return "text", text
512
+ except Exception as e:
513
+ st.error(f"Error processing Word document: {str(e)}")
514
+ print(f"Error processing Word document: {str(e)}")
515
+ print(traceback.format_exc())
516
+ return None, None
517
+ else:
518
+ st.error(f"Unsupported file type: {file_type}. Please upload an Excel file, PDF, or Word document.")
519
+ return None, None
520
+
521
+ def chat_with_data(data, user_question, data_type):
522
+ if data_type == "excel":
523
+ df_string = data.to_string()
524
+ data_description = "Excel sheet data"
525
+ else: # PDF or Word document
526
+ df_string = data
527
+ data_description = "document content"
528
+
529
+ prompt = f"""You are an AI assistant specialized in analyzing {data_description}. You have access to the following data:
530
+
531
+ {df_string}
532
+
533
+ Based on this data, please answer the following question:
534
+ {user_question}
535
+
536
+ Provide a detailed and accurate answer based on the given data. If the answer cannot be directly inferred from the data, provide the best possible response based on the available information and your general knowledge about data analysis."""
537
+
538
+ response = client.chat.completions.create(
539
+ model="gpt-4o-mini",
540
+ messages=[
541
+ {"role": "system", "content": f"You are a data analysis expert skilled in interpreting {data_description}."},
542
+ {"role": "user", "content": prompt}
543
+ ]
544
+ )
545
+
546
+ return response.choices[0].message.content
547
+
548
+ def extract_challan_data(pdf_text):
549
+ data = {}
550
+ patterns = {
551
+ 'ITNS No.': r'ITNS No\.\s*:\s*(\d+)',
552
+ 'TAN': r'TAN\s*:\s*(\w+)',
553
+ 'Name': r'Name\s*:\s*(.+)',
554
+ 'Assessment Year': r'Assessment Year\s*:\s*(\d{4}-\d{2})',
555
+ 'Financial Year': r'Financial Year\s*:\s*(\d{4}-\d{2})',
556
+ 'Amount': r'Amount \(in Rs\.\)\s*:\s*₹\s*([\d,]+)',
557
+ 'CIN': r'CIN\s*:\s*(\w+)',
558
+ 'Date of Deposit': r'Date of Deposit\s*:\s*(\d{2}-\w{3}-\d{4})',
559
+ 'Challan No': r'Challan No\s*:\s*(\d+)',
560
+ }
561
+
562
+ for key, pattern in patterns.items():
563
+ match = re.search(pattern, pdf_text)
564
+ if match:
565
+ data[key] = match.group(1)
566
+ else:
567
+ data[key] = 'N/A'
568
+
569
+ return data
570
+
571
+ def process_challan_pdfs(pdf_files):
572
+ all_data = []
573
+ for pdf_file in pdf_files:
574
+ pdf_text = process_pdf(pdf_file)
575
+ challan_data = extract_challan_data(pdf_text)
576
+ all_data.append(challan_data)
577
+
578
+ df = pd.DataFrame(all_data)
579
+ return df
580
+
581
+ def process_file_with_assistant(file, file_type, category, reports_needed, user_feedback):
582
+ print(f"Starting {file_type} processing with Assistant")
583
+ try:
584
+ # Upload the file to OpenAI
585
+ uploaded_file = client.files.create(
586
+ file=file,
587
+ purpose='assistants'
588
+ )
589
+ print(f"File uploaded successfully. File ID: {uploaded_file.id}")
590
+
591
+ # Create an assistant
592
+ assistant = client.beta.assistants.create(
593
+ name=f"{file_type} Analyzer",
594
+ instructions=f"You are an expert in analyzing {file_type} files, focusing on {category}. Provide insights and summaries of the content based on the following reports: {', '.join(reports_needed)}. Consider the user's previous feedback: {user_feedback}",
595
+ model="gpt-4o",
596
+ tools=[{"type": "file_search"}]
597
+ )
598
+ print(f"Assistant created. Assistant ID: {assistant.id}")
599
+
600
+ # Create a thread
601
+ thread = client.beta.threads.create()
602
+ print(f"Thread created. Thread ID: {thread.id}")
603
+
604
+ # Add a message to the thread with the file attachment
605
+ message = client.beta.threads.messages.create(
606
+ thread_id=thread.id,
607
+ role="user",
608
+ content=f"Please analyze this file and provide insights for the {category} category, focusing on the following reports: {', '.join(reports_needed)}.",
609
+ attachments=[
610
+ {"file_id": uploaded_file.id, "tools": [{"type": "file_search"}]}
611
+ ]
612
+ )
613
+ print(f"Message added to thread. Message ID: {message.id}")
614
+
615
+ # Run the assistant
616
+ run = client.beta.threads.runs.create(
617
+ thread_id=thread.id,
618
+ assistant_id=assistant.id
619
+ )
620
+ print(f"Run created. Run ID: {run.id}")
621
+
622
+ # Wait for the run to complete
623
+ while run.status != 'completed':
624
+ run = client.beta.threads.runs.retrieve(thread_id=thread.id, run_id=run.id)
625
+ print(f"Run status: {run.status}")
626
+ time.sleep(1)
627
+
628
+ # Retrieve the messages
629
+ messages = client.beta.threads.messages.list(thread_id=thread.id)
630
+
631
+ # Extract the assistant's response
632
+ analysis_result = next((msg.content[0].text.value for msg in messages if msg.role == 'assistant'), None)
633
+
634
+ print(f"{file_type} analysis completed successfully")
635
+ return analysis_result
636
+
637
+ except Exception as e:
638
+ print(f"Error in process_file_with_assistant: {str(e)}")
639
+ print(traceback.format_exc())
640
+ return None
641
+
642
+ # Streamlit UI
643
+ st.set_page_config(layout="wide")
644
+ st.title("Document Processing, Chat, Excel Filling, and Analysis")
645
+
646
+ # Add login/signup system
647
+ if "user" not in st.session_state:
648
+ st.session_state.user = None
649
+
650
+ def login(username, password):
651
+ user = users_collection.find_one({"username": username})
652
+ if user and user.get("password") == password:
653
+ return user
654
+ return None
655
+
656
+ def signup(username, password):
657
+ existing_user = users_collection.find_one({"username": username})
658
+ if existing_user:
659
+ return False
660
+ users_collection.insert_one({
661
+ "username": username,
662
+ "password": password,
663
+ "feedback": [] # Initialize an empty list for feedback
664
+ })
665
+ return True
666
+
667
+ def store_feedback(username, feedback):
668
+ users_collection.update_one(
669
+ {"username": username},
670
+ {"$push": {"feedback": feedback}},
671
+ upsert=True
672
+ )
673
+
674
+ # Login/Signup form
675
+ if not st.session_state.user:
676
+ tab1, tab2 = st.tabs(["Login", "Sign Up"])
677
+
678
+ with tab1:
679
+ st.subheader("Login")
680
+ login_username = st.text_input("Username", key="login_username")
681
+ login_password = st.text_input("Password", type="password", key="login_password")
682
+ if st.button("Login"):
683
+ user = login(login_username, login_password)
684
+ if user:
685
+ st.session_state.user = user
686
+ st.success("Logged in successfully!")
687
+ st.rerun()
688
+ else:
689
+ st.error("Invalid username or password")
690
+
691
+ with tab2:
692
+ st.subheader("Sign Up")
693
+ signup_username = st.text_input("Username", key="signup_username")
694
+ signup_password = st.text_input("Password", type="password", key="signup_password")
695
+ if st.button("Sign Up"):
696
+ if signup(signup_username, signup_password):
697
+ st.success("Account created successfully! Please log in.")
698
+ else:
699
+ st.error("Username already exists")
700
+
701
+ if st.session_state.user:
702
+ st.write(f"Welcome, {st.session_state.user['username']}!")
703
+
704
+ # Create four tabs
705
+ tab1, tab2, tab3, tab4 = st.tabs(["Upload, Chat, and Source", "Excel Processing", "Excel Analysis and Chat", "Challan Processing"])
706
+
707
+ with tab1:
708
+ st.subheader("Upload")
709
+
710
+ # PDF upload
711
+ uploaded_files = st.file_uploader("Choose one or more PDF files", type="pdf", accept_multiple_files=True)
712
+
713
+ # Web Link input
714
+ web_link = st.text_input("Enter a Web Link")
715
+
716
+ # YouTube Links input
717
+ youtube_links = st.text_area("Enter YouTube Links (one per line)")
718
+
719
+ if st.button("Process All"):
720
+ st.session_state.upload_progress = st.progress(0)
721
+ with st.spinner("Processing uploads..."):
722
+ results = []
723
+ if uploaded_files:
724
+ for file in uploaded_files:
725
+ pdf_result = process_upload("PDF", file, file.name)
726
+ results.append(pdf_result)
727
+ if web_link:
728
+ web_result = process_upload("Web Link", web_link)
729
+ results.append(web_result)
730
+ if youtube_links:
731
+ youtube_links_list = re.split(r'[\n\r]+', youtube_links.strip())
732
+ youtube_results = process_youtube_links(youtube_links_list)
733
+ results.extend(youtube_results)
734
+
735
+ if results:
736
+ for result in results:
737
+ st.success(result)
738
+ else:
739
+ st.warning("No content uploaded. Please provide at least one input.")
740
+ st.session_state.upload_progress.empty()
741
+
742
+ st.subheader("Chat")
743
+ user_input = st.text_input("Ask a question about the uploaded content:")
744
+ if st.button("Send"):
745
+ if user_input:
746
+ print(f"Sending user input: {user_input}")
747
+ st.session_state.chat_progress = st.progress(0)
748
+ response, sources = chat_with_ai(user_input)
749
+ st.session_state.chat_progress.progress(1.0)
750
+ st.markdown("**You:** " + user_input)
751
+ st.markdown("**AI:** " + response)
752
+
753
+ # Store sources in session state for display in the Source Chunks section
754
+ st.session_state.sources = sources
755
+ st.session_state.chat_progress.empty()
756
+ else:
757
+ print("Empty user input")
758
+ st.warning("Please enter a question.")
759
+
760
+ st.subheader("Source Chunks")
761
+ if 'sources' in st.session_state and st.session_state.sources:
762
+ for i, source in enumerate(st.session_state.sources, 1):
763
+ with st.expander(f"Source {i} - {source['type']} ({source['doc_name']}) - Score: {source['score']}"):
764
+ st.markdown(f"**Chunk Index:** {source['chunk_index']}")
765
+ st.text(source['text'])
766
+ else:
767
+ st.info("Ask a question to see source chunks here.")
768
+
769
+ with tab2:
770
+ st.subheader("Excel Processing")
771
+ uploaded_excel = st.file_uploader("Choose an Excel file", type=["xlsx", "xls"])
772
+
773
+ if uploaded_excel is not None:
774
+ dfs = process_excel(uploaded_excel)
775
+
776
+ # Display preview of each sheet
777
+ for sheet_name, df in dfs.items():
778
+ if not df.empty:
779
+ st.write(f"Preview of {sheet_name}:")
780
+ st.dataframe(df.head())
781
+
782
+ # Select the main sheet for processing
783
+ main_sheet = st.selectbox("Select the main sheet to fill", list(dfs.keys()))
784
+ main_df = dfs[main_sheet]
785
+
786
+ if st.button("Fill Missing Data"):
787
+ with st.spinner("Analyzing data and generating formulas..."):
788
+ other_dfs = {name: df for name, df in dfs.items() if name != main_sheet}
789
+ formulas = analyze_and_generate_formulas(main_df, other_dfs)
790
+
791
+ if formulas:
792
+ st.write("Generated Formulas:")
793
+ for column, formula in formulas.items():
794
+ st.code(f"{column}: {formula}")
795
+
796
+ filled_df = apply_formulas(main_df, other_dfs, formulas)
797
+
798
+ st.write("Filled Excel Data:")
799
+ st.dataframe(filled_df)
800
+
801
+ # Provide download link for the filled Excel file
802
+ buffer = io.BytesIO()
803
+ with pd.ExcelWriter(buffer, engine='xlsxwriter') as writer:
804
+ filled_df.to_excel(writer, index=False, sheet_name=main_sheet)
805
+ # Also save other sheets
806
+ for sheet_name, df in dfs.items():
807
+ if sheet_name != main_sheet:
808
+ df.to_excel(writer, index=False, sheet_name=sheet_name)
809
+ buffer.seek(0)
810
+ st.download_button(
811
+ label="Download Filled Excel",
812
+ data=buffer,
813
+ file_name="filled_excel.xlsx",
814
+ mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
815
+ )
816
+ else:
817
+ st.warning("No formulas were generated. The data might not have clear patterns for filling missing values.")
818
+
819
+ with tab3:
820
+ st.subheader("Excel and Document Analysis")
821
+ uploaded_file = st.file_uploader(
822
+ "Choose an Excel file, PDF, or Word document for analysis",
823
+ type=["xlsx", "xls", "pdf", "docx", "doc"],
824
+ key="excel_analysis_uploader"
825
+ )
826
+
827
+ if uploaded_file is not None:
828
+ file_type, content = process_uploaded_file(uploaded_file)
829
+
830
+ if file_type is not None and content is not None:
831
+ if file_type == "excel":
832
+ dfs = content
833
+ sheet_names = list(dfs.keys())
834
+ selected_sheet = st.selectbox("Select a sheet for analysis", sheet_names)
835
+
836
+ df_to_analyze = dfs[selected_sheet]
837
+ st.write(f"Preview of {selected_sheet}:")
838
+ st.dataframe(df_to_analyze.head())
839
+
840
+ st.session_state.analyzed_data = df_to_analyze
841
+ analysis_method = "OCR" # Default to OCR for Excel files
842
+ elif file_type == "text":
843
+ st.write("Document content preview:")
844
+ preview_text = content[:500] + "..."
845
+ st.text(preview_text) # Show first 500 characters
846
+
847
+ # Add option to choose between OCR and Assistants API for PDF/Word
848
+ analysis_method = st.radio("Choose analysis method:", ("OCR", "OpenAI Assistants API"))
849
+
850
+ st.session_state.analyzed_data = content
851
+
852
+ # Add category selection with "Default" option
853
+ categories = list(get_category_reports().keys())
854
+ if "Default" in categories:
855
+ categories.remove("Default")
856
+ categories = ["Default"] + categories
857
+ selected_category = st.selectbox("Select analysis category", categories)
858
+
859
+ if st.button("Analyze with GPT"):
860
+ with st.spinner("Analyzing data... This may take a while for large datasets."):
861
+ user_feedback = get_user_feedback(st.session_state.user["_id"])
862
+ reports_needed = get_category_reports().get(selected_category, [])
863
+
864
+ if file_type == "excel":
865
+ analysis_result = analyze_excel_with_gpt(st.session_state.analyzed_data, selected_sheet, user_feedback, selected_category, reports_needed)
866
+ else: # PDF or Word document
867
+ if analysis_method == "OpenAI Assistants API":
868
+ analysis_result = process_file_with_assistant(uploaded_file, "PDF", selected_category, reports_needed, user_feedback)
869
+ else:
870
+ analysis_result = analyze_document_with_gpt(st.session_state.analyzed_data, user_feedback, selected_category, reports_needed)
871
+
872
+ st.markdown("## Analysis Results")
873
+ st.markdown(analysis_result)
874
+
875
+ st.session_state.analysis_result = analysis_result
876
+
877
+ if file_type == "excel":
878
+ pdf_path = excel_to_pdf(st.session_state.analyzed_data)
879
+ with open(pdf_path, "rb") as pdf_file:
880
+ pdf_bytes = pdf_file.read()
881
+ st.download_button(
882
+ label="Download Excel PDF version",
883
+ data=pdf_bytes,
884
+ file_name="excel_data.pdf",
885
+ mime="application/pdf"
886
+ )
887
+
888
+ # Feedback section
889
+ st.markdown("## Feedback")
890
+ new_feedback = st.text_area("Provide feedback or additional insights about the analysis:")
891
+ if st.button("Submit Feedback"):
892
+ if new_feedback:
893
+ user = users_collection.find_one({"_id": st.session_state.user["_id"]})
894
+ existing_feedback = user.get("feedback", "")
895
+
896
+ updated_feedback = f"{existing_feedback}\n{new_feedback}" if existing_feedback else new_feedback
897
+
898
+ users_collection.update_one(
899
+ {"_id": st.session_state.user["_id"]},
900
+ {"$set": {"feedback": updated_feedback}}
901
+ )
902
+ st.success("Feedback submitted successfully!")
903
+ else:
904
+ st.warning("Please enter some feedback before submitting.")
905
+
906
+ # Chat with Data section
907
+ st.markdown("## Chat with Data")
908
+ with st.form(key='chat_form'):
909
+ user_question = st.text_input("Ask a question about the data:")
910
+ chat_submit_button = st.form_submit_button(label='Get Answer')
911
+
912
+ if chat_submit_button:
913
+ if user_question:
914
+ with st.spinner("Analyzing your question..."):
915
+ answer = chat_with_data(st.session_state.analyzed_data, user_question, file_type)
916
+ st.markdown("### Answer")
917
+ st.markdown(answer)
918
+ else:
919
+ st.warning("Please enter a question about the data.")
920
+ else:
921
+ st.error("Unable to process the uploaded file. Please check the file format and try again.")
922
+
923
+ with tab4:
924
+ st.subheader("Challan Processing")
925
+
926
+ challan_pdfs = st.file_uploader(
927
+ "Choose Challan PDF files",
928
+ type="pdf",
929
+ accept_multiple_files=True,
930
+ key="challan_processing_uploader"
931
+ )
932
+
933
+ if st.button("Process Challan PDFs"):
934
+ if challan_pdfs:
935
+ with st.spinner("Processing Challan PDFs..."):
936
+ challan_df = process_challan_pdfs(challan_pdfs)
937
+ st.write("Challan Data:")
938
+ st.dataframe(challan_df)
939
+
940
+ buffer = io.BytesIO()
941
+ with pd.ExcelWriter(buffer, engine='xlsxwriter') as writer:
942
+ challan_df.to_excel(writer, index=False, sheet_name='Challan Data')
943
+ buffer.seek(0)
944
+ st.download_button(
945
+ label="Download Challan Excel",
946
+ data=buffer,
947
+ file_name="challan_data.xlsx",
948
+ mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
949
+ )
950
+
951
+ st.success("Challan PDFs processed successfully")
952
+ else:
953
+ st.warning("No Challan PDFs uploaded. Please choose at least one PDF file.")