Navaneethakrishnan commited on
Commit
09281fe
·
1 Parent(s): d9ba19e

Add RAG system without large files

Browse files
API_README.md ADDED
@@ -0,0 +1,374 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Flask API Server for Advanced RAG System
2
+
3
+ This Flask API server provides REST endpoints for the Advanced RAG System, allowing you to upload documents and process queries programmatically.
4
+
5
+ ## Features
6
+
7
+ - **Document Upload**: Upload and process various document formats (PDF, TXT, DOCX, HTML, etc.)
8
+ - **Query Processing**: Ask questions about uploaded documents
9
+ - **OCR Support**: Process scanned documents with OCR
10
+ - **System Management**: Check status, validate components, clear data
11
+ - **Authentication**: Bearer token authentication
12
+ - **Error Handling**: Comprehensive error handling and fallback mechanisms
13
+
14
+ ## API Endpoints
15
+
16
+ ### 1. Health Check
17
+ **GET** `/health`
18
+
19
+ Check if the server is running and healthy.
20
+
21
+ **Response:**
22
+ ```json
23
+ {
24
+ "status": "healthy",
25
+ "rag_system_initialized": true,
26
+ "ocr_available": true
27
+ }
28
+ ```
29
+
30
+ ### 2. System Status
31
+ **GET** `/hackrx/status`
32
+
33
+ Get detailed system status and statistics.
34
+
35
+ **Headers:**
36
+ ```
37
+ Authorization: Bearer your_api_key_here
38
+ ```
39
+
40
+ **Response:**
41
+ ```json
42
+ {
43
+ "status": "ready",
44
+ "statistics": {
45
+ "vector_database": {
46
+ "total_chunks": 150,
47
+ "unique_sources": 3,
48
+ "file_types": ["pdf", "txt"]
49
+ },
50
+ "audit_trail": {
51
+ "total_entries": 25,
52
+ "successful_queries": 20,
53
+ "failed_queries": 5
54
+ }
55
+ },
56
+ "ocr_available": true
57
+ }
58
+ ```
59
+
60
+ ### 3. Document Upload
61
+ **POST** `/hackrx/upload`
62
+
63
+ Upload and process a document.
64
+
65
+ **Headers:**
66
+ ```
67
+ Authorization: Bearer your_api_key_here
68
+ ```
69
+
70
+ **Form Data:**
71
+ - `file`: The document file to upload
72
+ - `use_ocr`: (optional) "true" or "false" to enable OCR for PDFs
73
+
74
+ **Supported File Types:**
75
+ - PDF (.pdf)
76
+ - Text (.txt)
77
+ - Word (.docx)
78
+ - HTML (.html, .htm)
79
+ - Email (.eml, .msg)
80
+ - CSV (.csv)
81
+ - JSON (.json)
82
+
83
+ **Response:**
84
+ ```json
85
+ {
86
+ "success": true,
87
+ "message": "Document processed successfully",
88
+ "chunks_processed": 45,
89
+ "processing_time": 2.34,
90
+ "filename": "document.pdf"
91
+ }
92
+ ```
93
+
94
+ ### 4. Query Processing
95
+ **POST** `/hackrx/run`
96
+
97
+ Process questions about uploaded documents.
98
+
99
+ **Headers:**
100
+ ```
101
+ Authorization: Bearer your_api_key_here
102
+ Content-Type: application/json
103
+ ```
104
+
105
+ **Request Body:**
106
+ ```json
107
+ {
108
+ "questions": [
109
+ "What is covered under this policy?",
110
+ "What is the maximum coverage amount?",
111
+ "What documents are required for claims?"
112
+ ]
113
+ }
114
+ ```
115
+
116
+ **Response:**
117
+ ```json
118
+ {
119
+ "answers": [
120
+ {
121
+ "question": "What is covered under this policy?",
122
+ "answer": "Based on the policy document, the following are covered...",
123
+ "decision": "COVERED",
124
+ "confidence": 0.85,
125
+ "processing_time": 1.23,
126
+ "amount": 50000.0,
127
+ "waiting_period": "30 days",
128
+ "relevant_clauses": ["Section 3.1", "Section 4.2"],
129
+ "conditions": ["Must be hospitalized", "Pre-authorization required"],
130
+ "exclusions": ["Cosmetic procedures", "Experimental treatments"],
131
+ "required_documents": ["Hospital bills", "Medical reports"]
132
+ }
133
+ ]
134
+ }
135
+ ```
136
+
137
+ ### 5. System Validation
138
+ **GET** `/hackrx/validate`
139
+
140
+ Validate all system components.
141
+
142
+ **Headers:**
143
+ ```
144
+ Authorization: Bearer your_api_key_here
145
+ ```
146
+
147
+ **Response:**
148
+ ```json
149
+ {
150
+ "document_processor": true,
151
+ "vector_database": true,
152
+ "query_parser": true,
153
+ "reasoning_engine": true,
154
+ "all_valid": true,
155
+ "errors": []
156
+ }
157
+ ```
158
+
159
+ ### 6. Clear System
160
+ **POST** `/hackrx/clear`
161
+
162
+ Clear all system data and reset the RAG system.
163
+
164
+ **Headers:**
165
+ ```
166
+ Authorization: Bearer your_api_key_here
167
+ ```
168
+
169
+ **Response:**
170
+ ```json
171
+ {
172
+ "success": true,
173
+ "message": "System cleared successfully"
174
+ }
175
+ ```
176
+
177
+ ## Authentication
178
+
179
+ All endpoints (except `/health`) require Bearer token authentication:
180
+
181
+ ```
182
+ Authorization: Bearer your_api_key_here
183
+ ```
184
+
185
+ **Default API Key:** `your_api_key_here`
186
+
187
+ **Note:** Change this in production for security.
188
+
189
+ ## Error Responses
190
+
191
+ All endpoints return appropriate HTTP status codes:
192
+
193
+ - `200`: Success
194
+ - `400`: Bad Request (missing parameters, invalid data)
195
+ - `401`: Unauthorized (missing or invalid Authorization header)
196
+ - `403`: Forbidden (invalid API key)
197
+ - `500`: Internal Server Error
198
+
199
+ Error response format:
200
+ ```json
201
+ {
202
+ "error": "Error description"
203
+ }
204
+ ```
205
+
206
+ ## Usage Examples
207
+
208
+ ### Python Example
209
+
210
+ ```python
211
+ import requests
212
+ import json
213
+
214
+ # Configuration
215
+ BASE_URL = "http://localhost:5000"
216
+ API_KEY = "your_api_key_here"
217
+ HEADERS = {
218
+ "Authorization": f"Bearer {API_KEY}",
219
+ "Content-Type": "application/json"
220
+ }
221
+
222
+ # 1. Upload a document
223
+ with open("document.pdf", "rb") as f:
224
+ files = {"file": f}
225
+ data = {"use_ocr": "false"}
226
+ upload_headers = {"Authorization": f"Bearer {API_KEY}"}
227
+
228
+ response = requests.post(
229
+ f"{BASE_URL}/hackrx/upload",
230
+ files=files,
231
+ data=data,
232
+ headers=upload_headers
233
+ )
234
+ print("Upload response:", response.json())
235
+
236
+ # 2. Process queries
237
+ questions = [
238
+ "What is covered under this policy?",
239
+ "What is the maximum coverage amount?"
240
+ ]
241
+
242
+ payload = {"questions": questions}
243
+ response = requests.post(
244
+ f"{BASE_URL}/hackrx/run",
245
+ json=payload,
246
+ headers=HEADERS
247
+ )
248
+
249
+ answers = response.json()["answers"]
250
+ for answer in answers:
251
+ print(f"Q: {answer['question']}")
252
+ print(f"A: {answer['answer']}")
253
+ print(f"Decision: {answer['decision']}")
254
+ print(f"Confidence: {answer['confidence']}")
255
+ print("---")
256
+ ```
257
+
258
+ ### cURL Examples
259
+
260
+ **Health Check:**
261
+ ```bash
262
+ curl http://localhost:5000/health
263
+ ```
264
+
265
+ **System Status:**
266
+ ```bash
267
+ curl -H "Authorization: Bearer your_api_key_here" \
268
+ http://localhost:5000/hackrx/status
269
+ ```
270
+
271
+ **Upload Document:**
272
+ ```bash
273
+ curl -X POST \
274
+ -H "Authorization: Bearer your_api_key_here" \
275
+ -F "file=@document.pdf" \
276
+ -F "use_ocr=false" \
277
+ http://localhost:5000/hackrx/upload
278
+ ```
279
+
280
+ **Process Queries:**
281
+ ```bash
282
+ curl -X POST \
283
+ -H "Authorization: Bearer your_api_key_here" \
284
+ -H "Content-Type: application/json" \
285
+ -d '{"questions": ["What is covered under this policy?"]}' \
286
+ http://localhost:5000/hackrx/run
287
+ ```
288
+
289
+ ## Running the Server
290
+
291
+ 1. **Install Dependencies:**
292
+ ```bash
293
+ pip install flask requests
294
+ ```
295
+
296
+ 2. **Start the Server:**
297
+ ```bash
298
+ python app.py
299
+ ```
300
+
301
+ 3. **Test the API:**
302
+ ```bash
303
+ python test_api.py
304
+ ```
305
+
306
+ ## Configuration
307
+
308
+ ### Environment Variables
309
+
310
+ You can set these environment variables:
311
+
312
+ - `FLASK_ENV`: Set to `production` for production deployment
313
+ - `API_KEY`: Override the default API key
314
+ - `PORT`: Override the default port (5000)
315
+
316
+ ### Production Deployment
317
+
318
+ For production deployment:
319
+
320
+ 1. Change the API key in `app.py`
321
+ 2. Set `debug=False` in `app.run()`
322
+ 3. Use a production WSGI server like Gunicorn:
323
+ ```bash
324
+ pip install gunicorn
325
+ gunicorn -w 4 -b 0.0.0.0:5000 app:app
326
+ ```
327
+
328
+ ## Troubleshooting
329
+
330
+ ### Common Issues
331
+
332
+ 1. **RAG System Initialization Failed**
333
+ - Check if all required dependencies are installed
334
+ - Ensure model files are available
335
+ - Check system memory and resources
336
+
337
+ 2. **Document Upload Fails**
338
+ - Verify file format is supported
339
+ - Check file size limits
340
+ - Ensure proper file permissions
341
+
342
+ 3. **Query Processing Errors**
343
+ - Make sure documents are uploaded first
344
+ - Check if the RAG system is properly initialized
345
+ - Verify the question format
346
+
347
+ 4. **Authentication Errors**
348
+ - Ensure the Authorization header is present
349
+ - Verify the API key is correct
350
+ - Check the Bearer token format
351
+
352
+ ### Logs
353
+
354
+ The server provides detailed logging. Check the console output for:
355
+ - RAG system initialization status
356
+ - Document processing progress
357
+ - Query processing results
358
+ - Error messages and stack traces
359
+
360
+ ## Security Considerations
361
+
362
+ 1. **Change the Default API Key**: Update `your_api_key_here` in production
363
+ 2. **Use HTTPS**: Always use HTTPS in production
364
+ 3. **Rate Limiting**: Consider implementing rate limiting for production use
365
+ 4. **Input Validation**: The API includes basic validation, but add more as needed
366
+ 5. **File Upload Security**: Implement additional file validation for production
367
+
368
+ ## Support
369
+
370
+ For issues and questions:
371
+ 1. Check the console logs for error messages
372
+ 2. Verify all dependencies are installed
373
+ 3. Test with the provided `test_api.py` script
374
+ 4. Check the system validation endpoint for component status
API_SERVER_README.md ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Flask API Server for main.py
2
+
3
+ A simple Flask API server that provides REST endpoints for the main.py RAG system without duplicating model loading or initialization.
4
+
5
+ ## How It Works
6
+
7
+ The `app.py` server acts as a lightweight wrapper around `main.py`:
8
+ - **No Model Loading**: The server doesn't load models, parsers, or RAG components
9
+ - **Subprocess Calls**: Uses `subprocess` to call `main.py` with command line arguments
10
+ - **Localhost Only**: Runs only on 127.0.0.1 for security
11
+ - **Simple Interface**: Provides basic GET/POST endpoints
12
+
13
+ ## API Endpoints
14
+
15
+ ### 1. Health Check
16
+ **GET** `http://127.0.0.1:5000/health`
17
+
18
+ Check if the Flask server is running.
19
+
20
+ **Response:**
21
+ ```json
22
+ {
23
+ "status": "healthy",
24
+ "message": "Flask server is running"
25
+ }
26
+ ```
27
+
28
+ ### 2. System Status
29
+ **GET** `http://127.0.0.1:5000/hackrx/status`
30
+
31
+ Check if main.py is ready and working.
32
+
33
+ **Response:**
34
+ ```json
35
+ {
36
+ "status": "ready",
37
+ "message": "main.py is ready"
38
+ }
39
+ ```
40
+
41
+ ### 3. Document Upload
42
+ **POST** `http://127.0.0.1:5000/hackrx/upload`
43
+
44
+ Upload and process a document using main.py.
45
+
46
+ **Form Data:**
47
+ - `file`: The document file to upload
48
+
49
+ **Response:**
50
+ ```json
51
+ {
52
+ "success": true,
53
+ "message": "Document processed successfully",
54
+ "chunks_processed": 45,
55
+ "processing_time": 2.34,
56
+ "filename": "document.pdf"
57
+ }
58
+ ```
59
+
60
+ ### 4. Query Processing
61
+ **POST** `http://127.0.0.1:5000/hackrx/run`
62
+
63
+ Process questions about uploaded documents using main.py.
64
+
65
+ **Request Body:**
66
+ ```json
67
+ {
68
+ "questions": [
69
+ "What is covered under this policy?",
70
+ "What is the maximum coverage amount?"
71
+ ]
72
+ }
73
+ ```
74
+
75
+ **Response:**
76
+ ```json
77
+ {
78
+ "answers": [
79
+ {
80
+ "question": "What is covered under this policy?",
81
+ "answer": "Based on the policy document, the following are covered...",
82
+ "decision": "COVERED",
83
+ "confidence": 0.85,
84
+ "processing_time": 1.23
85
+ }
86
+ ]
87
+ }
88
+ ```
89
+
90
+ ## How the Server Works
91
+
92
+ ### Document Upload Process:
93
+ 1. Flask receives uploaded file
94
+ 2. Saves file to `uploads/` directory
95
+ 3. Calls: `python main.py --upload /path/to/file`
96
+ 4. Parses output from main.py
97
+ 5. Returns JSON response
98
+
99
+ ### Query Processing Process:
100
+ 1. Flask receives JSON with questions
101
+ 2. For each question:
102
+ - Creates temporary file with question
103
+ - Calls: `python main.py --query /path/to/question.txt`
104
+ - Parses structured output from main.py
105
+ - Extracts decision, confidence, justification
106
+ 3. Returns JSON with all answers
107
+
108
+ ### Status Check Process:
109
+ 1. Calls: `python main.py --status`
110
+ 2. Checks if main.py responds successfully
111
+ 3. Returns status JSON
112
+
113
+ ## Usage Examples
114
+
115
+ ### Python Example
116
+
117
+ ```python
118
+ import requests
119
+
120
+ BASE_URL = "http://127.0.0.1:5000"
121
+
122
+ # 1. Upload a document
123
+ with open("document.pdf", "rb") as f:
124
+ files = {"file": f}
125
+ response = requests.post(f"{BASE_URL}/hackrx/upload", files=files)
126
+ print("Upload response:", response.json())
127
+
128
+ # 2. Process queries
129
+ questions = [
130
+ "What is covered under this policy?",
131
+ "What is the maximum coverage amount?"
132
+ ]
133
+
134
+ payload = {"questions": questions}
135
+ response = requests.post(
136
+ f"{BASE_URL}/hackrx/run",
137
+ json=payload,
138
+ headers={"Content-Type": "application/json"}
139
+ )
140
+
141
+ answers = response.json()["answers"]
142
+ for answer in answers:
143
+ print(f"Q: {answer['question']}")
144
+ print(f"A: {answer['answer']}")
145
+ print(f"Decision: {answer['decision']}")
146
+ print(f"Confidence: {answer['confidence']}")
147
+ print("---")
148
+ ```
149
+
150
+ ### cURL Examples
151
+
152
+ **Health Check:**
153
+ ```bash
154
+ curl http://127.0.0.1:5000/health
155
+ ```
156
+
157
+ **System Status:**
158
+ ```bash
159
+ curl http://127.0.0.1:5000/hackrx/status
160
+ ```
161
+
162
+ **Upload Document:**
163
+ ```bash
164
+ curl -X POST -F "file=@document.pdf" http://127.0.0.1:5000/hackrx/upload
165
+ ```
166
+
167
+ **Process Queries:**
168
+ ```bash
169
+ curl -X POST \
170
+ -H "Content-Type: application/json" \
171
+ -d '{"questions": ["What is covered under this policy?"]}' \
172
+ http://127.0.0.1:5000/hackrx/run
173
+ ```
174
+
175
+ ## Running the Server
176
+
177
+ 1. **Start the server:**
178
+ ```bash
179
+ python app.py
180
+ ```
181
+
182
+ 2. **Test the API:**
183
+ ```bash
184
+ python test_api.py
185
+ ```
186
+
187
+ ## Command Line Interface
188
+
189
+ The main.py now supports command line arguments:
190
+
191
+ ```bash
192
+ # Process a single query
193
+ python main.py --query question.txt
194
+
195
+ # Upload and process a document
196
+ python main.py --upload document.pdf
197
+
198
+ # Check system status
199
+ python main.py --status
200
+
201
+ # Interactive mode (default)
202
+ python main.py
203
+ ```
204
+
205
+ ## Advantages
206
+
207
+ 1. **No Duplication**: Doesn't load models or initialize RAG system
208
+ 2. **Lightweight**: Minimal memory footprint
209
+ 3. **Simple**: Easy to understand and maintain
210
+ 4. **Secure**: Localhost only
211
+ 5. **Reliable**: Uses existing main.py functionality
212
+
213
+ ## Error Handling
214
+
215
+ - **File Not Found**: Returns 400 if file doesn't exist
216
+ - **Unsupported Format**: Returns 400 for unsupported file types
217
+ - **Processing Errors**: Returns 500 with error details
218
+ - **Timeouts**: 60 seconds for queries, 120 seconds for uploads
219
+
220
+ ## Notes
221
+
222
+ - **main.py Required**: The server requires main.py to be in the same directory
223
+ - **Python Path**: Assumes `python` command is available
224
+ - **File Cleanup**: Temporary files are automatically cleaned up
225
+ - **Upload Directory**: Creates `uploads/` directory if it doesn't exist
README.md CHANGED
@@ -1,3 +0,0 @@
1
- ---
2
- license: mit
3
- ---
 
 
 
 
SIMPLE_API_README.md ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Simple Flask API for RAG System
2
+
3
+ A simplified Flask API server that provides localhost access to the main.py RAG system functionality.
4
+
5
+ ## Features
6
+
7
+ - **Localhost Only**: Runs only on 127.0.0.1 (localhost)
8
+ - **Simple Authentication**: No authentication required for localhost use
9
+ - **Core Operations**: Document upload and query processing
10
+ - **GET/POST Operations**: Simple REST endpoints
11
+
12
+ ## API Endpoints
13
+
14
+ ### 1. Health Check
15
+ **GET** `http://127.0.0.1:5000/health`
16
+
17
+ Check if the server is running.
18
+
19
+ **Response:**
20
+ ```json
21
+ {
22
+ "status": "healthy",
23
+ "rag_system_initialized": true
24
+ }
25
+ ```
26
+
27
+ ### 2. System Status
28
+ **GET** `http://127.0.0.1:5000/hackrx/status`
29
+
30
+ Check if the RAG system is ready.
31
+
32
+ **Response:**
33
+ ```json
34
+ {
35
+ "status": "ready",
36
+ "message": "RAG system is ready"
37
+ }
38
+ ```
39
+
40
+ ### 3. Document Upload
41
+ **POST** `http://127.0.0.1:5000/hackrx/upload`
42
+
43
+ Upload and process a document.
44
+
45
+ **Form Data:**
46
+ - `file`: The document file to upload
47
+
48
+ **Supported File Types:**
49
+ - PDF (.pdf)
50
+ - Text (.txt)
51
+ - Word (.docx)
52
+ - HTML (.html, .htm)
53
+ - Email (.eml, .msg)
54
+ - CSV (.csv)
55
+ - JSON (.json)
56
+
57
+ **Response:**
58
+ ```json
59
+ {
60
+ "success": true,
61
+ "message": "Document processed successfully",
62
+ "chunks_processed": 45,
63
+ "processing_time": 2.34,
64
+ "filename": "document.pdf"
65
+ }
66
+ ```
67
+
68
+ ### 4. Query Processing
69
+ **POST** `http://127.0.0.1:5000/hackrx/run`
70
+
71
+ Process questions about uploaded documents.
72
+
73
+ **Request Body:**
74
+ ```json
75
+ {
76
+ "questions": [
77
+ "What is covered under this policy?",
78
+ "What is the maximum coverage amount?",
79
+ "What documents are required for claims?"
80
+ ]
81
+ }
82
+ ```
83
+
84
+ **Response:**
85
+ ```json
86
+ {
87
+ "answers": [
88
+ {
89
+ "question": "What is covered under this policy?",
90
+ "answer": "Based on the policy document, the following are covered...",
91
+ "decision": "COVERED",
92
+ "confidence": 0.85,
93
+ "processing_time": 1.23
94
+ }
95
+ ]
96
+ }
97
+ ```
98
+
99
+ ## Usage Examples
100
+
101
+ ### Python Example
102
+
103
+ ```python
104
+ import requests
105
+ import json
106
+
107
+ BASE_URL = "http://127.0.0.1:5000"
108
+
109
+ # 1. Upload a document
110
+ with open("document.pdf", "rb") as f:
111
+ files = {"file": f}
112
+ response = requests.post(f"{BASE_URL}/hackrx/upload", files=files)
113
+ print("Upload response:", response.json())
114
+
115
+ # 2. Process queries
116
+ questions = [
117
+ "What is covered under this policy?",
118
+ "What is the maximum coverage amount?"
119
+ ]
120
+
121
+ payload = {"questions": questions}
122
+ response = requests.post(
123
+ f"{BASE_URL}/hackrx/run",
124
+ json=payload,
125
+ headers={"Content-Type": "application/json"}
126
+ )
127
+
128
+ answers = response.json()["answers"]
129
+ for answer in answers:
130
+ print(f"Q: {answer['question']}")
131
+ print(f"A: {answer['answer']}")
132
+ print(f"Decision: {answer['decision']}")
133
+ print(f"Confidence: {answer['confidence']}")
134
+ print("---")
135
+ ```
136
+
137
+ ### cURL Examples
138
+
139
+ **Health Check:**
140
+ ```bash
141
+ curl http://127.0.0.1:5000/health
142
+ ```
143
+
144
+ **System Status:**
145
+ ```bash
146
+ curl http://127.0.0.1:5000/hackrx/status
147
+ ```
148
+
149
+ **Upload Document:**
150
+ ```bash
151
+ curl -X POST -F "file=@document.pdf" http://127.0.0.1:5000/hackrx/upload
152
+ ```
153
+
154
+ **Process Queries:**
155
+ ```bash
156
+ curl -X POST \
157
+ -H "Content-Type: application/json" \
158
+ -d '{"questions": ["What is covered under this policy?"]}' \
159
+ http://127.0.0.1:5000/hackrx/run
160
+ ```
161
+
162
+ ## Running the Server
163
+
164
+ 1. **Start the server:**
165
+ ```bash
166
+ python app.py
167
+ ```
168
+
169
+ 2. **Test the API:**
170
+ ```bash
171
+ python test_api.py
172
+ ```
173
+
174
+ ## Error Responses
175
+
176
+ - `200`: Success
177
+ - `400`: Bad Request (missing parameters, invalid data)
178
+ - `500`: Internal Server Error
179
+
180
+ Error response format:
181
+ ```json
182
+ {
183
+ "error": "Error description"
184
+ }
185
+ ```
186
+
187
+ ## Notes
188
+
189
+ - **Localhost Only**: The server only accepts connections from localhost (127.0.0.1)
190
+ - **No Authentication**: No API keys or authentication required for localhost use
191
+ - **Simple Interface**: Focused on core document upload and query processing
192
+ - **Automatic Cleanup**: Uploaded files are automatically cleaned up after processing
app.py ADDED
@@ -0,0 +1,691 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Flask API Pipeline for Advanced RAG System
3
+ Provides REST API endpoints for document processing and query analysis
4
+ """
5
+
6
+ from flask import Flask, request, jsonify, send_file
7
+ import os
8
+ import time
9
+ import json
10
+ import logging
11
+ from datetime import datetime
12
+ from pathlib import Path
13
+ from typing import List, Dict, Any, Optional
14
+ import tempfile
15
+ import shutil
16
+
17
+ # Import RAG system components
18
+ from rag_system import AdvancedRAGSystem, QueryResult
19
+ from document_processer import AdvancedDocumentProcessor, DocumentChunk
20
+ from vector_database import VectorDatabase, SearchResult
21
+ from query_parser import AdvancedQueryParser, ParsedQuery
22
+ from llm_reasoning import AdvancedLLMReasoning, ReasoningResult
23
+
24
+ # Configure logging
25
+ logging.basicConfig(
26
+ level=logging.INFO,
27
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
28
+ handlers=[
29
+ logging.FileHandler('app.log'),
30
+ logging.StreamHandler()
31
+ ]
32
+ )
33
+ logger = logging.getLogger(__name__)
34
+
35
+ app = Flask(__name__)
36
+
37
+ # Global RAG system instance
38
+ rag_system = None
39
+ system_initialized = False
40
+
41
+ # Request logging middleware
42
+ @app.before_request
43
+ def log_request_info():
44
+ """Log all incoming requests"""
45
+ logger.info(f"Request: {request.method} {request.url}")
46
+ if request.method == 'POST':
47
+ logger.info(f"Request data: {request.get_data()[:200]}...") # Log first 200 chars
48
+
49
+ @app.after_request
50
+ def log_response_info(response):
51
+ """Log all outgoing responses"""
52
+ logger.info(f"Response: {response.status_code} for {request.method} {request.url}")
53
+ return response
54
+
55
+ @app.route('/', methods=['GET'])
56
+ def root():
57
+ """Root endpoint with API documentation"""
58
+ logger.info("Root endpoint accessed")
59
+ return jsonify({
60
+ 'message': 'Advanced RAG System API',
61
+ 'version': '1.0.0',
62
+ 'status': 'running',
63
+ 'endpoints': {
64
+ 'health': 'GET /api/health',
65
+ 'status': 'GET /api/status',
66
+ 'upload': 'POST /api/upload',
67
+ 'query': 'POST /api/query',
68
+ 'batch_query': 'POST /api/batch_query',
69
+ 'validate': 'GET /api/validate',
70
+ 'audit': 'GET /api/audit',
71
+ 'statistics': 'GET /api/statistics',
72
+ 'export': 'POST /api/export',
73
+ 'clear': 'POST /api/clear',
74
+ 'keep_alive': 'GET /api/keep-alive'
75
+ },
76
+ 'description': 'Insurance Policy Analysis RAG System',
77
+ 'features': [
78
+ 'Document upload and processing',
79
+ 'Natural language query processing',
80
+ 'Policy coverage analysis',
81
+ 'Claim requirement extraction',
82
+ 'Audit trail and statistics'
83
+ ],
84
+ 'timestamp': datetime.now().isoformat()
85
+ })
86
+
87
+ def initialize_rag_system():
88
+ """Initialize the RAG system"""
89
+ global rag_system, system_initialized
90
+
91
+ try:
92
+ logger.info("Initializing RAG system...")
93
+ rag_system = AdvancedRAGSystem(
94
+ model_path="./mistral-7b-instruct-v0.1.Q4_K_M.gguf",
95
+ use_gpu=False, # Use CPU for better compatibility
96
+ vector_db_path="./vector_db"
97
+ )
98
+ system_initialized = True
99
+ logger.info("RAG system initialized successfully!")
100
+ return True
101
+ except Exception as e:
102
+ logger.error(f"Failed to initialize RAG system: {e}")
103
+ system_initialized = False
104
+ return False
105
+
106
+ def ensure_system_ready():
107
+ """Ensure the RAG system is ready"""
108
+ if not system_initialized or rag_system is None:
109
+ if not initialize_rag_system():
110
+ return False
111
+ return True
112
+
113
+ @app.route('/api/health', methods=['GET'])
114
+ def health_check():
115
+ """Health check endpoint"""
116
+ logger.info("Health check requested")
117
+ try:
118
+ response = {
119
+ 'status': 'healthy',
120
+ 'timestamp': datetime.now().isoformat(),
121
+ 'system_initialized': system_initialized,
122
+ 'rag_system_ready': rag_system is not None
123
+ }
124
+ logger.info(f"Health check response: {response}")
125
+ return jsonify(response)
126
+ except Exception as e:
127
+ logger.error(f"Health check failed: {e}")
128
+ return jsonify({'error': str(e)}), 500
129
+
130
+ @app.route('/api/keep-alive', methods=['GET'])
131
+ def keep_alive():
132
+ """Keep-alive endpoint to prevent auto-termination"""
133
+ logger.info("Keep-alive ping received")
134
+ return jsonify({
135
+ 'status': 'alive',
136
+ 'timestamp': datetime.now().isoformat(),
137
+ 'message': 'Server is running'
138
+ })
139
+
140
+ @app.route('/hackrx/run', methods=['POST'])
141
+ def hackrx_run():
142
+ """Main endpoint for hackathon - processes queries with document URL"""
143
+ logger.info("HackRX run endpoint accessed")
144
+
145
+ # Check Authorization header
146
+ auth_header = request.headers.get('Authorization')
147
+ if not auth_header or not auth_header.startswith('Bearer '):
148
+ logger.warning("Missing or invalid Authorization header")
149
+ return jsonify({'error': 'Unauthorized'}), 401
150
+
151
+ api_key = auth_header.split(' ')[1]
152
+ # For now, accept any Bearer token (you can add validation later)
153
+ logger.info(f"API key provided: {api_key[:10]}...")
154
+
155
+ try:
156
+ data = request.get_json()
157
+ if not data:
158
+ logger.warning("No JSON data provided in hackrx/run request")
159
+ return jsonify({'error': 'No JSON data provided'}), 400
160
+
161
+ # Extract documents URL and questions
162
+ documents_url = data.get('documents')
163
+ questions = data.get('questions')
164
+
165
+ if not questions or not isinstance(questions, list):
166
+ logger.warning("No questions list provided in hackrx/run request")
167
+ return jsonify({'error': 'No questions list provided'}), 400
168
+
169
+ logger.info(f"Processing {len(questions)} questions")
170
+ if documents_url:
171
+ logger.info(f"Document URL provided: {documents_url}")
172
+
173
+ # Ensure system is ready
174
+ if not ensure_system_ready():
175
+ logger.error("RAG system not ready for hackrx/run")
176
+ return jsonify({'error': 'RAG system not ready'}), 500
177
+
178
+ # If document URL is provided, download and process it
179
+ if documents_url:
180
+ try:
181
+ logger.info("Downloading document from URL...")
182
+ import requests
183
+ response = requests.get(documents_url, timeout=30)
184
+ if response.status_code == 200:
185
+ # Save document temporarily
186
+ temp_file = f"temp_document_{int(time.time())}.pdf"
187
+ with open(temp_file, 'wb') as f:
188
+ f.write(response.content)
189
+
190
+ # Process document
191
+ logger.info("Processing downloaded document...")
192
+ chunks = rag_system.ingest_document(temp_file, use_ocr=False)
193
+ logger.info(f"Document processed: {len(chunks)} chunks created")
194
+
195
+ # Clean up
196
+ os.remove(temp_file)
197
+ else:
198
+ logger.warning(f"Failed to download document: {response.status_code}")
199
+ except Exception as e:
200
+ logger.error(f"Error downloading/processing document: {e}")
201
+
202
+ # Process questions
203
+ answers = []
204
+ total_start_time = time.time()
205
+
206
+ for i, question in enumerate(questions):
207
+ if not isinstance(question, str) or not question.strip():
208
+ continue
209
+
210
+ logger.info(f"Processing question {i+1}/{len(questions)}: {question}")
211
+ start_time = time.time()
212
+
213
+ try:
214
+ result = rag_system.process_query(question)
215
+ processing_time = time.time() - start_time
216
+
217
+ # Extract just the answer text for hackathon format
218
+ answer_text = result.reasoning_result.justification
219
+
220
+ answers.append(answer_text)
221
+ logger.info(f"Question {i+1} processed successfully in {processing_time:.2f}s")
222
+
223
+ except Exception as e:
224
+ logger.error(f"Error processing question {i+1}: {e}")
225
+ answers.append(f"Error processing query: {str(e)}")
226
+
227
+ total_time = time.time() - total_start_time
228
+ logger.info(f"HackRX run completed: {len(answers)} answers in {total_time:.2f}s")
229
+
230
+ # Return in hackathon format
231
+ return jsonify({'answers': answers})
232
+
233
+ except Exception as e:
234
+ logger.error(f"HackRX run failed: {e}")
235
+ return jsonify({'error': f'Processing failed: {str(e)}'}), 500
236
+
237
+ @app.route('/hackrx/upload', methods=['POST'])
238
+ def hackrx_upload():
239
+ """Upload endpoint for hackathon"""
240
+ logger.info("HackRX upload endpoint accessed")
241
+ try:
242
+ # Check if file is uploaded
243
+ if 'file' not in request.files:
244
+ logger.warning("No file provided in hackrx/upload request")
245
+ return jsonify({'error': 'No file provided'}), 400
246
+
247
+ file = request.files['file']
248
+ if file.filename == '':
249
+ logger.warning("Empty filename in hackrx/upload request")
250
+ return jsonify({'error': 'No file selected'}), 400
251
+
252
+ logger.info(f"Processing file: {file.filename}")
253
+
254
+ # Ensure system is ready
255
+ if not ensure_system_ready():
256
+ logger.error("RAG system not ready for upload")
257
+ return jsonify({'error': 'RAG system not ready'}), 500
258
+
259
+ # Check file type
260
+ supported_extensions = {'.pdf', '.txt', '.docx', '.html', '.htm', '.eml', '.msg', '.csv', '.json'}
261
+ file_extension = Path(file.filename).suffix.lower()
262
+
263
+ if file_extension not in supported_extensions:
264
+ logger.warning(f"Unsupported file type: {file_extension}")
265
+ return jsonify({'error': f'Unsupported file type: {file_extension}'}), 400
266
+
267
+ # Save uploaded file
268
+ upload_dir = Path('uploads')
269
+ upload_dir.mkdir(exist_ok=True)
270
+
271
+ file_path = upload_dir / file.filename
272
+ file.save(str(file_path))
273
+ logger.info(f"File saved to: {file_path}")
274
+
275
+ try:
276
+ # Process document
277
+ start_time = time.time()
278
+ logger.info("Starting document ingestion...")
279
+ chunks = rag_system.ingest_document(str(file_path), use_ocr=False)
280
+ processing_time = time.time() - start_time
281
+
282
+ logger.info(f"Document processed successfully: {len(chunks)} chunks created in {processing_time:.2f}s")
283
+
284
+ # Clean up uploaded file
285
+ os.remove(str(file_path))
286
+ logger.info("Temporary file cleaned up")
287
+
288
+ response = {
289
+ 'success': True,
290
+ 'message': 'Document processed successfully',
291
+ 'filename': file.filename,
292
+ 'chunks_processed': len(chunks),
293
+ 'processing_time': processing_time,
294
+ 'file_type': file_extension,
295
+ 'timestamp': datetime.now().isoformat()
296
+ }
297
+ logger.info(f"HackRX upload response: {response}")
298
+ return jsonify(response)
299
+
300
+ except Exception as e:
301
+ # Clean up on error
302
+ if os.path.exists(str(file_path)):
303
+ os.remove(str(file_path))
304
+ logger.info("Cleaned up file after error")
305
+ logger.error(f"Document processing error: {e}")
306
+ raise e
307
+
308
+ except Exception as e:
309
+ logger.error(f"HackRX upload failed: {e}")
310
+ return jsonify({'error': f'Document processing failed: {str(e)}'}), 500
311
+
312
+ @app.route('/api/status', methods=['GET'])
313
+ def system_status():
314
+ """Get detailed system status"""
315
+ try:
316
+ if not ensure_system_ready():
317
+ return jsonify({
318
+ 'status': 'error',
319
+ 'message': 'RAG system initialization failed'
320
+ }), 500
321
+
322
+ # Get system statistics
323
+ stats = rag_system.get_system_statistics()
324
+
325
+ return jsonify({
326
+ 'status': 'ready',
327
+ 'system_statistics': stats,
328
+ 'timestamp': datetime.now().isoformat()
329
+ })
330
+
331
+ except Exception as e:
332
+ logger.error(f"Status check failed: {e}")
333
+ return jsonify({
334
+ 'status': 'error',
335
+ 'message': f'Status check failed: {str(e)}'
336
+ }), 500
337
+
338
+ @app.route('/api/upload', methods=['POST'])
339
+ def upload_document():
340
+ """Upload and process a document"""
341
+ logger.info("Document upload requested")
342
+ try:
343
+ # Check if file is uploaded
344
+ if 'file' not in request.files:
345
+ logger.warning("No file provided in upload request")
346
+ return jsonify({'error': 'No file provided'}), 400
347
+
348
+ file = request.files['file']
349
+ if file.filename == '':
350
+ logger.warning("Empty filename in upload request")
351
+ return jsonify({'error': 'No file selected'}), 400
352
+
353
+ logger.info(f"Processing file: {file.filename}")
354
+
355
+ # Ensure system is ready
356
+ if not ensure_system_ready():
357
+ logger.error("RAG system not ready for upload")
358
+ return jsonify({'error': 'RAG system not ready'}), 500
359
+
360
+ # Check file type
361
+ supported_extensions = {'.pdf', '.txt', '.docx', '.html', '.htm', '.eml', '.msg', '.csv', '.json'}
362
+ file_extension = Path(file.filename).suffix.lower()
363
+
364
+ if file_extension not in supported_extensions:
365
+ logger.warning(f"Unsupported file type: {file_extension}")
366
+ return jsonify({'error': f'Unsupported file type: {file_extension}'}), 400
367
+
368
+ # Get OCR option
369
+ use_ocr = request.form.get('use_ocr', 'false').lower() == 'true'
370
+ logger.info(f"OCR enabled: {use_ocr}")
371
+
372
+ # Save uploaded file
373
+ upload_dir = Path('uploads')
374
+ upload_dir.mkdir(exist_ok=True)
375
+
376
+ file_path = upload_dir / file.filename
377
+ file.save(str(file_path))
378
+ logger.info(f"File saved to: {file_path}")
379
+
380
+ try:
381
+ # Process document
382
+ start_time = time.time()
383
+ logger.info("Starting document ingestion...")
384
+ chunks = rag_system.ingest_document(str(file_path), use_ocr=use_ocr)
385
+ processing_time = time.time() - start_time
386
+
387
+ logger.info(f"Document processed successfully: {len(chunks)} chunks created in {processing_time:.2f}s")
388
+
389
+ # Clean up uploaded file
390
+ os.remove(str(file_path))
391
+ logger.info("Temporary file cleaned up")
392
+
393
+ response = {
394
+ 'success': True,
395
+ 'message': 'Document processed successfully',
396
+ 'filename': file.filename,
397
+ 'chunks_processed': len(chunks),
398
+ 'processing_time': processing_time,
399
+ 'file_type': file_extension,
400
+ 'ocr_used': use_ocr,
401
+ 'timestamp': datetime.now().isoformat()
402
+ }
403
+ logger.info(f"Upload response: {response}")
404
+ return jsonify(response)
405
+
406
+ except Exception as e:
407
+ # Clean up on error
408
+ if os.path.exists(str(file_path)):
409
+ os.remove(str(file_path))
410
+ logger.info("Cleaned up file after error")
411
+ logger.error(f"Document processing error: {e}")
412
+ raise e
413
+
414
+ except Exception as e:
415
+ logger.error(f"Document upload failed: {e}")
416
+ return jsonify({'error': f'Document processing failed: {str(e)}'}), 500
417
+
418
+ @app.route('/api/query', methods=['POST'])
419
+ def process_query():
420
+ """Process a single query"""
421
+ logger.info("Single query processing requested")
422
+ try:
423
+ data = request.get_json()
424
+ if not data:
425
+ logger.warning("No JSON data provided in query request")
426
+ return jsonify({'error': 'No JSON data provided'}), 400
427
+
428
+ query = data.get('query')
429
+ if not query or not isinstance(query, str):
430
+ logger.warning("Invalid query provided")
431
+ return jsonify({'error': 'Invalid query provided'}), 400
432
+
433
+ logger.info(f"Processing query: {query}")
434
+
435
+ # Ensure system is ready
436
+ if not ensure_system_ready():
437
+ logger.error("RAG system not ready for query processing")
438
+ return jsonify({'error': 'RAG system not ready'}), 500
439
+
440
+ # Process query
441
+ start_time = time.time()
442
+ logger.info("Starting query processing...")
443
+ result = rag_system.process_query(query)
444
+ processing_time = time.time() - start_time
445
+
446
+ logger.info(f"Query processed successfully in {processing_time:.2f}s")
447
+
448
+ # Format response
449
+ response = {
450
+ 'query': query,
451
+ 'answer': result.reasoning_result.justification,
452
+ 'decision': result.reasoning_result.decision,
453
+ 'confidence': result.reasoning_result.confidence_score,
454
+ 'processing_time': processing_time,
455
+ 'timestamp': datetime.now().isoformat(),
456
+ 'metadata': {
457
+ 'amount': result.reasoning_result.amount,
458
+ 'waiting_period': result.reasoning_result.waiting_period,
459
+ 'relevant_clauses': result.reasoning_result.relevant_clauses,
460
+ 'conditions': result.reasoning_result.conditions,
461
+ 'exclusions': result.reasoning_result.exclusions,
462
+ 'required_documents': result.reasoning_result.required_documents
463
+ }
464
+ }
465
+
466
+ logger.info(f"Query response: {response}")
467
+ return jsonify(response)
468
+
469
+ except Exception as e:
470
+ logger.error(f"Query processing failed: {e}")
471
+ return jsonify({'error': f'Query processing failed: {str(e)}'}), 500
472
+
473
+ @app.route('/api/batch_query', methods=['POST'])
474
+ def process_batch_queries():
475
+ """Process multiple queries"""
476
+ try:
477
+ data = request.get_json()
478
+ if not data:
479
+ return jsonify({'error': 'No JSON data provided'}), 400
480
+
481
+ queries = data.get('queries')
482
+ if not queries or not isinstance(queries, list):
483
+ return jsonify({'error': 'Invalid queries list provided'}), 400
484
+
485
+ # Ensure system is ready
486
+ if not ensure_system_ready():
487
+ return jsonify({'error': 'RAG system not ready'}), 500
488
+
489
+ results = []
490
+ total_start_time = time.time()
491
+
492
+ for query in queries:
493
+ if not isinstance(query, str) or not query.strip():
494
+ continue
495
+
496
+ try:
497
+ start_time = time.time()
498
+ result = rag_system.process_query(query)
499
+ processing_time = time.time() - start_time
500
+
501
+ query_result = {
502
+ 'query': query,
503
+ 'answer': result.reasoning_result.justification,
504
+ 'decision': result.reasoning_result.decision,
505
+ 'confidence': result.reasoning_result.confidence_score,
506
+ 'processing_time': processing_time,
507
+ 'metadata': {
508
+ 'amount': result.reasoning_result.amount,
509
+ 'waiting_period': result.reasoning_result.waiting_period,
510
+ 'relevant_clauses': result.reasoning_result.relevant_clauses,
511
+ 'conditions': result.reasoning_result.conditions,
512
+ 'exclusions': result.reasoning_result.exclusions,
513
+ 'required_documents': result.reasoning_result.required_documents
514
+ }
515
+ }
516
+
517
+ results.append(query_result)
518
+
519
+ except Exception as e:
520
+ results.append({
521
+ 'query': query,
522
+ 'answer': f"Error processing query: {str(e)}",
523
+ 'decision': 'ERROR',
524
+ 'confidence': 0.0,
525
+ 'processing_time': 0.0,
526
+ 'metadata': {}
527
+ })
528
+
529
+ total_time = time.time() - total_start_time
530
+
531
+ return jsonify({
532
+ 'results': results,
533
+ 'total_queries': len(queries),
534
+ 'successful_queries': len([r for r in results if r['decision'] != 'ERROR']),
535
+ 'total_processing_time': total_time,
536
+ 'timestamp': datetime.now().isoformat()
537
+ })
538
+
539
+ except Exception as e:
540
+ logger.error(f"Batch query processing failed: {e}")
541
+ return jsonify({'error': f'Batch query processing failed: {str(e)}'}), 500
542
+
543
+ @app.route('/api/validate', methods=['GET'])
544
+ def validate_system():
545
+ """Validate system components"""
546
+ try:
547
+ if not ensure_system_ready():
548
+ return jsonify({'error': 'RAG system not ready'}), 500
549
+
550
+ validation = rag_system.validate_system()
551
+ return jsonify(validation)
552
+
553
+ except Exception as e:
554
+ logger.error(f"System validation failed: {e}")
555
+ return jsonify({'error': f'System validation failed: {str(e)}'}), 500
556
+
557
+ @app.route('/api/audit', methods=['GET'])
558
+ def get_audit_trail():
559
+ """Get audit trail"""
560
+ try:
561
+ if not ensure_system_ready():
562
+ return jsonify({'error': 'RAG system not ready'}), 500
563
+
564
+ audit_log = rag_system.get_audit_trail()
565
+ return jsonify({
566
+ 'audit_trail': audit_log,
567
+ 'total_entries': len(audit_log),
568
+ 'timestamp': datetime.now().isoformat()
569
+ })
570
+
571
+ except Exception as e:
572
+ logger.error(f"Audit trail retrieval failed: {e}")
573
+ return jsonify({'error': f'Audit trail retrieval failed: {str(e)}'}), 500
574
+
575
+ @app.route('/api/export', methods=['POST'])
576
+ def export_system_data():
577
+ """Export system data"""
578
+ try:
579
+ data = request.get_json() or {}
580
+ filename = data.get('filename', f'system_export_{datetime.now().strftime("%Y%m%d_%H%M%S")}.json')
581
+
582
+ if not ensure_system_ready():
583
+ return jsonify({'error': 'RAG system not ready'}), 500
584
+
585
+ success = rag_system.export_system_data(filename)
586
+
587
+ if success:
588
+ return jsonify({
589
+ 'success': True,
590
+ 'message': 'System data exported successfully',
591
+ 'filename': filename,
592
+ 'timestamp': datetime.now().isoformat()
593
+ })
594
+ else:
595
+ return jsonify({'error': 'Failed to export system data'}), 500
596
+
597
+ except Exception as e:
598
+ logger.error(f"System export failed: {e}")
599
+ return jsonify({'error': f'System export failed: {str(e)}'}), 500
600
+
601
+ @app.route('/api/clear', methods=['POST'])
602
+ def clear_system():
603
+ """Clear system data"""
604
+ try:
605
+ if not ensure_system_ready():
606
+ return jsonify({'error': 'RAG system not ready'}), 500
607
+
608
+ success = rag_system.clear_system()
609
+
610
+ return jsonify({
611
+ 'success': success,
612
+ 'message': 'System cleared successfully' if success else 'Failed to clear system',
613
+ 'timestamp': datetime.now().isoformat()
614
+ })
615
+
616
+ except Exception as e:
617
+ logger.error(f"System clear failed: {e}")
618
+ return jsonify({'error': f'System clear failed: {str(e)}'}), 500
619
+
620
+ @app.route('/api/statistics', methods=['GET'])
621
+ def get_statistics():
622
+ """Get system statistics"""
623
+ try:
624
+ if not ensure_system_ready():
625
+ return jsonify({'error': 'RAG system not ready'}), 500
626
+
627
+ stats = rag_system.get_system_statistics()
628
+ return jsonify(stats)
629
+
630
+ except Exception as e:
631
+ logger.error(f"Statistics retrieval failed: {e}")
632
+ return jsonify({'error': f'Statistics retrieval failed: {str(e)}'}), 500
633
+
634
+ # Error handlers
635
+ @app.errorhandler(404)
636
+ def not_found(error):
637
+ return jsonify({'error': 'Endpoint not found'}), 404
638
+
639
+ @app.errorhandler(500)
640
+ def internal_error(error):
641
+ return jsonify({'error': 'Internal server error'}), 500
642
+
643
+ @app.errorhandler(Exception)
644
+ def handle_exception(e):
645
+ logger.error(f"Unhandled exception: {e}")
646
+ return jsonify({'error': 'Internal server error'}), 500
647
+
648
+ if __name__ == '__main__':
649
+ print("🚀 Starting Flask API Pipeline for Advanced RAG System")
650
+ print("=" * 60)
651
+ print("📋 Available endpoints:")
652
+ print(" GET /api/health - Health check")
653
+ print(" GET /api/status - System status")
654
+ print(" POST /api/upload - Upload document")
655
+ print(" POST /api/query - Process single query")
656
+ print(" POST /api/batch_query - Process multiple queries")
657
+ print(" GET /api/validate - Validate system")
658
+ print(" GET /api/audit - Get audit trail")
659
+ print(" POST /api/export - Export system data")
660
+ print(" POST /api/clear - Clear system")
661
+ print(" GET /api/statistics - Get statistics")
662
+ print("=" * 60)
663
+
664
+ # Initialize system on startup
665
+ logger.info("Starting Flask API Pipeline")
666
+ if initialize_rag_system():
667
+ print("✅ RAG system initialized successfully!")
668
+ logger.info("RAG system initialized successfully")
669
+ else:
670
+ print("⚠️ RAG system initialization failed - will retry on first request")
671
+ logger.warning("RAG system initialization failed")
672
+
673
+ print(f"🌐 Server will run on http://127.0.0.1:5000")
674
+ print("=" * 60)
675
+ logger.info("Starting Flask server...")
676
+
677
+ try:
678
+ app.run(
679
+ debug=False,
680
+ host='0.0.0.0',
681
+ port=5000,
682
+ threaded=True,
683
+ use_reloader=False # Prevent auto-restart issues
684
+ )
685
+ except KeyboardInterrupt:
686
+ logger.info("Server stopped by user (Ctrl+C)")
687
+ print("\n👋 Server stopped by user")
688
+ except Exception as e:
689
+ logger.error(f"Server crashed: {e}")
690
+ print(f"❌ Server crashed: {e}")
691
+ raise
app2.py ADDED
@@ -0,0 +1,690 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Flask API Pipeline for RAG System
3
+ Dedicated API server for the Advanced RAG System
4
+ Separate from main.py (command-line pipeline)
5
+ """
6
+
7
+ from flask import Flask, request, jsonify, send_file
8
+ import os
9
+ import time
10
+ import json
11
+ import logging
12
+ from datetime import datetime
13
+ from pathlib import Path
14
+ from typing import List, Dict, Any, Optional
15
+ import tempfile
16
+ import shutil
17
+ import requests
18
+
19
+ # Import RAG system components
20
+ from rag_system import AdvancedRAGSystem, QueryResult
21
+ from document_processer import AdvancedDocumentProcessor, DocumentChunk
22
+ from vector_database import VectorDatabase, SearchResult
23
+ from query_parser import AdvancedQueryParser, ParsedQuery
24
+ from llm_reasoning import AdvancedLLMReasoning, ReasoningResult
25
+
26
+ # Configure logging
27
+ logging.basicConfig(
28
+ level=logging.INFO,
29
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
30
+ handlers=[
31
+ logging.FileHandler('app2.log'),
32
+ logging.StreamHandler()
33
+ ]
34
+ )
35
+ logger = logging.getLogger(__name__)
36
+
37
+ app = Flask(__name__)
38
+
39
+ # Global RAG system instance
40
+ rag_system = None
41
+ system_initialized = False
42
+
43
+ # Request logging middleware
44
+ @app.before_request
45
+ def log_request_info():
46
+ """Log all incoming requests"""
47
+ logger.info(f"Request: {request.method} {request.url}")
48
+ if request.method == 'POST':
49
+ logger.info(f"Request data: {request.get_data()[:200]}...")
50
+
51
+ @app.after_request
52
+ def log_response_info(response):
53
+ """Log all outgoing responses"""
54
+ logger.info(f"Response: {response.status_code} for {request.method} {request.url}")
55
+ return response
56
+
57
+ @app.route('/', methods=['GET'])
58
+ def root():
59
+ """Root endpoint with API documentation"""
60
+ logger.info("Root endpoint accessed")
61
+ return jsonify({
62
+ 'message': 'RAG System API Pipeline',
63
+ 'version': '2.0.0',
64
+ 'status': 'running',
65
+ 'endpoints': {
66
+ 'health': 'GET /api/health',
67
+ 'status': 'GET /api/status',
68
+ 'upload': 'POST /api/upload',
69
+ 'query': 'POST /api/query',
70
+ 'batch_query': 'POST /api/batch_query',
71
+ 'validate': 'GET /api/validate',
72
+ 'audit': 'GET /api/audit',
73
+ 'statistics': 'GET /api/statistics',
74
+ 'export': 'POST /api/export',
75
+ 'clear': 'POST /api/clear',
76
+ 'keep_alive': 'GET /api/keep-alive',
77
+ 'hackrx_run': 'POST /hackrx/run',
78
+ 'hackrx_upload': 'POST /hackrx/upload'
79
+ },
80
+ 'description': 'Advanced RAG System API Pipeline',
81
+ 'features': [
82
+ 'Document upload and processing',
83
+ 'Natural language query processing',
84
+ 'Policy coverage analysis',
85
+ 'Claim requirement extraction',
86
+ 'Audit trail and statistics',
87
+ 'Hackathon-compatible endpoints'
88
+ ],
89
+ 'timestamp': datetime.now().isoformat()
90
+ })
91
+
92
+ def initialize_rag_system():
93
+ """Initialize the RAG system"""
94
+ global rag_system, system_initialized
95
+
96
+ try:
97
+ logger.info("Initializing RAG system...")
98
+ rag_system = AdvancedRAGSystem(
99
+ model_path="./mistral-7b-instruct-v0.1.Q4_K_M.gguf",
100
+ use_gpu=False, # Use CPU for better compatibility
101
+ vector_db_path="./vector_db"
102
+ )
103
+ system_initialized = True
104
+ logger.info("RAG system initialized successfully!")
105
+ return True
106
+ except Exception as e:
107
+ logger.error(f"RAG system initialization failed: {e}")
108
+ return False
109
+
110
+ def ensure_system_ready():
111
+ """Ensure RAG system is ready"""
112
+ if not system_initialized:
113
+ return initialize_rag_system()
114
+ return True
115
+
116
+ @app.route('/api/health', methods=['GET'])
117
+ def health_check():
118
+ """Health check endpoint"""
119
+ logger.info("Health check requested")
120
+ try:
121
+ return jsonify({
122
+ 'status': 'healthy',
123
+ 'timestamp': datetime.now().isoformat(),
124
+ 'system_initialized': system_initialized,
125
+ 'rag_system_ready': system_initialized,
126
+ 'message': 'RAG System API is running'
127
+ })
128
+ except Exception as e:
129
+ logger.error(f"Health check failed: {e}")
130
+ return jsonify({'error': f'Health check failed: {str(e)}'}), 500
131
+
132
+ @app.route('/api/keep-alive', methods=['GET'])
133
+ def keep_alive():
134
+ """Keep-alive endpoint to prevent server termination"""
135
+ logger.info("Keep-alive ping received")
136
+ return jsonify({
137
+ 'status': 'alive',
138
+ 'timestamp': datetime.now().isoformat(),
139
+ 'message': 'RAG System API is running'
140
+ })
141
+
142
+ @app.route('/hackrx/run', methods=['POST'])
143
+ def hackrx_run():
144
+ """Hackathon run endpoint"""
145
+ logger.info("HackRX run endpoint accessed")
146
+ try:
147
+ # Check authorization
148
+ auth_header = request.headers.get('Authorization')
149
+ if not auth_header or not auth_header.startswith('Bearer '):
150
+ logger.warning("Missing or invalid Authorization header")
151
+ return jsonify({'error': 'Unauthorized'}), 401
152
+
153
+ api_key = auth_header.split(' ')[1]
154
+ # For now, accept any Bearer token
155
+ logger.info(f"API key provided: {api_key[:10]}...")
156
+
157
+ # Get request data
158
+ data = request.get_json()
159
+ if not data:
160
+ logger.warning("No JSON data provided")
161
+ return jsonify({'error': 'No JSON data provided'}), 400
162
+
163
+ documents_url = data.get('documents')
164
+ questions = data.get('questions', [])
165
+
166
+ if not questions:
167
+ logger.warning("No questions provided")
168
+ return jsonify({'error': 'No questions provided'}), 400
169
+
170
+ logger.info(f"Processing {len(questions)} questions")
171
+ if documents_url:
172
+ logger.info(f"Document URL provided: {documents_url}")
173
+
174
+ # Ensure system is ready
175
+ if not ensure_system_ready():
176
+ logger.error("RAG system not ready")
177
+ return jsonify({'error': 'RAG system not ready'}), 500
178
+
179
+ # Download and process document if URL provided
180
+ if documents_url:
181
+ try:
182
+ logger.info("Downloading document from URL...")
183
+ response = requests.get(documents_url, timeout=30)
184
+ response.raise_for_status()
185
+
186
+ # Save to temporary file
187
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.pdf')
188
+ temp_file.write(response.content)
189
+ temp_file.close()
190
+
191
+ logger.info(f"Document downloaded to: {temp_file.name}")
192
+
193
+ # Ingest document
194
+ chunks = rag_system.ingest_document(temp_file.name, use_ocr=False)
195
+ logger.info(f"Document ingested: {len(chunks)} chunks created")
196
+
197
+ # Clean up
198
+ os.unlink(temp_file.name)
199
+
200
+ except Exception as e:
201
+ logger.error(f"Document download/processing failed: {e}")
202
+ return jsonify({'error': f'Document processing failed: {str(e)}'}), 500
203
+
204
+ # Process questions
205
+ answers = []
206
+ for i, question in enumerate(questions):
207
+ try:
208
+ logger.info(f"Processing question {i+1}/{len(questions)}: {question[:50]}...")
209
+ result = rag_system.process_query(question)
210
+
211
+ if result and result.reasoning_result:
212
+ answer = result.reasoning_result.justification
213
+ answers.append(answer)
214
+ logger.info(f"Question {i+1} processed successfully")
215
+ else:
216
+ answers.append("Unable to process this question.")
217
+ logger.warning(f"Question {i+1} failed to process")
218
+
219
+ except Exception as e:
220
+ logger.error(f"Question {i+1} processing failed: {e}")
221
+ answers.append(f"Error processing question: {str(e)}")
222
+
223
+ response_data = {'answers': answers}
224
+ logger.info(f"HackRX run completed: {len(answers)} answers generated")
225
+ return jsonify(response_data)
226
+
227
+ except Exception as e:
228
+ logger.error(f"HackRX run failed: {e}")
229
+ return jsonify({'error': f'Processing failed: {str(e)}'}), 500
230
+
231
+ @app.route('/hackrx/upload', methods=['POST'])
232
+ def hackrx_upload():
233
+ """Hackathon upload endpoint"""
234
+ logger.info("HackRX upload endpoint accessed")
235
+ try:
236
+ # Check if file is uploaded
237
+ if 'file' not in request.files:
238
+ logger.warning("No file provided in hackrx/upload request")
239
+ return jsonify({'error': 'No file provided'}), 400
240
+
241
+ file = request.files['file']
242
+ if file.filename == '':
243
+ logger.warning("Empty filename in hackrx/upload request")
244
+ return jsonify({'error': 'No file selected'}), 400
245
+
246
+ logger.info(f"Processing file: {file.filename}")
247
+
248
+ # Ensure system is ready
249
+ if not ensure_system_ready():
250
+ logger.error("RAG system not ready for upload")
251
+ return jsonify({'error': 'RAG system not ready'}), 500
252
+
253
+ # Check file type
254
+ supported_extensions = {'.pdf', '.txt', '.docx', '.html', '.htm', '.eml', '.msg', '.csv', '.json'}
255
+ file_extension = Path(file.filename).suffix.lower()
256
+
257
+ if file_extension not in supported_extensions:
258
+ logger.warning(f"Unsupported file type: {file_extension}")
259
+ return jsonify({'error': f'Unsupported file type: {file_extension}'}), 400
260
+
261
+ # Save uploaded file
262
+ upload_dir = Path('uploads')
263
+ upload_dir.mkdir(exist_ok=True)
264
+
265
+ file_path = upload_dir / file.filename
266
+ file.save(str(file_path))
267
+ logger.info(f"File saved to: {file_path}")
268
+
269
+ try:
270
+ # Process document
271
+ start_time = time.time()
272
+ logger.info("Starting document ingestion...")
273
+ chunks = rag_system.ingest_document(str(file_path), use_ocr=False)
274
+ processing_time = time.time() - start_time
275
+
276
+ logger.info(f"Document processed successfully: {len(chunks)} chunks created in {processing_time:.2f}s")
277
+
278
+ # Clean up uploaded file
279
+ os.remove(str(file_path))
280
+ logger.info("Temporary file cleaned up")
281
+
282
+ response = {
283
+ 'success': True,
284
+ 'message': 'Document processed successfully',
285
+ 'filename': file.filename,
286
+ 'chunks_processed': len(chunks),
287
+ 'processing_time': processing_time,
288
+ 'file_type': file_extension,
289
+ 'timestamp': datetime.now().isoformat()
290
+ }
291
+ logger.info(f"HackRX upload response: {response}")
292
+ return jsonify(response)
293
+
294
+ except Exception as e:
295
+ # Clean up on error
296
+ if os.path.exists(str(file_path)):
297
+ os.remove(str(file_path))
298
+ logger.info("Cleaned up file after error")
299
+ logger.error(f"Document processing error: {e}")
300
+ raise e
301
+
302
+ except Exception as e:
303
+ logger.error(f"HackRX upload failed: {e}")
304
+ return jsonify({'error': f'Document processing failed: {str(e)}'}), 500
305
+
306
+ @app.route('/api/status', methods=['GET'])
307
+ def system_status():
308
+ """Get detailed system status"""
309
+ try:
310
+ if not ensure_system_ready():
311
+ return jsonify({
312
+ 'status': 'error',
313
+ 'message': 'RAG system initialization failed'
314
+ }), 500
315
+
316
+ # Get system statistics
317
+ stats = rag_system.get_system_statistics()
318
+
319
+ return jsonify({
320
+ 'status': 'ready',
321
+ 'system_statistics': stats,
322
+ 'timestamp': datetime.now().isoformat()
323
+ })
324
+
325
+ except Exception as e:
326
+ logger.error(f"Status check failed: {e}")
327
+ return jsonify({
328
+ 'status': 'error',
329
+ 'message': f'Status check failed: {str(e)}'
330
+ }), 500
331
+
332
+ @app.route('/api/upload', methods=['POST'])
333
+ def upload_document():
334
+ """Upload document endpoint"""
335
+ logger.info("Upload endpoint accessed")
336
+ try:
337
+ # Check if file is uploaded
338
+ if 'file' not in request.files:
339
+ logger.warning("No file provided in upload request")
340
+ return jsonify({'error': 'No file provided'}), 400
341
+
342
+ file = request.files['file']
343
+ if file.filename == '':
344
+ logger.warning("Empty filename in upload request")
345
+ return jsonify({'error': 'No file selected'}), 400
346
+
347
+ logger.info(f"Processing file: {file.filename}")
348
+
349
+ # Ensure system is ready
350
+ if not ensure_system_ready():
351
+ logger.error("RAG system not ready for upload")
352
+ return jsonify({'error': 'RAG system not ready'}), 500
353
+
354
+ # Check file type
355
+ supported_extensions = {'.pdf', '.txt', '.docx', '.html', '.htm', '.eml', '.msg', '.csv', '.json'}
356
+ file_extension = Path(file.filename).suffix.lower()
357
+
358
+ if file_extension not in supported_extensions:
359
+ logger.warning(f"Unsupported file type: {file_extension}")
360
+ return jsonify({'error': f'Unsupported file type: {file_extension}'}), 400
361
+
362
+ # Save uploaded file
363
+ upload_dir = Path('uploads')
364
+ upload_dir.mkdir(exist_ok=True)
365
+
366
+ file_path = upload_dir / file.filename
367
+ file.save(str(file_path))
368
+ logger.info(f"File saved to: {file_path}")
369
+
370
+ try:
371
+ # Process document
372
+ start_time = time.time()
373
+ logger.info("Starting document ingestion...")
374
+ chunks = rag_system.ingest_document(str(file_path), use_ocr=False)
375
+ processing_time = time.time() - start_time
376
+
377
+ logger.info(f"Document processed successfully: {len(chunks)} chunks created in {processing_time:.2f}s")
378
+
379
+ # Clean up uploaded file
380
+ os.remove(str(file_path))
381
+ logger.info("Temporary file cleaned up")
382
+
383
+ response = {
384
+ 'success': True,
385
+ 'message': 'Document uploaded and processed successfully',
386
+ 'filename': file.filename,
387
+ 'chunks_processed': len(chunks),
388
+ 'processing_time': processing_time,
389
+ 'file_type': file_extension,
390
+ 'timestamp': datetime.now().isoformat()
391
+ }
392
+ logger.info(f"Upload response: {response}")
393
+ return jsonify(response)
394
+
395
+ except Exception as e:
396
+ # Clean up on error
397
+ if os.path.exists(str(file_path)):
398
+ os.remove(str(file_path))
399
+ logger.info("Cleaned up file after error")
400
+ logger.error(f"Document processing error: {e}")
401
+ raise e
402
+
403
+ except Exception as e:
404
+ logger.error(f"Upload failed: {e}")
405
+ return jsonify({'error': f'Document processing failed: {str(e)}'}), 500
406
+
407
+ @app.route('/api/query', methods=['POST'])
408
+ def process_query():
409
+ """Process single query endpoint"""
410
+ logger.info("Query endpoint accessed")
411
+ try:
412
+ data = request.get_json()
413
+ if not data:
414
+ logger.warning("No JSON data provided")
415
+ return jsonify({'error': 'No JSON data provided'}), 400
416
+
417
+ query = data.get('query')
418
+ if not query:
419
+ logger.warning("No query provided")
420
+ return jsonify({'error': 'No query provided'}), 400
421
+
422
+ logger.info(f"Processing query: {query[:50]}...")
423
+
424
+ # Ensure system is ready
425
+ if not ensure_system_ready():
426
+ logger.error("RAG system not ready")
427
+ return jsonify({'error': 'RAG system not ready'}), 500
428
+
429
+ # Process query
430
+ start_time = time.time()
431
+ result = rag_system.process_query(query)
432
+ processing_time = time.time() - start_time
433
+
434
+ if result:
435
+ response = {
436
+ 'success': True,
437
+ 'query': query,
438
+ 'answer': result.reasoning_result.justification if result.reasoning_result else "No answer generated",
439
+ 'confidence': result.reasoning_result.confidence if result.reasoning_result else 0.0,
440
+ 'processing_time': processing_time,
441
+ 'timestamp': datetime.now().isoformat()
442
+ }
443
+ logger.info(f"Query processed successfully in {processing_time:.2f}s")
444
+ return jsonify(response)
445
+ else:
446
+ logger.warning("Query processing returned no result")
447
+ return jsonify({'error': 'No result generated'}), 500
448
+
449
+ except Exception as e:
450
+ logger.error(f"Query processing failed: {e}")
451
+ return jsonify({'error': f'Query processing failed: {str(e)}'}), 500
452
+
453
+ @app.route('/api/batch_query', methods=['POST'])
454
+ def process_batch_queries():
455
+ """Process multiple queries endpoint"""
456
+ logger.info("Batch query endpoint accessed")
457
+ try:
458
+ data = request.get_json()
459
+ if not data:
460
+ logger.warning("No JSON data provided")
461
+ return jsonify({'error': 'No JSON data provided'}), 400
462
+
463
+ queries = data.get('queries', [])
464
+ if not queries:
465
+ logger.warning("No queries provided")
466
+ return jsonify({'error': 'No queries provided'}), 400
467
+
468
+ logger.info(f"Processing {len(queries)} queries")
469
+
470
+ # Ensure system is ready
471
+ if not ensure_system_ready():
472
+ logger.error("RAG system not ready")
473
+ return jsonify({'error': 'RAG system not ready'}), 500
474
+
475
+ # Process queries
476
+ results = []
477
+ start_time = time.time()
478
+
479
+ for i, query in enumerate(queries):
480
+ try:
481
+ logger.info(f"Processing query {i+1}/{len(queries)}: {query[:50]}...")
482
+ result = rag_system.process_query(query)
483
+
484
+ if result and result.reasoning_result:
485
+ results.append({
486
+ 'query': query,
487
+ 'answer': result.reasoning_result.justification,
488
+ 'confidence': result.reasoning_result.confidence,
489
+ 'success': True
490
+ })
491
+ else:
492
+ results.append({
493
+ 'query': query,
494
+ 'answer': "No answer generated",
495
+ 'confidence': 0.0,
496
+ 'success': False
497
+ })
498
+
499
+ except Exception as e:
500
+ logger.error(f"Query {i+1} processing failed: {e}")
501
+ results.append({
502
+ 'query': query,
503
+ 'answer': f"Error: {str(e)}",
504
+ 'confidence': 0.0,
505
+ 'success': False
506
+ })
507
+
508
+ processing_time = time.time() - start_time
509
+
510
+ response = {
511
+ 'success': True,
512
+ 'queries_processed': len(queries),
513
+ 'successful_queries': len([r for r in results if r['success']]),
514
+ 'processing_time': processing_time,
515
+ 'results': results,
516
+ 'timestamp': datetime.now().isoformat()
517
+ }
518
+
519
+ logger.info(f"Batch query completed: {len(results)} results in {processing_time:.2f}s")
520
+ return jsonify(response)
521
+
522
+ except Exception as e:
523
+ logger.error(f"Batch query processing failed: {e}")
524
+ return jsonify({'error': f'Batch query processing failed: {str(e)}'}), 500
525
+
526
+ @app.route('/api/validate', methods=['GET'])
527
+ def validate_system():
528
+ """Validate system endpoint"""
529
+ logger.info("Validate endpoint accessed")
530
+ try:
531
+ if not ensure_system_ready():
532
+ return jsonify({'error': 'RAG system not ready'}), 500
533
+
534
+ # Perform validation checks
535
+ validation_results = {
536
+ 'rag_system_ready': system_initialized,
537
+ 'vector_db_accessible': True, # Add actual check if needed
538
+ 'model_loaded': True, # Add actual check if needed
539
+ 'timestamp': datetime.now().isoformat()
540
+ }
541
+
542
+ return jsonify(validation_results)
543
+
544
+ except Exception as e:
545
+ logger.error(f"Validation failed: {e}")
546
+ return jsonify({'error': f'Validation failed: {str(e)}'}), 500
547
+
548
+ @app.route('/api/audit', methods=['GET'])
549
+ def get_audit_trail():
550
+ """Get audit trail endpoint"""
551
+ logger.info("Audit endpoint accessed")
552
+ try:
553
+ if not ensure_system_ready():
554
+ return jsonify({'error': 'RAG system not ready'}), 500
555
+
556
+ # Get audit trail from RAG system
557
+ audit_trail = rag_system.get_audit_trail()
558
+
559
+ return jsonify({
560
+ 'audit_trail': audit_trail,
561
+ 'timestamp': datetime.now().isoformat()
562
+ })
563
+
564
+ except Exception as e:
565
+ logger.error(f"Audit trail retrieval failed: {e}")
566
+ return jsonify({'error': f'Audit trail retrieval failed: {str(e)}'}), 500
567
+
568
+ @app.route('/api/export', methods=['POST'])
569
+ def export_system_data():
570
+ """Export system data endpoint"""
571
+ logger.info("Export endpoint accessed")
572
+ try:
573
+ if not ensure_system_ready():
574
+ return jsonify({'error': 'RAG system not ready'}), 500
575
+
576
+ data = request.get_json() or {}
577
+ export_format = data.get('format', 'json')
578
+
579
+ # Export system data
580
+ export_data = rag_system.export_system_data(export_format)
581
+
582
+ if export_data:
583
+ return jsonify({
584
+ 'success': True,
585
+ 'format': export_format,
586
+ 'data': export_data,
587
+ 'timestamp': datetime.now().isoformat()
588
+ })
589
+ else:
590
+ return jsonify({'error': 'Failed to export system data'}), 500
591
+
592
+ except Exception as e:
593
+ logger.error(f"System export failed: {e}")
594
+ return jsonify({'error': f'System export failed: {str(e)}'}), 500
595
+
596
+ @app.route('/api/clear', methods=['POST'])
597
+ def clear_system():
598
+ """Clear system data endpoint"""
599
+ logger.info("Clear endpoint accessed")
600
+ try:
601
+ if not ensure_system_ready():
602
+ return jsonify({'error': 'RAG system not ready'}), 500
603
+
604
+ success = rag_system.clear_system()
605
+
606
+ return jsonify({
607
+ 'success': success,
608
+ 'message': 'System cleared successfully' if success else 'Failed to clear system',
609
+ 'timestamp': datetime.now().isoformat()
610
+ })
611
+
612
+ except Exception as e:
613
+ logger.error(f"System clear failed: {e}")
614
+ return jsonify({'error': f'System clear failed: {str(e)}'}), 500
615
+
616
+ @app.route('/api/statistics', methods=['GET'])
617
+ def get_statistics():
618
+ """Get system statistics endpoint"""
619
+ logger.info("Statistics endpoint accessed")
620
+ try:
621
+ if not ensure_system_ready():
622
+ return jsonify({'error': 'RAG system not ready'}), 500
623
+
624
+ stats = rag_system.get_system_statistics()
625
+ return jsonify(stats)
626
+
627
+ except Exception as e:
628
+ logger.error(f"Statistics retrieval failed: {e}")
629
+ return jsonify({'error': f'Statistics retrieval failed: {str(e)}'}), 500
630
+
631
+ # Error handlers
632
+ @app.errorhandler(404)
633
+ def not_found(error):
634
+ return jsonify({'error': 'Endpoint not found'}), 404
635
+
636
+ @app.errorhandler(500)
637
+ def internal_error(error):
638
+ return jsonify({'error': 'Internal server error'}), 500
639
+
640
+ @app.errorhandler(Exception)
641
+ def handle_exception(e):
642
+ logger.error(f"Unhandled exception: {e}")
643
+ return jsonify({'error': 'Internal server error'}), 500
644
+
645
+ if __name__ == '__main__':
646
+ print("🚀 Starting RAG System API Pipeline (app2.py)")
647
+ print("=" * 60)
648
+ print("📋 Available endpoints:")
649
+ print(" GET /api/health - Health check")
650
+ print(" GET /api/status - System status")
651
+ print(" POST /api/upload - Upload document")
652
+ print(" POST /api/query - Process single query")
653
+ print(" POST /api/batch_query - Process multiple queries")
654
+ print(" GET /api/validate - Validate system")
655
+ print(" GET /api/audit - Get audit trail")
656
+ print(" POST /api/export - Export system data")
657
+ print(" POST /api/clear - Clear system")
658
+ print(" GET /api/statistics - Get statistics")
659
+ print(" POST /hackrx/run - Hackathon run endpoint")
660
+ print(" POST /hackrx/upload - Hackathon upload endpoint")
661
+ print("=" * 60)
662
+
663
+ # Initialize system on startup
664
+ logger.info("Starting RAG System API Pipeline")
665
+ if initialize_rag_system():
666
+ print("✅ RAG system initialized successfully!")
667
+ logger.info("RAG system initialized successfully")
668
+ else:
669
+ print("⚠️ RAG system initialization failed - will retry on first request")
670
+ logger.warning("RAG system initialization failed")
671
+
672
+ print(f"🌐 Server will run on http://127.0.0.1:5000")
673
+ print("=" * 60)
674
+ logger.info("Starting Flask server...")
675
+
676
+ try:
677
+ app.run(
678
+ debug=False,
679
+ host='0.0.0.0',
680
+ port=5000,
681
+ threaded=True,
682
+ use_reloader=False # Prevent auto-restart issues
683
+ )
684
+ except KeyboardInterrupt:
685
+ logger.info("Server stopped by user (Ctrl+C)")
686
+ print("\n👋 Server stopped by user")
687
+ except Exception as e:
688
+ logger.error(f"Server crashed: {e}")
689
+ print(f"❌ Server crashed: {e}")
690
+ raise
demo.py ADDED
@@ -0,0 +1,419 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Comprehensive RAG System Demo
3
+ Demonstrates all features of the RAG system including document ingestion, query processing, and audit trail
4
+ """
5
+
6
+ import os
7
+ import json
8
+ import time
9
+ from pathlib import Path
10
+ from rag_system_gpu import RAGSystem
11
+
12
+ def print_section(title):
13
+ """Print a formatted section header"""
14
+ print("\n" + "="*60)
15
+ print(f" {title}")
16
+ print("="*60)
17
+
18
+ def print_subsection(title):
19
+ """Print a formatted subsection header"""
20
+ print(f"\n--- {title} ---")
21
+
22
+ def demo_document_ingestion():
23
+ """Interactive document ingestion with file upload from anywhere"""
24
+ print_section("DOCUMENT INGESTION")
25
+
26
+ try:
27
+ rag_system = RAGSystem(use_gpu=True) # Use GPU for better performance
28
+ print("✅ GPU-optimized RAG system initialized successfully")
29
+ except Exception as e:
30
+ print(f"❌ Failed to initialize RAG system: {e}")
31
+ print("💡 This might be due to missing model file or dependencies")
32
+ return None
33
+
34
+ print_subsection("PDF Document Upload")
35
+ print("💡 Please provide the path to your PDF document from anywhere on your system.")
36
+ print("💡 Supported formats: PDF files")
37
+ print("💡 Type 'sample' to use the default sample.pdf (if available)")
38
+ print("💡 Type 'browse' to open file browser (if available)")
39
+ print("💡 Type 'quit' to exit")
40
+ print()
41
+
42
+ while True:
43
+ try:
44
+ # Get user input for file path
45
+ file_path = input("📁 Enter PDF file path (or 'browse'/'sample'): ").strip()
46
+
47
+ # Check for exit commands
48
+ if file_path.lower() in ['quit', 'exit', 'q']:
49
+ print("👋 Goodbye!")
50
+ return None
51
+
52
+ # Check for browse command
53
+ if file_path.lower() == 'browse':
54
+ try:
55
+ import tkinter as tk
56
+ from tkinter import filedialog
57
+
58
+ # Create a hidden root window
59
+ root = tk.Tk()
60
+ root.withdraw() # Hide the main window
61
+
62
+ # Open file dialog
63
+ file_path = filedialog.askopenfilename(
64
+ title="Select PDF File",
65
+ filetypes=[("PDF files", "*.pdf"), ("All files", "*.*")]
66
+ )
67
+
68
+ root.destroy() # Close the hidden window
69
+
70
+ if not file_path:
71
+ print("❌ No file selected.")
72
+ continue
73
+
74
+ print(f"✅ Selected file: {file_path}")
75
+
76
+ except ImportError:
77
+ print("❌ File browser not available. Please enter the file path manually.")
78
+ continue
79
+ except Exception as e:
80
+ print(f"❌ Error opening file browser: {e}")
81
+ print("💡 Please enter the file path manually.")
82
+ continue
83
+
84
+ # Check for sample command
85
+ elif file_path.lower() == 'sample':
86
+ if os.path.exists("sample.pdf"):
87
+ file_path = "sample.pdf"
88
+ print("📄 Using sample.pdf...")
89
+ else:
90
+ print("❌ sample.pdf not found. Please provide a different file path.")
91
+ continue
92
+
93
+ # Skip empty input
94
+ elif not file_path:
95
+ print("⚠️ Please enter a file path.")
96
+ continue
97
+
98
+ # Check if file exists
99
+ if not os.path.exists(file_path):
100
+ print(f"❌ File not found: {file_path}")
101
+ print("💡 Please check the file path and try again.")
102
+ continue
103
+
104
+ # Check if it's a PDF file
105
+ if not file_path.lower().endswith('.pdf'):
106
+ print("❌ Only PDF files are supported.")
107
+ print("💡 Please provide a PDF file.")
108
+ continue
109
+
110
+ print_subsection(f"Processing PDF Document")
111
+ print(f"📄 File: {os.path.basename(file_path)}")
112
+ print(f"📂 Path: {file_path}")
113
+
114
+ # Ask about OCR
115
+ use_ocr_input = input("🔍 Use OCR for scanned documents? (y/n, default: n): ").strip().lower()
116
+ use_ocr = use_ocr_input in ['y', 'yes']
117
+
118
+ if use_ocr:
119
+ print("🔍 OCR enabled - processing scanned document...")
120
+ else:
121
+ print("📄 Processing as text-based PDF...")
122
+
123
+ # Ingest the document
124
+ print("⏳ Processing document...")
125
+ start_time = time.time()
126
+ chunks = rag_system.ingest_document(file_path, use_ocr=use_ocr)
127
+ processing_time = time.time() - start_time
128
+
129
+ print(f"✅ Successfully processed {len(chunks)} chunks in {processing_time:.2f} seconds")
130
+ print(f"📊 Document chunks created:")
131
+
132
+ for i, chunk in enumerate(chunks[:5]): # Show first 5 chunks
133
+ print(f" Chunk {i+1}: {chunk.chunk_id}")
134
+ print(f" Content preview: {chunk.content[:100]}...")
135
+ print()
136
+
137
+ if len(chunks) > 5:
138
+ print(f" ... and {len(chunks) - 5} more chunks")
139
+
140
+ print("🎉 Document processing completed successfully!")
141
+ return rag_system
142
+
143
+ except KeyboardInterrupt:
144
+ print("\n👋 Interrupted by user. Goodbye!")
145
+ return None
146
+ except Exception as e:
147
+ print(f"❌ Error during document ingestion: {e}")
148
+ print("💡 Please check if the file is a valid PDF and try again.")
149
+ print("💡 For scanned documents, try enabling OCR.")
150
+ continue
151
+
152
+ print_subsection(f"Processing PDF Document")
153
+ print(f"📄 File: {selected_file.name}")
154
+
155
+ # Ask about OCR
156
+ use_ocr_input = input("🔍 Use OCR for scanned documents? (y/n, default: n): ").strip().lower()
157
+ use_ocr = use_ocr_input in ['y', 'yes']
158
+
159
+ if use_ocr:
160
+ print("🔍 OCR enabled - processing scanned document...")
161
+ else:
162
+ print("📄 Processing as text-based PDF...")
163
+
164
+ # Ingest the document
165
+ print("⏳ Processing document...")
166
+ start_time = time.time()
167
+ chunks = rag_system.ingest_document(str(selected_file), use_ocr=use_ocr)
168
+ processing_time = time.time() - start_time
169
+
170
+ print(f"✅ Successfully processed {len(chunks)} chunks in {processing_time:.2f} seconds")
171
+ print(f"📊 Document chunks created:")
172
+
173
+ for i, chunk in enumerate(chunks[:5]): # Show first 5 chunks
174
+ print(f" Chunk {i+1}: {chunk.chunk_id}")
175
+ print(f" Content preview: {chunk.content[:100]}...")
176
+ print()
177
+
178
+ if len(chunks) > 5:
179
+ print(f" ... and {len(chunks) - 5} more chunks")
180
+
181
+ print("🎉 Document processing completed successfully!")
182
+ return rag_system
183
+
184
+ except KeyboardInterrupt:
185
+ print("\n👋 Interrupted by user. Goodbye!")
186
+ return None
187
+ except Exception as e:
188
+ print(f"❌ Error during document ingestion: {e}")
189
+ print("💡 Please check if the file is a valid PDF and try again.")
190
+ print("💡 For scanned documents, try enabling OCR.")
191
+ continue
192
+
193
+ def demo_query_processing(rag_system):
194
+ """Interactive query processing with user input"""
195
+ print_section("INTERACTIVE QUERY PROCESSING")
196
+
197
+ print("💡 Enter your insurance policy questions below.")
198
+ print("💡 Type 'quit' or 'exit' to stop asking questions.")
199
+ print("💡 Type 'help' for example questions.")
200
+ print()
201
+
202
+ results = []
203
+ query_count = 0
204
+
205
+ while True:
206
+ try:
207
+ # Get user input
208
+ user_query = input("🤔 Enter your question: ").strip()
209
+
210
+ # Check for exit commands
211
+ if user_query.lower() in ['quit', 'exit', 'q']:
212
+ print("👋 Goodbye!")
213
+ break
214
+
215
+ # Check for help command
216
+ if user_query.lower() == 'help':
217
+ print("\n📋 Example questions you can ask:")
218
+ print(" • Is heart surgery covered under this policy?")
219
+ print(" • What is the waiting period for pre-existing diseases?")
220
+ print(" • Can I claim for dental treatment?")
221
+ print(" • What is the maximum coverage amount?")
222
+ print(" • Are there any exclusions for chronic diseases?")
223
+ print(" • What documents are required for claim submission?")
224
+ print(" • Is cancer treatment covered?")
225
+ print(" • What is the claim process?")
226
+ print()
227
+ continue
228
+
229
+ # Skip empty queries
230
+ if not user_query:
231
+ print("⚠️ Please enter a question.")
232
+ continue
233
+
234
+ query_count += 1
235
+ print_subsection(f"Processing Query #{query_count}")
236
+ print(f"🤔 Query: {user_query}")
237
+
238
+ # Process the query
239
+ start_time = time.time()
240
+ result = rag_system.process_query(user_query)
241
+ processing_time = time.time() - start_time
242
+
243
+ # Display results
244
+ print(f"⏱️ Processing time: {processing_time:.2f} seconds")
245
+ print(f"📋 Decision: {result.decision.upper()}")
246
+ print(f"🎯 Confidence: {result.confidence_score:.2%}")
247
+
248
+ if result.amount:
249
+ print(f"💰 Amount: ₹{result.amount:,.2f}")
250
+
251
+ print(f"📝 Justification: {result.justification}")
252
+
253
+ if result.relevant_clauses:
254
+ print(f"📄 Relevant Clauses: {', '.join(result.relevant_clauses)}")
255
+
256
+ results.append({
257
+ "query": user_query,
258
+ "result": result,
259
+ "processing_time": processing_time
260
+ })
261
+
262
+ print()
263
+
264
+ # Ask if user wants to continue
265
+ if query_count % 3 == 0: # Ask every 3 queries
266
+ continue_choice = input("❓ Continue asking questions? (y/n): ").strip().lower()
267
+ if continue_choice not in ['y', 'yes', '']:
268
+ print("👋 Thanks for using the RAG system!")
269
+ break
270
+
271
+ except KeyboardInterrupt:
272
+ print("\n👋 Interrupted by user. Goodbye!")
273
+ break
274
+ except Exception as e:
275
+ print(f"❌ Error processing query: {e}")
276
+ print("💡 Try asking a different question or type 'help' for examples.")
277
+ print()
278
+
279
+ return results
280
+
281
+ def demo_audit_trail(rag_system):
282
+ """Demo audit trail functionality"""
283
+ print_section("AUDIT TRAIL DEMO")
284
+
285
+ try:
286
+ # Get audit trail
287
+ audit_log = rag_system.get_audit_trail()
288
+
289
+ print_subsection("Audit Trail Overview")
290
+ print(f"📊 Total queries processed: {len(audit_log)}")
291
+
292
+ if audit_log:
293
+ print("\n📋 Recent audit entries:")
294
+ for i, entry in enumerate(audit_log[-3:], 1): # Show last 3 entries
295
+ print(f" Entry {i}:")
296
+ print(f" Timestamp: {entry.get('timestamp', 'N/A')}")
297
+ print(f" Query: {entry.get('query', 'N/A')}")
298
+ print(f" Relevant chunks: {entry.get('relevant_chunks_count', 0)}")
299
+
300
+ if 'error' in entry:
301
+ print(f" Error: {entry['error']}")
302
+ print()
303
+
304
+ # Save audit trail
305
+ print_subsection("Saving Audit Trail")
306
+ audit_file = "demo_audit_trail.json"
307
+ rag_system.save_audit_trail(audit_file)
308
+ print(f"✅ Audit trail saved to: {audit_file}")
309
+
310
+ # Show audit file size
311
+ if os.path.exists(audit_file):
312
+ file_size = os.path.getsize(audit_file)
313
+ print(f"📁 File size: {file_size:,} bytes")
314
+
315
+ except Exception as e:
316
+ print(f"❌ Error with audit trail: {e}")
317
+
318
+ def demo_system_analysis(rag_system, query_results):
319
+ """Demo system performance and analysis"""
320
+ print_section("SYSTEM ANALYSIS DEMO")
321
+
322
+ if not query_results:
323
+ print("❌ No query results to analyze")
324
+ return
325
+
326
+ print_subsection("Performance Statistics")
327
+
328
+ # Calculate statistics
329
+ total_queries = len(query_results)
330
+ avg_processing_time = sum(r['processing_time'] for r in query_results) / total_queries
331
+ avg_confidence = sum(r['result'].confidence_score for r in query_results) / total_queries
332
+
333
+ decisions = [r['result'].decision for r in query_results]
334
+ decision_counts = {}
335
+ for decision in decisions:
336
+ decision_counts[decision] = decision_counts.get(decision, 0) + 1
337
+
338
+ print(f"📊 Total queries processed: {total_queries}")
339
+ print(f"⏱️ Average processing time: {avg_processing_time:.2f} seconds")
340
+ print(f"🎯 Average confidence score: {avg_confidence:.2%}")
341
+
342
+ print("\n📋 Decision Distribution:")
343
+ for decision, count in decision_counts.items():
344
+ percentage = (count / total_queries) * 100
345
+ print(f" {decision.upper()}: {count} ({percentage:.1f}%)")
346
+
347
+ print_subsection("Query Analysis")
348
+
349
+ # Find best and worst performing queries
350
+ best_query = max(query_results, key=lambda x: x['result'].confidence_score)
351
+ worst_query = min(query_results, key=lambda x: x['result'].confidence_score)
352
+
353
+ print(f"🏆 Best performing query:")
354
+ print(f" Query: {best_query['query']}")
355
+ print(f" Confidence: {best_query['result'].confidence_score:.2%}")
356
+
357
+ print(f"\n⚠️ Worst performing query:")
358
+ print(f" Query: {worst_query['query']}")
359
+ print(f" Confidence: {worst_query['result'].confidence_score:.2%}")
360
+
361
+ def demo_advanced_features():
362
+ """Demo advanced features like hybrid search and contextual compression"""
363
+ print_section("ADVANCED FEATURES DEMO")
364
+
365
+ print_subsection("Vector Database Features")
366
+ print("🔍 Semantic search with contextual compression")
367
+ print("📚 Document chunking with metadata preservation")
368
+ print("🎯 Similarity scoring and ranking")
369
+
370
+ print_subsection("LLM Integration")
371
+ print("🧠 Query parsing with entity extraction")
372
+ print("💭 Reasoning with policy clause mapping")
373
+ print("📋 Structured JSON response generation")
374
+
375
+ print_subsection("Audit and Compliance")
376
+ print("📝 Complete audit trail with timestamps")
377
+ print("🔗 Decision justification with clause references")
378
+ print("💾 Exportable audit logs for compliance")
379
+
380
+ def main():
381
+ """Main demo function"""
382
+ print("🚀 RAG Insurance Policy Analyzer - Interactive GPU Demo")
383
+ print("This demo allows you to upload PDF documents from anywhere on your system")
384
+ print("and ask questions interactively. Powered by GPU-accelerated RAG system")
385
+
386
+ # Step 1: Document Ingestion
387
+ rag_system = demo_document_ingestion()
388
+ if not rag_system:
389
+ print("❌ Demo cannot continue without successful document ingestion")
390
+ return
391
+
392
+ # Step 2: Query Processing
393
+ query_results = demo_query_processing(rag_system)
394
+
395
+ # Step 3: Audit Trail
396
+ demo_audit_trail(rag_system)
397
+
398
+ # Step 4: System Analysis
399
+ demo_system_analysis(rag_system, query_results)
400
+
401
+ # Step 5: Advanced Features
402
+ demo_advanced_features()
403
+
404
+ print_section("INTERACTIVE DEMO COMPLETED")
405
+ print("✅ You've successfully used the interactive RAG system!")
406
+ print("📁 Check the following files for outputs:")
407
+ print(" - demo_audit_trail.json (audit trail)")
408
+ print(" - vector_db/ (vector database)")
409
+ print(" - uploads/ (uploaded documents)")
410
+
411
+ print("\n🎯 Next Steps:")
412
+ print(" 1. Start the web interface: python api_server.py")
413
+ print(" 2. Open http://localhost:8000 in your browser")
414
+ print(" 3. Upload documents and process queries interactively")
415
+ print(" 4. Or run this demo again: python demo.py")
416
+ print(" 5. Try different PDF files from anywhere on your system")
417
+
418
+ if __name__ == "__main__":
419
+ main()
doc2_processed_results.txt ADDED
The diff for this file is too large to render. See raw diff
 
document_processer.py ADDED
@@ -0,0 +1,650 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Advanced Document Processor for Multi-Format Document Ingestion
3
+ Handles PDF, TXT, email, and other document types with OCR and table extraction
4
+ """
5
+
6
+ import os
7
+ import re
8
+ import hashlib
9
+ import logging
10
+ from datetime import datetime
11
+ from typing import List, Dict, Any, Optional, Tuple
12
+ from dataclasses import dataclass
13
+ from pathlib import Path
14
+ import email
15
+ import json
16
+
17
+ # Document processing libraries
18
+ import fitz # PyMuPDF
19
+ try:
20
+ import pytesseract
21
+ from PIL import Image
22
+ import cv2
23
+ import numpy as np
24
+ OCR_AVAILABLE = True
25
+ except ImportError:
26
+ OCR_AVAILABLE = False
27
+ print("⚠️ pytesseract not available. OCR functionality will be disabled.")
28
+ from pdf2image import convert_from_path
29
+ from docx import Document
30
+ from bs4 import BeautifulSoup
31
+ import requests
32
+ import pandas as pd
33
+ # Table extraction libraries
34
+ try:
35
+ import tabula
36
+ import camelot
37
+ TABLE_EXTRACTION_AVAILABLE = True
38
+ except ImportError:
39
+ TABLE_EXTRACTION_AVAILABLE = False
40
+ print("⚠️ Table extraction libraries not available. Table extraction will be disabled.")
41
+
42
+ # LangChain for text splitting
43
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
44
+ from langchain.schema import Document
45
+
46
+ # Configure logging
47
+ logging.basicConfig(level=logging.INFO)
48
+ logger = logging.getLogger(__name__)
49
+
50
+ @dataclass
51
+ class DocumentChunk:
52
+ """Represents a processed document chunk with metadata"""
53
+ chunk_id: str
54
+ content: str
55
+ source_file: str
56
+ file_type: str
57
+ page_number: Optional[int] = None
58
+ chunk_index: Optional[int] = None
59
+ section_type: Optional[str] = None # text, table, header, footer, etc.
60
+ confidence_score: Optional[float] = None
61
+ extracted_entities: Optional[Dict[str, Any]] = None
62
+ table_data: Optional[Dict[str, Any]] = None
63
+ embedding: Optional[List[float]] = None
64
+
65
+ class AdvancedDocumentProcessor:
66
+ """Advanced document processor with multi-format support and table extraction"""
67
+
68
+ def __init__(self, ocr_language='eng', chunk_size=1000, chunk_overlap=200):
69
+ self.ocr_language = ocr_language
70
+ self.chunk_size = chunk_size
71
+ self.chunk_overlap = chunk_overlap
72
+
73
+ # Initialize text splitter
74
+ self.text_splitter = RecursiveCharacterTextSplitter(
75
+ chunk_size=chunk_size,
76
+ chunk_overlap=chunk_overlap,
77
+ separators=["\n\n", "\n", ". ", " ", ""]
78
+ )
79
+
80
+ # Supported file types
81
+ self.supported_extensions = {
82
+ '.pdf': self._process_pdf,
83
+ '.txt': self._process_txt,
84
+ '.docx': self._process_docx,
85
+ '.html': self._process_html,
86
+ '.htm': self._process_html,
87
+ '.eml': self._process_email,
88
+ '.msg': self._process_email,
89
+ '.csv': self._process_csv,
90
+ '.json': self._process_json
91
+ }
92
+
93
+ def process_document(self, file_path: str, use_ocr: bool = False) -> List[DocumentChunk]:
94
+ """Main entry point for document processing"""
95
+ try:
96
+ file_path = Path(file_path)
97
+
98
+ if not file_path.exists():
99
+ raise FileNotFoundError(f"File not found: {file_path}")
100
+
101
+ file_extension = file_path.suffix.lower()
102
+
103
+ if file_extension not in self.supported_extensions:
104
+ raise ValueError(f"Unsupported file type: {file_extension}")
105
+
106
+ logger.info(f"Processing document: {file_path}")
107
+
108
+ # Process based on file type
109
+ processor_func = self.supported_extensions[file_extension]
110
+ raw_content = processor_func(str(file_path), use_ocr)
111
+
112
+ # Extract tables and structured content
113
+ structured_content = self._extract_structured_content(raw_content, file_path)
114
+
115
+ # Chunk the content
116
+ chunks = self._chunk_content(structured_content, str(file_path), file_extension)
117
+
118
+ logger.info(f"Successfully processed {len(chunks)} chunks from {file_path}")
119
+ return chunks
120
+
121
+ except Exception as e:
122
+ logger.error(f"Error processing document {file_path}: {e}")
123
+ logger.error("This might be due to missing dependencies or OCR issues.")
124
+ logger.error("Please check:")
125
+ logger.error("1. File exists and is readable")
126
+ logger.error("2. Tesseract is installed (run: python fix_tesseract.py)")
127
+ logger.error("3. All dependencies are installed (run: python install_nltk_version.py)")
128
+ logger.error("4. Run debug_document_processing.py to diagnose issues")
129
+ import traceback
130
+ logger.error(f"Full error traceback: {traceback.format_exc()}")
131
+ raise
132
+
133
+ def _process_pdf(self, pdf_path: str, use_ocr: bool = False) -> Dict[str, Any]:
134
+ """Process PDF with text extraction, OCR, and table detection"""
135
+ try:
136
+ doc = fitz.open(pdf_path)
137
+ content = {
138
+ 'text': "",
139
+ 'tables': [],
140
+ 'images': [],
141
+ 'metadata': {}
142
+ }
143
+
144
+ for page_num in range(len(doc)):
145
+ page = doc.load_page(page_num)
146
+
147
+ # Extract text normally first
148
+ page_text = page.get_text()
149
+
150
+ # If no text or very little text, use OCR
151
+ if use_ocr or len(page_text.strip()) < 50:
152
+ logger.info(f"Using OCR for page {page_num + 1}")
153
+ ocr_text = self._extract_text_with_ocr(page, page_num)
154
+ if ocr_text.strip(): # Only use OCR text if it's not empty
155
+ page_text = ocr_text
156
+ else:
157
+ logger.warning(f"OCR returned empty text for page {page_num + 1}, using normal extraction")
158
+
159
+ content['text'] += f"\n\n--- Page {page_num + 1} ---\n{page_text}"
160
+
161
+ # Extract tables from this page
162
+ page_tables = self._extract_tables_from_page(page, page_num)
163
+ content['tables'].extend(page_tables)
164
+
165
+ # Extract images (for future OCR if needed)
166
+ page_images = self._extract_images_from_page(page, page_num)
167
+ content['images'].extend(page_images)
168
+
169
+ doc.close()
170
+ return content
171
+
172
+ except Exception as e:
173
+ logger.error(f"Error processing PDF {pdf_path}: {e}")
174
+ raise
175
+
176
+ def _extract_text_with_ocr(self, page, page_num: int) -> str:
177
+ """Extract text using OCR with pytesseract"""
178
+ if not OCR_AVAILABLE:
179
+ logger.error("pytesseract not available. Please install it with: pip install pytesseract")
180
+ return ""
181
+
182
+ try:
183
+ # Get page as image
184
+ pix = page.get_pixmap()
185
+ img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
186
+
187
+ # Simple OCR without complex preprocessing
188
+ text = pytesseract.image_to_string(
189
+ img,
190
+ lang=self.ocr_language,
191
+ config='--psm 6' # Assume uniform block of text
192
+ )
193
+
194
+ return text
195
+
196
+ except Exception as e:
197
+ logger.error(f"OCR error on page {page_num}: {e}")
198
+ logger.error("This might be due to Tesseract not being installed or configured properly.")
199
+ logger.error("Please run: python fix_tesseract.py")
200
+ return ""
201
+
202
+ def _extract_tables_from_page(self, page, page_num: int) -> List[Dict[str, Any]]:
203
+ """Extract tables from PDF page using basic methods"""
204
+ tables = []
205
+
206
+ try:
207
+ if TABLE_EXTRACTION_AVAILABLE:
208
+ # Method 1: Use camelot for table extraction
209
+ try:
210
+ page_tables = camelot.read_pdf(
211
+ page.parent,
212
+ pages=str(page_num + 1),
213
+ flavor='lattice'
214
+ )
215
+
216
+ for table in page_tables:
217
+ if table.df.shape[0] > 1: # Only include tables with data
218
+ tables.append({
219
+ 'page': page_num + 1,
220
+ 'data': table.df.to_dict('records'),
221
+ 'accuracy': table.accuracy,
222
+ 'whitespace': table.whitespace,
223
+ 'method': 'camelot'
224
+ })
225
+ except Exception as e:
226
+ logger.debug(f"Camelot table extraction failed: {e}")
227
+
228
+ # Method 2: Use tabula for simpler tables
229
+ try:
230
+ tabula_tables = tabula.read_pdf(
231
+ page.parent,
232
+ pages=page_num + 1,
233
+ multiple_tables=True
234
+ )
235
+
236
+ for i, table in enumerate(tabula_tables):
237
+ if not table.empty:
238
+ tables.append({
239
+ 'page': page_num + 1,
240
+ 'data': table.to_dict('records'),
241
+ 'method': 'tabula'
242
+ })
243
+ except Exception as e:
244
+ logger.debug(f"Tabula table extraction failed: {e}")
245
+
246
+ # Method 3: Basic table extraction using text patterns (fallback)
247
+ if not tables:
248
+ tables = self._extract_tables_basic(page, page_num)
249
+
250
+ except Exception as e:
251
+ logger.error(f"Table extraction error on page {page_num}: {e}")
252
+
253
+ return tables
254
+
255
+ def _extract_images_from_page(self, page, page_num: int) -> List[Dict[str, Any]]:
256
+ """Extract images from PDF page"""
257
+ images = []
258
+
259
+ try:
260
+ image_list = page.get_images()
261
+
262
+ for img_index, img in enumerate(image_list):
263
+ xref = img[0]
264
+ pix = fitz.Pixmap(page.parent, xref)
265
+
266
+ if pix.n - pix.alpha < 4: # GRAY or RGB
267
+ images.append({
268
+ 'page': page_num + 1,
269
+ 'index': img_index,
270
+ 'width': pix.width,
271
+ 'height': pix.height,
272
+ 'format': pix.colorspace.name
273
+ })
274
+
275
+ pix = None # Free memory
276
+
277
+ except Exception as e:
278
+ logger.error(f"Image extraction error on page {page_num}: {e}")
279
+
280
+ return images
281
+
282
+ def _extract_tables_basic(self, page, page_num: int) -> List[Dict[str, Any]]:
283
+ """Basic table extraction using text patterns when advanced libraries are not available"""
284
+ tables = []
285
+
286
+ try:
287
+ # Get page text
288
+ page_text = page.get_text()
289
+
290
+ # Simple table detection using common patterns
291
+ lines = page_text.split('\n')
292
+ table_lines = []
293
+ in_table = False
294
+
295
+ for line in lines:
296
+ # Check for table-like patterns (multiple columns separated by spaces/tabs)
297
+ if len(line.strip()) > 0 and ('\t' in line or line.count(' ') >= 2):
298
+ if not in_table:
299
+ in_table = True
300
+ table_lines = []
301
+ table_lines.append(line)
302
+ elif in_table:
303
+ # End of table detected
304
+ if table_lines:
305
+ # Convert to table format
306
+ table_data = []
307
+ for table_line in table_lines:
308
+ # Split by multiple spaces or tabs
309
+ columns = [col.strip() for col in table_line.split('\t') if col.strip()]
310
+ if not columns:
311
+ columns = [col.strip() for col in line.split(' ') if col.strip()]
312
+ if columns:
313
+ table_data.append(columns)
314
+
315
+ if table_data and len(table_data) > 1:
316
+ tables.append({
317
+ 'page': page_num + 1,
318
+ 'data': table_data,
319
+ 'method': 'basic_pattern'
320
+ })
321
+
322
+ in_table = False
323
+ table_lines = []
324
+
325
+ # Handle table at end of page
326
+ if in_table and table_lines:
327
+ table_data = []
328
+ for table_line in table_lines:
329
+ columns = [col.strip() for col in table_line.split('\t') if col.strip()]
330
+ if not columns:
331
+ columns = [col.strip() for col in line.split(' ') if col.strip()]
332
+ if columns:
333
+ table_data.append(columns)
334
+
335
+ if table_data and len(table_data) > 1:
336
+ tables.append({
337
+ 'page': page_num + 1,
338
+ 'data': table_data,
339
+ 'method': 'basic_pattern'
340
+ })
341
+
342
+ except Exception as e:
343
+ logger.error(f"Basic table extraction error on page {page_num}: {e}")
344
+
345
+ return tables
346
+
347
+ def _process_txt(self, txt_path: str, use_ocr: bool = False) -> Dict[str, Any]:
348
+ """Process plain text files"""
349
+ try:
350
+ with open(txt_path, 'r', encoding='utf-8') as f:
351
+ text = f.read()
352
+
353
+ return {
354
+ 'text': text,
355
+ 'tables': [],
356
+ 'images': [],
357
+ 'metadata': {}
358
+ }
359
+
360
+ except Exception as e:
361
+ logger.error(f"Error processing TXT {txt_path}: {e}")
362
+ raise
363
+
364
+ def _process_docx(self, docx_path: str, use_ocr: bool = False) -> Dict[str, Any]:
365
+ """Process Word documents"""
366
+ try:
367
+ doc = Document(docx_path)
368
+ text = ""
369
+ tables = []
370
+
371
+ # Extract text from paragraphs - handle different versions of python-docx
372
+ try:
373
+ # Try the standard way first
374
+ for paragraph in doc.paragraphs:
375
+ text += paragraph.text + "\n"
376
+ except AttributeError:
377
+ # Fallback for different versions
378
+ try:
379
+ # Try alternative attribute names
380
+ if hasattr(doc, 'paragraphs'):
381
+ for paragraph in doc.paragraphs:
382
+ text += paragraph.text + "\n"
383
+ elif hasattr(doc, 'content'):
384
+ text = doc.content
385
+ else:
386
+ # Last resort - try to extract text from the document structure
387
+ text = str(doc)
388
+ except Exception as e:
389
+ logger.warning(f"Could not extract text from DOCX paragraphs: {e}")
390
+ text = "DOCX content could not be extracted"
391
+
392
+ # Extract tables from Word document
393
+ try:
394
+ # Check if tables attribute exists
395
+ if hasattr(doc, 'tables'):
396
+ for table in doc.tables:
397
+ table_data = []
398
+ for row in table.rows:
399
+ row_data = [cell.text for cell in row.cells]
400
+ table_data.append(row_data)
401
+
402
+ if table_data:
403
+ tables.append({
404
+ 'data': table_data,
405
+ 'method': 'docx'
406
+ })
407
+ else:
408
+ logger.warning("DOCX document has no 'tables' attribute - tables will not be extracted")
409
+ except Exception as e:
410
+ logger.warning(f"Could not extract tables from DOCX: {e}")
411
+
412
+ return {
413
+ 'text': text,
414
+ 'tables': tables,
415
+ 'images': [],
416
+ 'metadata': {}
417
+ }
418
+
419
+ except Exception as e:
420
+ logger.error(f"Error processing DOCX {docx_path}: {e}")
421
+ raise
422
+
423
+ def _process_html(self, html_path: str, use_ocr: bool = False) -> Dict[str, Any]:
424
+ """Process HTML files"""
425
+ try:
426
+ with open(html_path, 'r', encoding='utf-8') as f:
427
+ html_content = f.read()
428
+
429
+ soup = BeautifulSoup(html_content, 'html.parser')
430
+
431
+ # Extract text
432
+ text = soup.get_text(separator='\n', strip=True)
433
+
434
+ # Extract tables
435
+ tables = []
436
+ for table in soup.find_all('table'):
437
+ table_data = []
438
+ for row in table.find_all('tr'):
439
+ row_data = [cell.get_text(strip=True) for cell in row.find_all(['td', 'th'])]
440
+ if row_data:
441
+ table_data.append(row_data)
442
+
443
+ if table_data:
444
+ tables.append({
445
+ 'data': table_data,
446
+ 'method': 'html'
447
+ })
448
+
449
+ return {
450
+ 'text': text,
451
+ 'tables': tables,
452
+ 'images': [],
453
+ 'metadata': {}
454
+ }
455
+
456
+ except Exception as e:
457
+ logger.error(f"Error processing HTML {html_path}: {e}")
458
+ raise
459
+
460
+ def _process_email(self, email_path: str, use_ocr: bool = False) -> Dict[str, Any]:
461
+ """Process email files (.eml, .msg)"""
462
+ try:
463
+ with open(email_path, 'r', encoding='utf-8') as f:
464
+ email_content = f.read()
465
+
466
+ # Parse email
467
+ msg = email.message_from_string(email_content)
468
+
469
+ # Extract email components
470
+ subject = msg.get('Subject', '')
471
+ sender = msg.get('From', '')
472
+ recipient = msg.get('To', '')
473
+ date = msg.get('Date', '')
474
+
475
+ # Extract body
476
+ body = ""
477
+ if msg.is_multipart():
478
+ for part in msg.walk():
479
+ if part.get_content_type() == "text/plain":
480
+ body = part.get_payload(decode=True).decode()
481
+ break
482
+ else:
483
+ body = msg.get_payload(decode=True).decode()
484
+
485
+ # Combine all text
486
+ text = f"Subject: {subject}\nFrom: {sender}\nTo: {recipient}\nDate: {date}\n\n{body}"
487
+
488
+ return {
489
+ 'text': text,
490
+ 'tables': [],
491
+ 'images': [],
492
+ 'metadata': {
493
+ 'subject': subject,
494
+ 'sender': sender,
495
+ 'recipient': recipient,
496
+ 'date': date
497
+ }
498
+ }
499
+
500
+ except Exception as e:
501
+ logger.error(f"Error processing email {email_path}: {e}")
502
+ raise
503
+
504
+ def _process_csv(self, csv_path: str, use_ocr: bool = False) -> Dict[str, Any]:
505
+ """Process CSV files"""
506
+ try:
507
+ df = pd.read_csv(csv_path)
508
+
509
+ # Convert to text representation
510
+ text = df.to_string(index=False)
511
+
512
+ # Store as table
513
+ tables = [{
514
+ 'data': df.to_dict('records'),
515
+ 'method': 'csv'
516
+ }]
517
+
518
+ return {
519
+ 'text': text,
520
+ 'tables': tables,
521
+ 'images': [],
522
+ 'metadata': {
523
+ 'columns': list(df.columns),
524
+ 'rows': len(df)
525
+ }
526
+ }
527
+
528
+ except Exception as e:
529
+ logger.error(f"Error processing CSV {csv_path}: {e}")
530
+ raise
531
+
532
+ def _process_json(self, json_path: str, use_ocr: bool = False) -> Dict[str, Any]:
533
+ """Process JSON files"""
534
+ try:
535
+ with open(json_path, 'r', encoding='utf-8') as f:
536
+ data = json.load(f)
537
+
538
+ # Convert to text representation
539
+ text = json.dumps(data, indent=2)
540
+
541
+ return {
542
+ 'text': text,
543
+ 'tables': [],
544
+ 'images': [],
545
+ 'metadata': {
546
+ 'type': 'json',
547
+ 'keys': list(data.keys()) if isinstance(data, dict) else []
548
+ }
549
+ }
550
+
551
+ except Exception as e:
552
+ logger.error(f"Error processing JSON {json_path}: {e}")
553
+ raise
554
+
555
+ def _extract_structured_content(self, content: Dict[str, Any], file_path: Path) -> List[Dict[str, Any]]:
556
+ """Extract and structure content from processed document"""
557
+ structured_content = []
558
+
559
+ # Add main text content
560
+ if content['text'].strip():
561
+ structured_content.append({
562
+ 'type': 'text',
563
+ 'content': content['text'],
564
+ 'section_type': 'main_text'
565
+ })
566
+
567
+ # Add tables
568
+ for i, table in enumerate(content['tables']):
569
+ table_text = self._table_to_text(table['data'])
570
+ structured_content.append({
571
+ 'type': 'table',
572
+ 'content': table_text,
573
+ 'section_type': 'table',
574
+ 'table_data': table,
575
+ 'table_index': i
576
+ })
577
+
578
+ # Add metadata as text
579
+ if content.get('metadata'):
580
+ metadata_text = json.dumps(content['metadata'], indent=2)
581
+ structured_content.append({
582
+ 'type': 'text',
583
+ 'content': f"Document Metadata:\n{metadata_text}",
584
+ 'section_type': 'metadata'
585
+ })
586
+
587
+ return structured_content
588
+
589
+ def _table_to_text(self, table_data: List[List[str]]) -> str:
590
+ """Convert table data to readable text"""
591
+ if not table_data:
592
+ return ""
593
+
594
+ text_lines = []
595
+ for row in table_data:
596
+ text_lines.append(" | ".join(str(cell) for cell in row))
597
+
598
+ return "\n".join(text_lines)
599
+
600
+ def _chunk_content(self, structured_content: List[Dict[str, Any]], source_file: str, file_type: str) -> List[DocumentChunk]:
601
+ """Chunk the structured content into DocumentChunk objects"""
602
+ chunks = []
603
+ chunk_index = 0
604
+
605
+ for section in structured_content:
606
+ try:
607
+ # Use LangChain's text splitter for better semantic chunking
608
+ docs = [Document(page_content=section['content'], metadata={"source": source_file})]
609
+ split_docs = self.text_splitter.split_documents(docs)
610
+
611
+ for i, doc in enumerate(split_docs):
612
+ chunk_id = f"chunk_{chunk_index+1}_{hashlib.md5(doc.page_content.encode()).hexdigest()[:8]}"
613
+
614
+ chunk = DocumentChunk(
615
+ chunk_id=chunk_id,
616
+ content=doc.page_content.strip(),
617
+ source_file=source_file,
618
+ file_type=file_type,
619
+ chunk_index=chunk_index,
620
+ section_type=section.get('section_type', 'text'),
621
+ table_data=section.get('table_data'),
622
+ confidence_score=section.get('confidence_score', 1.0)
623
+ )
624
+
625
+ chunks.append(chunk)
626
+ chunk_index += 1
627
+
628
+ except Exception as e:
629
+ logger.error(f"Error chunking section: {e}")
630
+ continue
631
+
632
+ return chunks
633
+
634
+ # Example usage
635
+ if __name__ == "__main__":
636
+ processor = AdvancedDocumentProcessor()
637
+
638
+ # Test with a PDF file
639
+ test_file = "sample.pdf"
640
+ if os.path.exists(test_file):
641
+ chunks = processor.process_document(test_file, use_ocr=False)
642
+ print(f"Processed {len(chunks)} chunks from {test_file}")
643
+
644
+ for i, chunk in enumerate(chunks[:3]):
645
+ print(f"\nChunk {i+1}:")
646
+ print(f"ID: {chunk.chunk_id}")
647
+ print(f"Type: {chunk.section_type}")
648
+ print(f"Content preview: {chunk.content[:100]}...")
649
+ else:
650
+ print(f"Test file {test_file} not found")
keep_alive.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Keep-alive script to prevent server auto-termination
4
+ Pings the Flask server every 2 minutes to keep it alive
5
+ """
6
+
7
+ import requests
8
+ import time
9
+ import logging
10
+ from datetime import datetime
11
+
12
+ # Configure logging
13
+ logging.basicConfig(
14
+ level=logging.INFO,
15
+ format='%(asctime)s - %(levelname)s - %(message)s'
16
+ )
17
+ logger = logging.getLogger(__name__)
18
+
19
+ def ping_server():
20
+ """Ping the Flask server"""
21
+ try:
22
+ response = requests.get("http://127.0.0.1:5000/api/keep-alive", timeout=10)
23
+ if response.status_code == 200:
24
+ logger.info("✅ Server ping successful")
25
+ return True
26
+ else:
27
+ logger.warning(f"⚠️ Server ping failed with status: {response.status_code}")
28
+ return False
29
+ except requests.exceptions.ConnectionError:
30
+ logger.error("❌ Cannot connect to server - is it running?")
31
+ return False
32
+ except Exception as e:
33
+ logger.error(f"❌ Ping failed: {e}")
34
+ return False
35
+
36
+ def main():
37
+ """Main keep-alive loop"""
38
+ logger.info("🚀 Starting keep-alive script")
39
+ logger.info("This script will ping the server every 2 minutes")
40
+ logger.info("Press Ctrl+C to stop")
41
+
42
+ ping_count = 0
43
+ start_time = datetime.now()
44
+
45
+ try:
46
+ while True:
47
+ ping_count += 1
48
+ current_time = datetime.now()
49
+ uptime = current_time - start_time
50
+
51
+ logger.info(f"Ping #{ping_count} at {current_time.strftime('%H:%M:%S')} (Uptime: {uptime})")
52
+
53
+ if ping_server():
54
+ logger.info("Server is alive and responding")
55
+ else:
56
+ logger.warning("Server may be having issues")
57
+
58
+ # Wait 2 minutes before next ping
59
+ logger.info("Sleeping for 2 minutes...")
60
+ time.sleep(120) # 2 minutes
61
+
62
+ except KeyboardInterrupt:
63
+ logger.info("👋 Keep-alive script stopped by user")
64
+ except Exception as e:
65
+ logger.error(f"Keep-alive script crashed: {e}")
66
+
67
+ if __name__ == "__main__":
68
+ main()
llm_reasoning.py ADDED
@@ -0,0 +1,819 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Advanced LLM Reasoning Engine for Query Analysis and Response Generation
3
+ Handles complex reasoning, clause referencing, and structured response generation
4
+ """
5
+
6
+ import os
7
+ import json
8
+ import logging
9
+ import re
10
+ from datetime import datetime
11
+ from typing import List, Dict, Any, Optional, Tuple
12
+ from dataclasses import dataclass
13
+ from pathlib import Path
14
+
15
+ # LLM and AI libraries
16
+ from llama_cpp import Llama
17
+ from transformers import pipeline
18
+ import torch
19
+ import numpy as np
20
+
21
+ # Configure logging
22
+ logging.basicConfig(level=logging.INFO)
23
+ logger = logging.getLogger(__name__)
24
+
25
+ @dataclass
26
+ class ReasoningResult:
27
+ """Represents the result of LLM reasoning"""
28
+ decision: str # approved, denied, pending, unclear
29
+ confidence_score: float
30
+ justification: str
31
+ relevant_clauses: List[str]
32
+ amount: Optional[float] = None
33
+ waiting_period: Optional[str] = None
34
+ conditions: List[str] = None
35
+ exclusions: List[str] = None
36
+ required_documents: List[str] = None
37
+ processing_time: Optional[str] = None
38
+ reasoning_steps: List[str] = None
39
+ source_references: List[Dict[str, Any]] = None
40
+
41
+ @dataclass
42
+ class ClauseReference:
43
+ """Represents a reference to a specific policy clause"""
44
+ clause_id: str
45
+ clause_text: str
46
+ relevance_score: float
47
+ page_number: Optional[int] = None
48
+ section_type: Optional[str] = None
49
+
50
+ class AdvancedLLMReasoning:
51
+ """Advanced LLM reasoning engine with clause referencing and structured analysis"""
52
+
53
+ def __init__(self,
54
+ model_path: str = None,
55
+ use_gpu: bool = True,
56
+ max_tokens: int = 2048):
57
+
58
+ self.model_path = model_path
59
+ self.use_gpu = use_gpu
60
+ self.max_tokens = max_tokens
61
+
62
+ # Initialize LLM
63
+ self._initialize_llm()
64
+
65
+ # Initialize reasoning patterns
66
+ self._initialize_reasoning_patterns()
67
+
68
+ # Initialize clause extraction
69
+ self._initialize_clause_extraction()
70
+
71
+ logger.info("Advanced LLM Reasoning Engine initialized")
72
+
73
+ def _initialize_llm(self):
74
+ """Initialize the LLM model"""
75
+ try:
76
+ # Set default model path if none provided
77
+ if self.model_path is None:
78
+ self.model_path = "./mistral-7b-instruct-v0.1.Q4_K_M.gguf"
79
+
80
+ # Check if model file exists
81
+ if not os.path.exists(self.model_path):
82
+ logger.warning(f"Model file not found: {self.model_path}")
83
+ logger.info("LLM reasoning will use fallback mode without local model")
84
+ self.llm = None
85
+ return
86
+
87
+ # Initialize Llama model with more robust configuration
88
+ try:
89
+ self.llm = Llama(
90
+ model_path=self.model_path,
91
+ n_ctx=4096,
92
+ n_gpu_layers=50 if self.use_gpu else 0,
93
+ verbose=False,
94
+ use_mmap=True,
95
+ use_mlock=False,
96
+ seed=42
97
+ )
98
+ logger.info(f"LLM model loaded successfully with GPU: {self.use_gpu}")
99
+ except Exception as gpu_error:
100
+ logger.warning(f"GPU loading failed: {gpu_error}")
101
+ # Try CPU-only configuration
102
+ try:
103
+ self.llm = Llama(
104
+ model_path=self.model_path,
105
+ n_ctx=2048,
106
+ n_gpu_layers=0, # CPU only
107
+ verbose=False,
108
+ use_mmap=True,
109
+ use_mlock=False,
110
+ seed=42
111
+ )
112
+ logger.info("LLM model loaded successfully with CPU configuration")
113
+ except Exception as cpu_error:
114
+ logger.error(f"CPU loading also failed: {cpu_error}")
115
+ self.llm = None
116
+
117
+ logger.info(f"LLM model loaded: {self.model_path}")
118
+
119
+ except Exception as e:
120
+ logger.error(f"Error initializing LLM: {e}")
121
+ logger.info("LLM reasoning will use fallback mode")
122
+ self.llm = None
123
+
124
+ def _initialize_reasoning_patterns(self):
125
+ """Initialize reasoning patterns and templates"""
126
+ try:
127
+ # Reasoning templates for different query types and domains
128
+ self.reasoning_templates = {
129
+ 'coverage_check': """
130
+ You are an expert document analyst. Analyze the provided document information carefully to answer the user's query.
131
+
132
+ USER QUERY: {query}
133
+
134
+ DOCUMENT SECTIONS:
135
+ {context}
136
+
137
+ INSTRUCTIONS:
138
+ 1. Read and understand the document sections provided
139
+ 2. Look for specific clauses, conditions, limitations, and exclusions
140
+ 3. Pay attention to time periods, limits, and restrictions
141
+ 4. Consider both what is allowed/permitted AND what is explicitly prohibited/excluded
142
+ 5. Base your decision ONLY on the information provided in the document
143
+
144
+ ANALYSIS REQUIREMENTS:
145
+ - Decision: APPROVED, DENIED, CONDITIONAL, or PENDING (be precise based on document text)
146
+ - Confidence Score: 0.0 to 1.0 (higher if document clearly states the answer)
147
+ - Justification: Quote specific document text and explain your reasoning
148
+ - Relevant Clauses: List the exact document sections that support your decision
149
+ - Conditions: Any specific conditions, time limits, or restrictions mentioned
150
+ - Exclusions: What is explicitly excluded or not permitted
151
+ - Required Documents: Documents mentioned as required for this type of request
152
+
153
+ IMPORTANT: If the document explicitly states something is NOT permitted or has limitations, you must reflect that in your decision. Do not assume approval unless the document clearly states it.
154
+
155
+ Respond in valid JSON format.
156
+ """,
157
+
158
+ 'legal_compliance': """
159
+ You are an expert legal compliance analyst. Analyze the provided legal documents to determine compliance status.
160
+
161
+ USER QUERY: {query}
162
+
163
+ LEGAL DOCUMENT SECTIONS:
164
+ {context}
165
+
166
+ INSTRUCTIONS:
167
+ 1. Read and understand the legal document sections provided
168
+ 2. Look for specific regulations, requirements, and compliance criteria
169
+ 3. Pay attention to deadlines, obligations, and legal requirements
170
+ 4. Consider both what is required AND what is explicitly prohibited
171
+ 5. Base your decision ONLY on the information provided in the legal documents
172
+
173
+ ANALYSIS REQUIREMENTS:
174
+ - Decision: COMPLIANT, NON_COMPLIANT, CONDITIONAL, or NEEDS_REVIEW
175
+ - Confidence Score: 0.0 to 1.0 (higher if document clearly states the answer)
176
+ - Justification: Quote specific legal text and explain your reasoning
177
+ - Relevant Regulations: List the exact legal sections that apply
178
+ - Requirements: Any specific legal requirements or obligations mentioned
179
+ - Violations: What would constitute non-compliance
180
+ - Required Actions: Steps needed to achieve or maintain compliance
181
+
182
+ Respond in valid JSON format.
183
+ """,
184
+
185
+ 'hr_policy': """
186
+ You are an expert HR policy analyst. Analyze the provided HR documents to answer employee-related queries.
187
+
188
+ USER QUERY: {query}
189
+
190
+ HR DOCUMENT SECTIONS:
191
+ {context}
192
+
193
+ INSTRUCTIONS:
194
+ 1. Read and understand the HR document sections provided
195
+ 2. Look for specific policies, procedures, and employee rights
196
+ 3. Pay attention to eligibility criteria, time limits, and benefits
197
+ 4. Consider both what is permitted AND what is explicitly prohibited
198
+ 5. Base your decision ONLY on the information provided in the HR documents
199
+
200
+ ANALYSIS REQUIREMENTS:
201
+ - Decision: APPROVED, DENIED, CONDITIONAL, or PENDING_REVIEW
202
+ - Confidence Score: 0.0 to 1.0 (higher if document clearly states the answer)
203
+ - Justification: Quote specific policy text and explain your reasoning
204
+ - Relevant Policies: List the exact policy sections that apply
205
+ - Eligibility: Any specific eligibility criteria or conditions
206
+ - Benefits: What benefits or entitlements are available
207
+ - Required Documentation: Documents needed to support the request
208
+
209
+ Respond in valid JSON format.
210
+ """,
211
+
212
+ 'contract_analysis': """
213
+ You are an expert contract analyst. Analyze the provided contract documents to answer contract-related queries.
214
+
215
+ USER QUERY: {query}
216
+
217
+ CONTRACT DOCUMENT SECTIONS:
218
+ {context}
219
+
220
+ INSTRUCTIONS:
221
+ 1. Read and understand the contract document sections provided
222
+ 2. Look for specific terms, conditions, and contractual obligations
223
+ 3. Pay attention to deadlines, deliverables, and performance requirements
224
+ 4. Consider both what is required AND what is explicitly prohibited
225
+ 5. Base your decision ONLY on the information provided in the contract documents
226
+
227
+ ANALYSIS REQUIREMENTS:
228
+ - Decision: PERMITTED, PROHIBITED, CONDITIONAL, or NEEDS_CLARIFICATION
229
+ - Confidence Score: 0.0 to 1.0 (higher if contract clearly states the answer)
230
+ - Justification: Quote specific contract text and explain your reasoning
231
+ - Relevant Clauses: List the exact contract sections that apply
232
+ - Obligations: Any specific contractual obligations or requirements
233
+ - Restrictions: What is explicitly prohibited or limited
234
+ - Remedies: Available remedies or consequences for non-compliance
235
+
236
+ Respond in valid JSON format.
237
+ """,
238
+
239
+ 'claim_processing': """
240
+ Analyze the claim processing requirements:
241
+
242
+ Query: {query}
243
+
244
+ Policy Information:
245
+ {context}
246
+
247
+ Please provide:
248
+ 1. Decision: APPROVED, DENIED, or PENDING
249
+ 2. Confidence Score: 0.0 to 1.0
250
+ 3. Justification: Detailed explanation
251
+ 4. Required Documents: List of needed documents
252
+ 5. Processing Time: Expected processing duration
253
+ 6. Steps: Claim processing steps
254
+ 7. Relevant Clauses: Policy clauses for claims
255
+
256
+ Respond in JSON format.
257
+ """,
258
+
259
+ 'policy_review': """
260
+ Review the policy terms and conditions:
261
+
262
+ Query: {query}
263
+
264
+ Policy Content:
265
+ {context}
266
+
267
+ Please provide:
268
+ 1. Decision: CLEAR, UNCLEAR, or NEEDS_CLARIFICATION
269
+ 2. Confidence Score: 0.0 to 1.0
270
+ 3. Justification: Detailed explanation
271
+ 4. Relevant Clauses: Specific policy sections
272
+ 5. Key Points: Important policy points
273
+ 6. Recommendations: Suggested actions
274
+
275
+ Respond in JSON format.
276
+ """
277
+ }
278
+
279
+ # Decision mapping for different domains
280
+ self.decision_mapping = {
281
+ # Insurance domain
282
+ 'COVERED': 'approved',
283
+ 'NOT_COVERED': 'denied',
284
+ 'CONDITIONAL': 'pending',
285
+ 'APPROVED': 'approved',
286
+ 'REJECTED': 'denied',
287
+ 'DENIED': 'denied',
288
+ 'PENDING': 'pending',
289
+ 'PENDING_REVIEW': 'pending',
290
+
291
+ # Legal compliance domain
292
+ 'COMPLIANT': 'approved',
293
+ 'NON_COMPLIANT': 'denied',
294
+ 'NEEDS_REVIEW': 'pending',
295
+
296
+ # Contract domain
297
+ 'PERMITTED': 'approved',
298
+ 'PROHIBITED': 'denied',
299
+ 'NEEDS_CLARIFICATION': 'pending',
300
+
301
+ # Policy review
302
+ 'CLEAR': 'approved',
303
+ 'UNCLEAR': 'pending'
304
+ }
305
+
306
+ logger.info("Reasoning patterns initialized")
307
+
308
+ except Exception as e:
309
+ logger.error(f"Error initializing reasoning patterns: {e}")
310
+
311
+ def _initialize_clause_extraction(self):
312
+ """Initialize clause extraction patterns"""
313
+ try:
314
+ # Patterns for extracting policy clauses
315
+ self.clause_patterns = {
316
+ 'coverage_clause': [
317
+ r'coverage.*?shall.*?include',
318
+ r'covered.*?expenses.*?include',
319
+ r'benefits.*?shall.*?cover',
320
+ r'policy.*?covers.*?following'
321
+ ],
322
+ 'exclusion_clause': [
323
+ r'exclusions.*?include',
324
+ r'not.*?covered.*?following',
325
+ r'excluded.*?from.*?coverage',
326
+ r'coverage.*?does.*?not.*?include'
327
+ ],
328
+ 'condition_clause': [
329
+ r'conditions.*?precedent',
330
+ r'requirements.*?for.*?coverage',
331
+ r'must.*?meet.*?following',
332
+ r'coverage.*?subject.*?to'
333
+ ],
334
+ 'amount_clause': [
335
+ r'maximum.*?benefit.*?\$[\d,]+',
336
+ r'coverage.*?limit.*?\$[\d,]+',
337
+ r'benefit.*?amount.*?\$[\d,]+',
338
+ r'up.*?to.*?\$[\d,]+'
339
+ ],
340
+ 'waiting_period': [
341
+ r'waiting.*?period.*?\d+.*?(days?|weeks?|months?)',
342
+ r'coverage.*?begins.*?after.*?\d+',
343
+ r'benefits.*?available.*?after.*?\d+'
344
+ ]
345
+ }
346
+
347
+ logger.info("Clause extraction patterns initialized")
348
+
349
+ except Exception as e:
350
+ logger.error(f"Error initializing clause extraction: {e}")
351
+
352
+ def analyze_query(self,
353
+ query: str,
354
+ context: List[Dict[str, Any]],
355
+ query_type: str = 'coverage_check') -> ReasoningResult:
356
+ """Main method to analyze a query using LLM reasoning with detailed analysis"""
357
+ try:
358
+ # Prepare context from relevant chunks
359
+ formatted_context = self._format_context_for_llm(context)
360
+
361
+ # Create comprehensive reasoning prompt
362
+ prompt = f"""
363
+ You are an expert insurance policy analyzer. Analyze the following query against the provided policy documents.
364
+
365
+ User Query: {query}
366
+
367
+ Relevant Policy Clauses:
368
+ {formatted_context}
369
+
370
+ Please provide a structured analysis in the following JSON format:
371
+ {{
372
+ "decision": "approved/rejected/conditional",
373
+ "amount": <amount if applicable, null otherwise>,
374
+ "justification": "<detailed explanation with specific clause references>",
375
+ "relevant_clauses": ["<list of clause IDs that support the decision>"],
376
+ "confidence_score": <0.0 to 1.0>,
377
+ "conditions": ["<any conditions that must be met>"],
378
+ "exclusions": ["<what is explicitly excluded>"],
379
+ "waiting_period": "<waiting period if applicable>",
380
+ "required_documents": ["<documents needed for claim>"],
381
+ "reasoning_steps": ["<step-by-step reasoning process>"]
382
+ }}
383
+
384
+ Base your decision on:
385
+ 1. Policy coverage and exclusions
386
+ 2. Eligibility criteria
387
+ 3. Waiting periods
388
+ 4. Pre-existing conditions
389
+ 5. Specific terms and conditions
390
+ 6. Time limitations and restrictions
391
+ 7. Required documentation
392
+
393
+ IMPORTANT:
394
+ - Quote specific policy text in your justification
395
+ - Reference exact clause IDs from the provided context
396
+ - If the policy explicitly states limitations, reflect them accurately
397
+ - Provide detailed reasoning, not just yes/no answers
398
+ - Consider both what is covered AND what is excluded
399
+ - Look for specific amounts, time periods, and conditions
400
+
401
+ JSON Response:
402
+ """
403
+
404
+ # Generate response using LLM
405
+ response = self._generate_llm_response(prompt)
406
+
407
+ # Parse the response
408
+ parsed_result = self._parse_llm_response(response, query_type)
409
+
410
+ # Extract clause references
411
+ clause_references = self._extract_clause_references(context, parsed_result)
412
+
413
+ # Build final result with comprehensive analysis
414
+ result = ReasoningResult(
415
+ decision=parsed_result.get('decision', 'pending'),
416
+ confidence_score=parsed_result.get('confidence_score', 0.5),
417
+ justification=parsed_result.get('justification', 'Unable to determine'),
418
+ relevant_clauses=parsed_result.get('relevant_clauses', []),
419
+ amount=parsed_result.get('amount'),
420
+ waiting_period=parsed_result.get('waiting_period'),
421
+ conditions=parsed_result.get('conditions', []),
422
+ exclusions=parsed_result.get('exclusions', []),
423
+ required_documents=parsed_result.get('required_documents', []),
424
+ processing_time=parsed_result.get('processing_time'),
425
+ reasoning_steps=parsed_result.get('reasoning_steps', []),
426
+ source_references=clause_references
427
+ )
428
+
429
+ logger.info(f"Query analysis completed: {result.decision} ({result.confidence_score:.2f})")
430
+ return result
431
+
432
+ except Exception as e:
433
+ logger.error(f"Error analyzing query: {e}")
434
+ return self._create_fallback_result(query)
435
+
436
+ def _format_context_for_llm(self, context: List[Dict[str, Any]]) -> str:
437
+ """Format context for LLM consumption"""
438
+ try:
439
+ formatted_parts = []
440
+
441
+ for i, item in enumerate(context, 1):
442
+ content = item.get('content', '')
443
+ source = item.get('source_file', 'Unknown')
444
+ similarity = item.get('similarity_score', 0.0)
445
+
446
+ formatted_parts.append(f"Section {i} (Source: {source}, Relevance: {similarity:.2f}):\n{content}\n")
447
+
448
+ return "\n".join(formatted_parts)
449
+
450
+ except Exception as e:
451
+ logger.error(f"Error formatting context: {e}")
452
+ return str(context)
453
+
454
+ def _generate_llm_response(self, prompt: str) -> str:
455
+ """Generate response using the LLM"""
456
+ try:
457
+ # Check if LLM is available
458
+ if self.llm is None:
459
+ logger.warning("LLM not available, using fallback response")
460
+ return self._generate_fallback_response(prompt)
461
+
462
+ # Create system prompt
463
+ system_prompt = """You are an expert insurance policy analyzer. Your job is to:
464
+ 1. Carefully read and understand the policy document sections provided
465
+ 2. Answer the user's query based ONLY on the information in the policy document
466
+ 3. Look for specific clauses, conditions, limitations, and exclusions
467
+ 4. Pay attention to time periods, coverage limits, and restrictions
468
+ 5. If the policy explicitly states something is NOT covered, you must say it's REJECTED
469
+ 6. If there are specific conditions or limitations, you must mention them
470
+ 7. Always respond in valid JSON format with accurate information
471
+ 8. Quote specific policy text in your justification
472
+ 9. Reference exact clause IDs from the provided context
473
+ 10. Provide detailed reasoning, not just yes/no answers
474
+ 11. Consider both what is covered AND what is excluded
475
+ 12. Look for specific amounts, time periods, and conditions
476
+ 13. Do not make assumptions - base your decision only on what the policy document states"""
477
+
478
+ # Generate response
479
+ response = self.llm.create_completion(
480
+ prompt=f"{system_prompt}\n\n{prompt}",
481
+ max_tokens=self.max_tokens,
482
+ temperature=0.1,
483
+ stop=["```", "Human:", "Assistant:"]
484
+ )
485
+
486
+ return response['choices'][0]['text'].strip()
487
+
488
+ except Exception as e:
489
+ logger.error(f"Error generating LLM response: {e}")
490
+ return self._generate_fallback_response(prompt)
491
+
492
+ def _parse_llm_response(self, response: str, query_type: str) -> Dict[str, Any]:
493
+ """Parse the LLM response into structured data"""
494
+ try:
495
+ # Try to extract JSON from response
496
+ json_match = re.search(r'\{.*\}', response, re.DOTALL)
497
+ if json_match:
498
+ json_str = json_match.group(0)
499
+ parsed = json.loads(json_str)
500
+ else:
501
+ # Fallback parsing
502
+ parsed = self._fallback_parse_response(response)
503
+
504
+ # Map decision to standard format
505
+ if 'decision' in parsed:
506
+ parsed['decision'] = self.decision_mapping.get(
507
+ parsed['decision'].upper(), 'pending'
508
+ )
509
+
510
+ # Ensure confidence score is float
511
+ if 'confidence_score' in parsed:
512
+ try:
513
+ parsed['confidence_score'] = float(parsed['confidence_score'])
514
+ except:
515
+ parsed['confidence_score'] = 0.5
516
+
517
+ return parsed
518
+
519
+ except Exception as e:
520
+ logger.error(f"Error parsing LLM response: {e}")
521
+ return {
522
+ 'decision': 'pending',
523
+ 'confidence_score': 0.5,
524
+ 'justification': 'Unable to parse response',
525
+ 'relevant_clauses': []
526
+ }
527
+
528
+ def _fallback_parse_response(self, response: str) -> Dict[str, Any]:
529
+ """Fallback parsing when JSON extraction fails"""
530
+ try:
531
+ result = {
532
+ 'decision': 'pending',
533
+ 'confidence_score': 0.5,
534
+ 'justification': response[:500],
535
+ 'relevant_clauses': []
536
+ }
537
+
538
+ # Try to extract decision
539
+ if 'covered' in response.lower():
540
+ result['decision'] = 'approved'
541
+ elif 'not covered' in response.lower() or 'excluded' in response.lower():
542
+ result['decision'] = 'denied'
543
+
544
+ # Try to extract confidence
545
+ confidence_match = re.search(r'confidence.*?(\d+\.?\d*)', response, re.IGNORECASE)
546
+ if confidence_match:
547
+ try:
548
+ result['confidence_score'] = float(confidence_match.group(1))
549
+ except:
550
+ pass
551
+
552
+ return result
553
+
554
+ except Exception as e:
555
+ logger.error(f"Error in fallback parsing: {e}")
556
+ return {
557
+ 'decision': 'pending',
558
+ 'confidence_score': 0.5,
559
+ 'justification': 'Analysis failed',
560
+ 'relevant_clauses': []
561
+ }
562
+
563
+ def _extract_clause_references(self,
564
+ context: List[Dict[str, Any]],
565
+ parsed_result: Dict[str, Any]) -> List[Dict[str, Any]]:
566
+ """Extract specific clause references from context"""
567
+ try:
568
+ references = []
569
+
570
+ for item in context:
571
+ content = item.get('content', '')
572
+ source = item.get('source_file', 'Unknown')
573
+
574
+ # Extract clauses using patterns
575
+ for clause_type, patterns in self.clause_patterns.items():
576
+ for pattern in patterns:
577
+ matches = re.findall(pattern, content, re.IGNORECASE)
578
+ for match in matches:
579
+ references.append({
580
+ 'clause_type': clause_type,
581
+ 'clause_text': match,
582
+ 'source_file': source,
583
+ 'relevance_score': item.get('similarity_score', 0.0)
584
+ })
585
+
586
+ return references
587
+
588
+ except Exception as e:
589
+ logger.error(f"Error extracting clause references: {e}")
590
+ return []
591
+
592
+ def _generate_fallback_response(self, prompt: str) -> str:
593
+ """Generate a fallback response when LLM is not available"""
594
+ try:
595
+ # Extract query and context from prompt
596
+ query_match = re.search(r'USER QUERY:\s*(.+?)(?=\n\n|$)', prompt, re.DOTALL | re.IGNORECASE)
597
+ context_match = re.search(r'POLICY DOCUMENT SECTIONS:\s*(.+?)(?=\n\n|$)', prompt, re.DOTALL | re.IGNORECASE)
598
+
599
+ query = query_match.group(1).strip() if query_match else "Unknown query"
600
+ context = context_match.group(1).strip() if context_match else "No policy context provided"
601
+
602
+ # Analyze the context for key information
603
+ context_lower = context.lower()
604
+ query_lower = query.lower()
605
+
606
+ # Look for specific policy terms and conditions
607
+ decision = "PENDING"
608
+ confidence = 0.5
609
+ justification = "Unable to determine coverage without proper policy analysis"
610
+ relevant_clauses = []
611
+ conditions = []
612
+ exclusions = []
613
+
614
+ # Check for coverage limitations and restrictions
615
+ if any(term in context_lower for term in ['until first discharge', 'discharge from hospital', 'hospitalization period', 'limited to']):
616
+ if any(term in query_lower for term in ['after discharge', 'post discharge', 'discharge', 'beyond']):
617
+ decision = "REJECTED"
618
+ confidence = 0.9
619
+ justification = "Policy explicitly states coverage is limited to hospitalization period until first discharge. Post-discharge care is not covered under this policy."
620
+ relevant_clauses = ["newborn coverage", "discharge limitation", "hospitalization period"]
621
+ conditions = ["Coverage only during hospitalization", "Until first discharge"]
622
+ exclusions = ["Post-discharge care", "Outpatient newborn care"]
623
+ waiting_period = "Until first discharge"
624
+ required_documents = ["Hospital discharge summary", "Birth certificate"]
625
+
626
+ # Check for waiting periods
627
+ elif any(term in context_lower for term in ['waiting period', 'waiting periods', 'time requirement']):
628
+ decision = "CONDITIONAL"
629
+ confidence = 0.7
630
+ justification = "Coverage subject to waiting period requirements as specified in the policy"
631
+ relevant_clauses = ["waiting periods", "time requirements"]
632
+ conditions = ["Waiting period must be satisfied"]
633
+ waiting_period = "As specified in policy"
634
+
635
+ # Check for exclusions
636
+ elif any(term in context_lower for term in ['not covered', 'excluded', 'exclusions', 'prohibited', 'not permitted']):
637
+ decision = "REJECTED"
638
+ confidence = 0.8
639
+ justification = "Policy explicitly excludes or prohibits this type of coverage"
640
+ relevant_clauses = ["exclusions", "prohibitions"]
641
+ exclusions = ["Excluded per policy terms"]
642
+
643
+ # Check for covered items
644
+ elif any(term in context_lower for term in ['covered', 'coverage', 'benefits', 'permitted', 'allowed']):
645
+ decision = "APPROVED"
646
+ confidence = 0.7
647
+ justification = "Policy indicates this type of coverage is permitted"
648
+ relevant_clauses = ["coverage", "benefits"]
649
+ conditions = ["Subject to policy terms"]
650
+
651
+ # Default case - analyze based on context content
652
+ else:
653
+ # Look for positive indicators in context
654
+ if any(term in context_lower for term in ['covered', 'coverage', 'benefits', 'permitted']):
655
+ decision = "APPROVED"
656
+ confidence = 0.6
657
+ justification = "Policy appears to provide coverage for this type of request"
658
+ relevant_clauses = ["general coverage"]
659
+ conditions = ["Subject to policy terms"]
660
+ elif any(term in context_lower for term in ['excluded', 'not covered', 'prohibited']):
661
+ decision = "REJECTED"
662
+ confidence = 0.6
663
+ justification = "Policy appears to exclude this type of coverage"
664
+ relevant_clauses = ["exclusions"]
665
+ exclusions = ["Excluded per policy terms"]
666
+ else:
667
+ # Try to extract any relevant information from the context
668
+ if len(context) > 0:
669
+ decision = "CONDITIONAL"
670
+ confidence = 0.5
671
+ justification = f"Based on the available policy information, this request requires further review. Found {len(context)} relevant policy sections."
672
+ relevant_clauses = ["policy sections found"]
673
+ conditions = ["Policy review required", "Additional documentation may be needed"]
674
+ else:
675
+ decision = "PENDING"
676
+ confidence = 0.3
677
+ justification = "No relevant policy information found. Please ensure the policy document has been properly uploaded and processed."
678
+ relevant_clauses = ["no policy data"]
679
+ conditions = ["Policy document upload required"]
680
+
681
+ # Check for specific amounts in context
682
+ amount_match = re.search(r'(\d+(?:,\d+)*(?:\.\d+)?)\s*(?:rs?|rupees?|inr|\$)', context_lower)
683
+ if amount_match:
684
+ amount = float(amount_match.group(1).replace(',', ''))
685
+ else:
686
+ amount = None
687
+
688
+ return json.dumps({
689
+ "decision": decision,
690
+ "confidence_score": confidence,
691
+ "justification": justification,
692
+ "relevant_clauses": relevant_clauses,
693
+ "amount": amount,
694
+ "waiting_period": waiting_period,
695
+ "conditions": conditions,
696
+ "exclusions": exclusions,
697
+ "required_documents": ["Policy document", "Claim form"]
698
+ })
699
+
700
+ except Exception as e:
701
+ logger.error(f"Error generating fallback response: {e}")
702
+ return json.dumps({
703
+ "decision": "PENDING",
704
+ "confidence_score": 0.5,
705
+ "justification": "Unable to analyze policy information",
706
+ "relevant_clauses": [],
707
+ "conditions": [],
708
+ "exclusions": []
709
+ })
710
+
711
+ def _create_fallback_result(self, query: str) -> ReasoningResult:
712
+ """Create a fallback result when analysis fails"""
713
+ return ReasoningResult(
714
+ decision='pending',
715
+ confidence_score=0.0,
716
+ justification='Unable to analyze query due to technical issues',
717
+ relevant_clauses=[],
718
+ reasoning_steps=['Analysis failed'],
719
+ source_references=[]
720
+ )
721
+
722
+ def explain_decision(self, result: ReasoningResult) -> str:
723
+ """Generate a human-readable explanation of the decision"""
724
+ try:
725
+ explanation_parts = []
726
+
727
+ # Main decision
728
+ explanation_parts.append(f"Decision: {result.decision.upper()}")
729
+ explanation_parts.append(f"Confidence: {result.confidence_score:.1%}")
730
+
731
+ # Justification
732
+ if result.justification:
733
+ explanation_parts.append(f"\nJustification:\n{result.justification}")
734
+
735
+ # Relevant clauses
736
+ if result.relevant_clauses:
737
+ explanation_parts.append(f"\nRelevant Policy Clauses:")
738
+ for clause in result.relevant_clauses:
739
+ explanation_parts.append(f"- {clause}")
740
+
741
+ # Amount information
742
+ if result.amount:
743
+ explanation_parts.append(f"\nCoverage Amount: ${result.amount:,.2f}")
744
+
745
+ # Waiting period
746
+ if result.waiting_period:
747
+ explanation_parts.append(f"\nWaiting Period: {result.waiting_period}")
748
+
749
+ # Conditions
750
+ if result.conditions:
751
+ explanation_parts.append(f"\nConditions:")
752
+ for condition in result.conditions:
753
+ explanation_parts.append(f"- {condition}")
754
+
755
+ # Exclusions
756
+ if result.exclusions:
757
+ explanation_parts.append(f"\nExclusions:")
758
+ for exclusion in result.exclusions:
759
+ explanation_parts.append(f"- {exclusion}")
760
+
761
+ # Required documents
762
+ if result.required_documents:
763
+ explanation_parts.append(f"\nRequired Documents:")
764
+ for doc in result.required_documents:
765
+ explanation_parts.append(f"- {doc}")
766
+
767
+ return "\n".join(explanation_parts)
768
+
769
+ except Exception as e:
770
+ logger.error(f"Error explaining decision: {e}")
771
+ return f"Decision: {result.decision.upper()}\nConfidence: {result.confidence_score:.1%}\nJustification: {result.justification}"
772
+
773
+ def validate_decision(self, result: ReasoningResult) -> bool:
774
+ """Validate the reasoning result for consistency"""
775
+ try:
776
+ # Check if confidence score is valid
777
+ if not (0.0 <= result.confidence_score <= 1.0):
778
+ return False
779
+
780
+ # Check if decision is valid
781
+ valid_decisions = ['approved', 'denied', 'pending']
782
+ if result.decision not in valid_decisions:
783
+ return False
784
+
785
+ # Check if justification is provided
786
+ if not result.justification or len(result.justification.strip()) < 10:
787
+ return False
788
+
789
+ return True
790
+
791
+ except Exception as e:
792
+ logger.error(f"Error validating decision: {e}")
793
+ return False
794
+
795
+ # Example usage
796
+ if __name__ == "__main__":
797
+ # Initialize reasoning engine
798
+ reasoning_engine = AdvancedLLMReasoning()
799
+
800
+ # Test query
801
+ test_query = "Is heart surgery covered under this policy?"
802
+ test_context = [
803
+ {
804
+ 'content': 'This policy covers medical procedures including heart surgery up to $50,000.',
805
+ 'source_file': 'policy.pdf',
806
+ 'similarity_score': 0.9
807
+ }
808
+ ]
809
+
810
+ # Analyze query
811
+ result = reasoning_engine.analyze_query(test_query, test_context, 'coverage_check')
812
+
813
+ print(f"Decision: {result.decision}")
814
+ print(f"Confidence: {result.confidence_score:.2f}")
815
+ print(f"Justification: {result.justification}")
816
+
817
+ # Explain decision
818
+ explanation = reasoning_engine.explain_decision(result)
819
+ print(f"\nExplanation:\n{explanation}")
main.py ADDED
@@ -0,0 +1,782 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Main Entry Point for Advanced RAG System
3
+ Provides user-friendly interface for document upload and query processing
4
+ """
5
+
6
+ import os
7
+ import sys
8
+ import json
9
+ import time
10
+ import argparse
11
+ from pathlib import Path
12
+ from typing import List, Dict, Any, Optional
13
+
14
+ # Import our RAG system
15
+ from rag_system import AdvancedRAGSystem, QueryResult
16
+
17
+ # Import document processor for direct access
18
+ from document_processer import AdvancedDocumentProcessor
19
+
20
+ # Import pytesseract for direct OCR
21
+ try:
22
+ import pytesseract
23
+ from PIL import Image
24
+ import cv2
25
+ import numpy as np
26
+ OCR_AVAILABLE = True
27
+ except ImportError:
28
+ OCR_AVAILABLE = False
29
+
30
+ def print_banner():
31
+ """Print system banner"""
32
+ print("=" * 80)
33
+ print("🚀 ADVANCED RAG SYSTEM - INSURANCE POLICY ANALYZER")
34
+ print("=" * 80)
35
+ print("📋 Features:")
36
+ print(" • Multi-format document processing (PDF, TXT, Email, etc.)")
37
+ print(" • Advanced OCR for scanned documents")
38
+ print(" • Natural language query processing")
39
+ print(" • Clause referencing and explanation")
40
+ print(" • Comprehensive audit trail")
41
+ print(" • GPU-accelerated processing")
42
+ print("=" * 80)
43
+
44
+ def print_menu():
45
+ """Print main menu options"""
46
+ print("\n📋 MAIN MENU:")
47
+ print("1. 📄 Upload Document")
48
+ print("2. 🤔 Process Query")
49
+ print("3. 📊 System Statistics")
50
+ print("4. 🔍 View Audit Trail")
51
+ print("5. 🧪 System Validation")
52
+ print("6. 💾 Export System Data")
53
+ print("7. 🗑️ Clear System")
54
+ print("8. 🧪 Test OCR")
55
+ print("9. ❓ Help")
56
+ print("0. 🚪 Exit")
57
+ print("-" * 40)
58
+
59
+ def get_user_choice() -> str:
60
+ """Get user choice from menu"""
61
+ try:
62
+ choice = input("\n🎯 Enter your choice (1-9): ").strip()
63
+ return choice
64
+ except KeyboardInterrupt:
65
+ print("\n👋 Goodbye!")
66
+ sys.exit(0)
67
+
68
+ def simple_ocr_pdf(file_path: str) -> str:
69
+ """Simple OCR function using pytesseract directly"""
70
+ if not OCR_AVAILABLE:
71
+ print("❌ pytesseract not available. Please install it with: pip install pytesseract")
72
+ return ""
73
+
74
+ try:
75
+ import fitz # PyMuPDF
76
+
77
+ # Open PDF
78
+ doc = fitz.open(file_path)
79
+ all_text = ""
80
+
81
+ for page_num in range(len(doc)):
82
+ page = doc.load_page(page_num)
83
+
84
+ # Get page as image
85
+ pix = page.get_pixmap()
86
+ img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
87
+
88
+ # Extract text using OCR
89
+ text = pytesseract.image_to_string(img, lang='eng', config='--psm 6')
90
+ all_text += f"\n\n--- Page {page_num + 1} ---\n{text}"
91
+
92
+ doc.close()
93
+ return all_text
94
+
95
+ except Exception as e:
96
+ print(f"❌ OCR error: {e}")
97
+ return ""
98
+
99
+ def upload_document(rag_system: AdvancedRAGSystem):
100
+ """Handle document upload"""
101
+ print("\n📄 DOCUMENT UPLOAD")
102
+ print("-" * 40)
103
+
104
+ try:
105
+ # Get file path
106
+ file_path = input("📁 Enter file path (or 'browse' for file dialog): ").strip()
107
+
108
+ if file_path.lower() == 'browse':
109
+ try:
110
+ import tkinter as tk
111
+ from tkinter import filedialog
112
+
113
+ root = tk.Tk()
114
+ root.withdraw()
115
+
116
+ file_path = filedialog.askopenfilename(
117
+ title="Select Document",
118
+ filetypes=[
119
+ ("All supported", "*.pdf;*.txt;*.docx;*.html;*.eml;*.csv;*.json"),
120
+ ("PDF files", "*.pdf"),
121
+ ("Text files", "*.txt"),
122
+ ("Word documents", "*.docx"),
123
+ ("HTML files", "*.html"),
124
+ ("Email files", "*.eml"),
125
+ ("CSV files", "*.csv"),
126
+ ("JSON files", "*.json"),
127
+ ("All files", "*.*")
128
+ ]
129
+ )
130
+
131
+ root.destroy()
132
+
133
+ if not file_path:
134
+ print("❌ No file selected.")
135
+ return
136
+
137
+ except ImportError:
138
+ print("❌ File browser not available. Please enter the file path manually.")
139
+ return
140
+ except Exception as e:
141
+ print(f"❌ Error opening file browser: {e}")
142
+ return
143
+
144
+ # Check if file exists
145
+ if not os.path.exists(file_path):
146
+ print(f"❌ File not found: {file_path}")
147
+ return
148
+
149
+ # Check file type
150
+ supported_extensions = {'.pdf', '.txt', '.docx', '.html', '.htm', '.eml', '.msg', '.csv', '.json'}
151
+ file_extension = Path(file_path).suffix.lower()
152
+
153
+ if file_extension not in supported_extensions:
154
+ print(f"❌ Unsupported file type: {file_extension}")
155
+ print(f"💡 Supported types: {', '.join(supported_extensions)}")
156
+ return
157
+
158
+ # Ask about OCR for PDF files
159
+ use_ocr = False
160
+ if file_extension == '.pdf':
161
+ if OCR_AVAILABLE:
162
+ ocr_choice = input("🔍 Use OCR for scanned documents? (y/n, default: n): ").strip().lower()
163
+ use_ocr = ocr_choice in ['y', 'yes']
164
+ else:
165
+ print("⚠️ OCR not available. Processing without OCR.")
166
+
167
+ print(f"\n⏳ Processing document: {os.path.basename(file_path)}")
168
+ print("💡 This may take a few moments...")
169
+
170
+ # Process document
171
+ start_time = time.time()
172
+ try:
173
+ chunks = rag_system.ingest_document(file_path, use_ocr=use_ocr)
174
+ processing_time = time.time() - start_time
175
+ except Exception as e:
176
+ print(f"❌ Document ingestion failed: {e}")
177
+ print("💡 Trying alternative processing method...")
178
+
179
+ # Fallback: Use document processor directly
180
+ try:
181
+ doc_processor = AdvancedDocumentProcessor()
182
+ chunks = doc_processor.process_document(file_path, use_ocr=use_ocr)
183
+
184
+ # Add chunks to vector database manually
185
+ if chunks:
186
+ success = rag_system.vector_database.add_documents(chunks)
187
+ if success:
188
+ print("✅ Document processed using fallback method")
189
+ processing_time = time.time() - start_time
190
+ else:
191
+ print("❌ Failed to add documents to vector database")
192
+ chunks = []
193
+ else:
194
+ print("❌ No chunks generated from document")
195
+ chunks = []
196
+ processing_time = time.time() - start_time
197
+ except Exception as fallback_error:
198
+ print(f"❌ Fallback processing also failed: {fallback_error}")
199
+ chunks = []
200
+ processing_time = time.time() - start_time
201
+
202
+ if chunks:
203
+ print(f"✅ Successfully processed {len(chunks)} chunks in {processing_time:.2f} seconds")
204
+ print(f"📊 Document chunks created:")
205
+
206
+ for i, chunk in enumerate(chunks[:5]): # Show first 5 chunks
207
+ print(f" Chunk {i+1}: {chunk.chunk_id}")
208
+ print(f" Type: {chunk.section_type}")
209
+ print(f" Content preview: {chunk.content[:100]}...")
210
+ print()
211
+
212
+ if len(chunks) > 5:
213
+ print(f" ... and {len(chunks) - 5} more chunks")
214
+ else:
215
+ print("❌ Failed to process document")
216
+ if file_extension == '.pdf' and not use_ocr:
217
+ print("💡 Try using OCR for scanned PDFs: Select 'y' when asked about OCR")
218
+
219
+ except KeyboardInterrupt:
220
+ print("\n👋 Upload cancelled.")
221
+ except Exception as e:
222
+ print(f"❌ Error uploading document: {e}")
223
+
224
+ def process_query(rag_system: AdvancedRAGSystem):
225
+ """Interactive query processing with continuous questioning"""
226
+ print("\n🤔 INTERACTIVE QUERY PROCESSING")
227
+ print("-" * 40)
228
+
229
+ print("💡 Enter your questions about the uploaded documents.")
230
+ print("💡 Type 'quit' or 'exit' to stop asking questions.")
231
+ print("💡 Type 'help' for example questions.")
232
+ print()
233
+
234
+ results = []
235
+ query_count = 0
236
+
237
+ while True:
238
+ try:
239
+ # Get user input
240
+ user_query = input("🤔 Enter your question: ").strip()
241
+
242
+ # Check for exit commands
243
+ if user_query.lower() in ['quit', 'exit', 'q']:
244
+ print("👋 Goodbye!")
245
+ break
246
+
247
+ # Check for help command
248
+ if user_query.lower() == 'help':
249
+ print("\n📋 Example questions you can ask:")
250
+ print(" • Is heart surgery covered under this policy?")
251
+ print(" • What is the waiting period for pre-existing diseases?")
252
+ print(" • Can I claim for dental treatment?")
253
+ print(" • What is the maximum coverage amount?")
254
+ print(" • Are there any exclusions for chronic diseases?")
255
+ print(" • What documents are required for claim submission?")
256
+ print(" • Is cancer treatment covered?")
257
+ print(" • What is the claim process?")
258
+ print(" • Does the policy cover newborn care after hospital discharge?")
259
+ print()
260
+ continue
261
+
262
+ # Skip empty queries
263
+ if not user_query:
264
+ print("⚠️ Please enter a question.")
265
+ continue
266
+
267
+ query_count += 1
268
+ print(f"\n🔍 Processing Query #{query_count}")
269
+ print(f"🤔 Query: {user_query}")
270
+
271
+ # Process the query
272
+ start_time = time.time()
273
+ try:
274
+ result = rag_system.process_query(user_query)
275
+ processing_time = time.time() - start_time
276
+ except Exception as e:
277
+ print(f"❌ Query processing failed: {e}")
278
+ print("💡 Creating fallback result...")
279
+
280
+ # Create fallback result
281
+ from query_parser import AdvancedQueryParser
282
+ from llm_reasoning import AdvancedLLMReasoning
283
+
284
+ try:
285
+ # Parse query
286
+ query_parser = AdvancedQueryParser()
287
+ parsed_query = query_parser.parse_query(user_query)
288
+
289
+ # Create fallback reasoning
290
+ reasoning_engine = AdvancedLLMReasoning()
291
+ fallback_context = [{
292
+ 'content': f"Based on the query: {user_query}",
293
+ 'source_file': 'fallback',
294
+ 'similarity_score': 0.5
295
+ }]
296
+
297
+ reasoning_result = reasoning_engine.analyze_query(
298
+ user_query, fallback_context, parsed_query.query_type
299
+ )
300
+
301
+ # Create fallback result
302
+ from rag_system import QueryResult
303
+ result = QueryResult(
304
+ query=user_query,
305
+ parsed_query=parsed_query,
306
+ search_results=[],
307
+ reasoning_result=reasoning_result,
308
+ processing_time=time.time() - start_time,
309
+ timestamp=time.time(),
310
+ audit_trail={'status': 'fallback', 'error': str(e)}
311
+ )
312
+ processing_time = time.time() - start_time
313
+
314
+ except Exception as fallback_error:
315
+ print(f"❌ Fallback processing also failed: {fallback_error}")
316
+ continue
317
+
318
+ # Display results
319
+ print(f"\n📋 QUERY RESULTS")
320
+ print("=" * 50)
321
+ print(f"⏱️ Processing time: {processing_time:.2f} seconds")
322
+ print(f"🎯 Decision: {result.reasoning_result.decision.upper()}")
323
+ print(f"📊 Confidence: {result.reasoning_result.confidence_score:.1%}")
324
+
325
+ if result.reasoning_result.amount:
326
+ print(f"💰 Amount: ${result.reasoning_result.amount:,.2f}")
327
+
328
+ if result.reasoning_result.waiting_period:
329
+ print(f"⏰ Waiting Period: {result.reasoning_result.waiting_period}")
330
+
331
+ print(f"\n📝 Justification:")
332
+ print(result.reasoning_result.justification)
333
+
334
+ if result.reasoning_result.relevant_clauses:
335
+ print(f"\n📄 Relevant Clauses:")
336
+ for clause in result.reasoning_result.relevant_clauses:
337
+ print(f" • {clause}")
338
+
339
+ if result.reasoning_result.conditions:
340
+ print(f"\n✅ Conditions:")
341
+ for condition in result.reasoning_result.conditions:
342
+ print(f" • {condition}")
343
+
344
+ if result.reasoning_result.exclusions:
345
+ print(f"\n❌ Exclusions:")
346
+ for exclusion in result.reasoning_result.exclusions:
347
+ print(f" • {exclusion}")
348
+
349
+ if result.reasoning_result.required_documents:
350
+ print(f"\n📋 Required Documents:")
351
+ for doc in result.reasoning_result.required_documents:
352
+ print(f" • {doc}")
353
+
354
+ # Show search results summary
355
+ if result.search_results:
356
+ print(f"\n🔍 Search Results Summary:")
357
+ print(f" Found {len(result.search_results)} relevant documents")
358
+ for i, search_result in enumerate(result.search_results[:3], 1):
359
+ print(f" {i}. {search_result.source_file} (Score: {search_result.similarity_score:.2f})")
360
+
361
+ # Ask if user wants to see detailed explanation
362
+ show_details = input("\n❓ Show detailed explanation? (y/n): ").strip().lower()
363
+ if show_details in ['y', 'yes']:
364
+ try:
365
+ detailed_explanation = rag_system.reasoning_engine.explain_decision(result.reasoning_result)
366
+ print(f"\n📖 DETAILED EXPLANATION:")
367
+ print("=" * 50)
368
+ print(detailed_explanation)
369
+ except Exception as e:
370
+ print(f"❌ Error generating detailed explanation: {e}")
371
+ print("💡 Detailed explanation not available")
372
+
373
+ results.append({
374
+ "query": user_query,
375
+ "result": result,
376
+ "processing_time": processing_time
377
+ })
378
+
379
+ print()
380
+
381
+ # Ask if user wants to continue
382
+ if query_count % 3 == 0: # Ask every 3 queries
383
+ continue_choice = input("❓ Continue asking questions? (y/n): ").strip().lower()
384
+ if continue_choice not in ['y', 'yes', '']:
385
+ print("👋 Thanks for using the RAG system!")
386
+ break
387
+
388
+ except KeyboardInterrupt:
389
+ print("\n👋 Interrupted by user. Goodbye!")
390
+ break
391
+ except Exception as e:
392
+ print(f"❌ Error processing query: {e}")
393
+ print("💡 Try asking a different question or type 'help' for examples.")
394
+ print()
395
+
396
+ return results
397
+
398
+ def show_system_statistics(rag_system: AdvancedRAGSystem):
399
+ """Show system statistics"""
400
+ print("\n📊 SYSTEM STATISTICS")
401
+ print("-" * 40)
402
+
403
+ try:
404
+ stats = rag_system.get_system_statistics()
405
+
406
+ if stats:
407
+ # Vector database stats
408
+ db_stats = stats.get('vector_database', {})
409
+ print(f"📚 Vector Database:")
410
+ print(f" Total chunks: {db_stats.get('total_chunks', 0)}")
411
+ print(f" Unique sources: {db_stats.get('unique_sources', 0)}")
412
+ print(f" File types: {', '.join(db_stats.get('file_types', []))}")
413
+
414
+ # Audit trail stats
415
+ audit_stats = stats.get('audit_trail', {})
416
+ print(f"\n📋 Audit Trail:")
417
+ print(f" Total entries: {audit_stats.get('total_entries', 0)}")
418
+ print(f" Successful queries: {audit_stats.get('successful_queries', 0)}")
419
+ print(f" Failed queries: {audit_stats.get('failed_queries', 0)}")
420
+ print(f" Document ingestions: {audit_stats.get('document_ingestions', 0)}")
421
+
422
+ # Component info
423
+ components = stats.get('components', {})
424
+ print(f"\n🔧 Components:")
425
+ print(f" Document Processor: {components.get('document_processor', 'Unknown')}")
426
+ print(f" Vector Database: {components.get('vector_database', 'Unknown')}")
427
+ print(f" Query Parser: {components.get('query_parser', 'Unknown')}")
428
+ print(f" Reasoning Engine: {components.get('reasoning_engine', 'Unknown')}")
429
+ print(f" GPU Enabled: {components.get('use_gpu', False)}")
430
+ else:
431
+ print("❌ Unable to retrieve system statistics")
432
+
433
+ except Exception as e:
434
+ print(f"❌ Error getting system statistics: {e}")
435
+
436
+ def show_audit_trail(rag_system: AdvancedRAGSystem):
437
+ """Show audit trail"""
438
+ print("\n🔍 AUDIT TRAIL")
439
+ print("-" * 40)
440
+
441
+ try:
442
+ audit_log = rag_system.get_audit_trail()
443
+
444
+ if audit_log:
445
+ print(f"📋 Total entries: {len(audit_log)}")
446
+
447
+ # Show recent entries
448
+ recent_entries = audit_log[-10:] # Last 10 entries
449
+ print(f"\n📝 Recent Entries:")
450
+
451
+ for i, entry in enumerate(reversed(recent_entries), 1):
452
+ action = entry.get('action', 'Unknown')
453
+ timestamp = entry.get('timestamp', 'Unknown')
454
+ status = entry.get('status', 'Unknown')
455
+
456
+ print(f" {i}. {action} - {status} ({timestamp})")
457
+
458
+ if action == 'query_processing':
459
+ query = entry.get('query', 'Unknown')
460
+ print(f" Query: {query[:50]}...")
461
+
462
+ reasoning = entry.get('reasoning_result', {})
463
+ if reasoning:
464
+ decision = reasoning.get('decision', 'Unknown')
465
+ confidence = reasoning.get('confidence_score', 0.0)
466
+ print(f" Decision: {decision} (Confidence: {confidence:.1%})")
467
+
468
+ elif action == 'document_ingestion':
469
+ file_path = entry.get('file_path', 'Unknown')
470
+ chunks = entry.get('chunks_processed', 0)
471
+ print(f" File: {os.path.basename(file_path)} ({chunks} chunks)")
472
+
473
+ print()
474
+
475
+ # Ask if user wants to save audit trail
476
+ save_choice = input("💾 Save audit trail to file? (y/n): ").strip().lower()
477
+ if save_choice in ['y', 'yes']:
478
+ filename = input("📁 Enter filename (default: audit_trail.json): ").strip()
479
+ if not filename:
480
+ filename = "audit_trail.json"
481
+
482
+ if rag_system.save_audit_trail(filename):
483
+ print(f"✅ Audit trail saved to: {filename}")
484
+ else:
485
+ print("❌ Failed to save audit trail")
486
+ else:
487
+ print("📋 No audit trail entries found")
488
+
489
+ except Exception as e:
490
+ print(f"❌ Error showing audit trail: {e}")
491
+
492
+ def validate_system(rag_system: AdvancedRAGSystem):
493
+ """Validate system components"""
494
+ print("\n🧪 SYSTEM VALIDATION")
495
+ print("-" * 40)
496
+
497
+ try:
498
+ validation = rag_system.validate_system()
499
+
500
+ print("🔍 Checking system components...")
501
+
502
+ components = [
503
+ ('Document Processor', validation.get('document_processor', False)),
504
+ ('Vector Database', validation.get('vector_database', False)),
505
+ ('Query Parser', validation.get('query_parser', False)),
506
+ ('Reasoning Engine', validation.get('reasoning_engine', False))
507
+ ]
508
+
509
+ all_valid = True
510
+ for component_name, is_valid in components:
511
+ status = "✅ PASS" if is_valid else "❌ FAIL"
512
+ print(f" {component_name}: {status}")
513
+ if not is_valid:
514
+ all_valid = False
515
+
516
+ print(f"\n🎯 Overall Status: {'✅ ALL COMPONENTS VALID' if all_valid else '❌ SOME COMPONENTS FAILED'}")
517
+
518
+ if not all_valid:
519
+ print("\n❌ Errors found:")
520
+ for error in validation.get('errors', []):
521
+ print(f" • {error}")
522
+
523
+ except Exception as e:
524
+ print(f"❌ Error validating system: {e}")
525
+
526
+ def export_system_data(rag_system: AdvancedRAGSystem):
527
+ """Export system data"""
528
+ print("\n💾 EXPORT SYSTEM DATA")
529
+ print("-" * 40)
530
+
531
+ try:
532
+ filename = input("📁 Enter filename (default: system_export.json): ").strip()
533
+ if not filename:
534
+ filename = "system_export.json"
535
+
536
+ print(f"⏳ Exporting system data to: {filename}")
537
+
538
+ if rag_system.export_system_data(filename):
539
+ print(f"✅ System data exported successfully to: {filename}")
540
+
541
+ # Show file size
542
+ if os.path.exists(filename):
543
+ file_size = os.path.getsize(filename)
544
+ print(f"📁 File size: {file_size:,} bytes")
545
+ else:
546
+ print("❌ Failed to export system data")
547
+
548
+ except Exception as e:
549
+ print(f"❌ Error exporting system data: {e}")
550
+
551
+ def clear_system(rag_system: AdvancedRAGSystem):
552
+ """Clear system data"""
553
+ print("\n🗑️ CLEAR SYSTEM")
554
+ print("-" * 40)
555
+
556
+ try:
557
+ confirm = input("⚠️ This will clear ALL system data. Are you sure? (yes/no): ").strip().lower()
558
+
559
+ if confirm == 'yes':
560
+ print("⏳ Clearing system data...")
561
+
562
+ if rag_system.clear_system():
563
+ print("✅ System data cleared successfully")
564
+ else:
565
+ print("❌ Failed to clear system data")
566
+ else:
567
+ print("❌ Operation cancelled")
568
+
569
+ except Exception as e:
570
+ print(f"❌ Error clearing system: {e}")
571
+
572
+ def test_ocr():
573
+ """Test OCR functionality"""
574
+ print("\n🧪 OCR TEST")
575
+ print("-" * 40)
576
+
577
+ if not OCR_AVAILABLE:
578
+ print("❌ pytesseract not available")
579
+ print("💡 Install with: pip install pytesseract")
580
+ return
581
+
582
+ try:
583
+ # Create a simple test image
584
+ from PIL import Image, ImageDraw, ImageFont
585
+
586
+ # Create test image
587
+ img = Image.new('RGB', (300, 100), color='white')
588
+ draw = ImageDraw.Draw(img)
589
+
590
+ # Try to use a default font
591
+ try:
592
+ font = ImageFont.load_default()
593
+ except:
594
+ font = None
595
+
596
+ # Draw text
597
+ text = "Test OCR Text"
598
+ draw.text((10, 40), text, fill='black', font=font)
599
+
600
+ # Test OCR
601
+ ocr_text = pytesseract.image_to_string(img)
602
+ print(f"✅ OCR test successful")
603
+ print(f" Original: '{text}'")
604
+ print(f" OCR result: '{ocr_text.strip()}'")
605
+
606
+ except Exception as e:
607
+ print(f"❌ OCR test failed: {e}")
608
+ print("💡 Make sure Tesseract is installed and configured")
609
+
610
+ def show_help():
611
+ """Show help information"""
612
+ print("\n❓ HELP")
613
+ print("-" * 40)
614
+ print("📋 This RAG system can process various document types and answer questions about them.")
615
+ print("\n📄 Supported Document Types:")
616
+ print(" • PDF files (with OCR support for scanned documents)")
617
+ print(" • Text files (.txt)")
618
+ print(" • Word documents (.docx)")
619
+ print(" • HTML files (.html)")
620
+ print(" • Email files (.eml, .msg)")
621
+ print(" • CSV files (.csv)")
622
+ print(" • JSON files (.json)")
623
+
624
+ print("\n🤔 Query Examples:")
625
+ print(" • 'Is heart surgery covered under this policy?'")
626
+ print(" • 'How do I file a claim?'")
627
+ print(" • 'What is the waiting period for pre-existing conditions?'")
628
+ print(" • 'What documents are required for claim submission?'")
629
+ print(" • 'What is the maximum coverage amount?'")
630
+
631
+ print("\n💡 Tips:")
632
+ print(" • Upload documents first before asking questions")
633
+ print(" • Use natural language - the system understands plain English")
634
+ print(" • The system can handle vague or incomplete queries")
635
+ print(" • All queries are logged in the audit trail")
636
+ print(" • Use the system validation to check component status")
637
+ print(" • Test OCR functionality if you have issues with scanned documents")
638
+
639
+ def main():
640
+ """Main function"""
641
+ # Parse command line arguments
642
+ parser = argparse.ArgumentParser(description='Advanced RAG System')
643
+ parser.add_argument('--query', help='Process a single query from file')
644
+ parser.add_argument('--upload', help='Upload and process a document')
645
+ parser.add_argument('--status', action='store_true', help='Check system status')
646
+
647
+ args = parser.parse_args()
648
+
649
+ # Handle command line arguments
650
+ if args.query:
651
+ # Process single query from file
652
+ try:
653
+ with open(args.query, 'r') as f:
654
+ question = f.read().strip()
655
+
656
+ # Initialize RAG system
657
+ rag_system = AdvancedRAGSystem(use_gpu=False)
658
+
659
+ # Process the query
660
+ result = rag_system.process_query(question)
661
+
662
+ # Output results in a structured format
663
+ print(f"Question: {question}")
664
+ print(f"Decision: {result.reasoning_result.decision}")
665
+ print(f"Confidence: {result.reasoning_result.confidence_score:.1%}")
666
+ print(f"Justification: {result.reasoning_result.justification}")
667
+
668
+ if result.reasoning_result.amount:
669
+ print(f"Amount: ${result.reasoning_result.amount:,.2f}")
670
+
671
+ if result.reasoning_result.waiting_period:
672
+ print(f"Waiting Period: {result.reasoning_result.waiting_period}")
673
+
674
+ if result.reasoning_result.relevant_clauses:
675
+ print(f"Relevant Clauses: {', '.join(result.reasoning_result.relevant_clauses)}")
676
+
677
+ if result.reasoning_result.conditions:
678
+ print(f"Conditions: {', '.join(result.reasoning_result.conditions)}")
679
+
680
+ if result.reasoning_result.exclusions:
681
+ print(f"Exclusions: {', '.join(result.reasoning_result.exclusions)}")
682
+
683
+ if result.reasoning_result.required_documents:
684
+ print(f"Required Documents: {', '.join(result.reasoning_result.required_documents)}")
685
+
686
+ return
687
+ except Exception as e:
688
+ print(f"Error processing query: {e}")
689
+ sys.exit(1)
690
+
691
+ elif args.upload:
692
+ # Upload and process document
693
+ try:
694
+ # Initialize RAG system
695
+ rag_system = AdvancedRAGSystem(use_gpu=False)
696
+
697
+ # Process the document
698
+ chunks = rag_system.ingest_document(args.upload, use_ocr=False)
699
+
700
+ print(f"Document processed successfully: {args.upload}")
701
+ print(f"Chunks processed: {len(chunks)}")
702
+
703
+ return
704
+ except Exception as e:
705
+ print(f"Error processing document: {e}")
706
+ sys.exit(1)
707
+
708
+ elif args.status:
709
+ # Check system status
710
+ try:
711
+ rag_system = AdvancedRAGSystem(use_gpu=False)
712
+ print("System Status: READY")
713
+ return
714
+ except Exception as e:
715
+ print(f"System Status: ERROR - {e}")
716
+ sys.exit(1)
717
+
718
+ # Interactive mode (default)
719
+ print_banner()
720
+
721
+ try:
722
+ # Initialize RAG system
723
+ print("🚀 Initializing RAG system...")
724
+ try:
725
+ rag_system = AdvancedRAGSystem(use_gpu=False) # Use CPU for better compatibility
726
+ print("✅ RAG system initialized successfully!")
727
+ except Exception as e:
728
+ print(f"❌ Failed to initialize RAG system: {e}")
729
+ print("💡 Trying with minimal configuration...")
730
+
731
+ try:
732
+ # Try with minimal settings
733
+ rag_system = AdvancedRAGSystem(
734
+ use_gpu=False,
735
+ model_path=None # Don't require model file
736
+ )
737
+ print("✅ RAG system initialized with minimal configuration!")
738
+ except Exception as e2:
739
+ print(f"❌ RAG system initialization failed: {e2}")
740
+ print("💡 Please check your system configuration")
741
+ return
742
+
743
+ # Main loop
744
+ while True:
745
+ print_menu()
746
+ choice = get_user_choice()
747
+
748
+ if choice == '1':
749
+ upload_document(rag_system)
750
+ elif choice == '2':
751
+ process_query(rag_system)
752
+ elif choice == '3':
753
+ show_system_statistics(rag_system)
754
+ elif choice == '4':
755
+ show_audit_trail(rag_system)
756
+ elif choice == '5':
757
+ validate_system(rag_system)
758
+ elif choice == '6':
759
+ export_system_data(rag_system)
760
+ elif choice == '7':
761
+ clear_system(rag_system)
762
+ elif choice == '8':
763
+ test_ocr()
764
+ elif choice == '9':
765
+ show_help()
766
+ elif choice == '0':
767
+ print("👋 Thank you for using the Advanced RAG System!")
768
+ break
769
+ else:
770
+ print("❌ Invalid choice. Please enter a number between 1-0.")
771
+
772
+ # Pause before showing menu again
773
+ input("\n⏸️ Press Enter to continue...")
774
+
775
+ except KeyboardInterrupt:
776
+ print("\n👋 Goodbye!")
777
+ except Exception as e:
778
+ print(f"❌ Fatal error: {e}")
779
+ print("💡 Please check your system configuration and try again.")
780
+
781
+ if __name__ == "__main__":
782
+ main()
performance_analysis.py ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Performance analysis script to identify bottlenecks
4
+ """
5
+
6
+ import os
7
+ import sys
8
+ import time
9
+ import cProfile
10
+ import pstats
11
+ from pathlib import Path
12
+
13
+ def analyze_performance():
14
+ """Analyze performance of each component"""
15
+ print("⚡ PERFORMANCE ANALYSIS")
16
+ print("=" * 50)
17
+
18
+ try:
19
+ from rag_system import AdvancedRAGSystem
20
+ from document_processer import AdvancedDocumentProcessor
21
+ from vector_database import VectorDatabase
22
+ from query_parser import AdvancedQueryParser
23
+ from llm_reasoning import AdvancedLLMReasoning
24
+
25
+ file_path = "doc2.pdf"
26
+ if not os.path.exists(file_path):
27
+ print(f"❌ File not found: {file_path}")
28
+ return
29
+
30
+ print(f"📄 Testing with file: {file_path}")
31
+
32
+ # Test 1: Document Processing Performance
33
+ print("\n1️⃣ DOCUMENT PROCESSING PERFORMANCE")
34
+ print("-" * 40)
35
+
36
+ doc_processor = AdvancedDocumentProcessor()
37
+ start_time = time.time()
38
+ chunks = doc_processor.process_document(file_path, use_ocr=False)
39
+ doc_time = time.time() - start_time
40
+
41
+ print(f"✅ Document processing: {doc_time:.2f}s")
42
+ print(f"📊 Chunks created: {len(chunks)}")
43
+ print(f"📊 Average time per chunk: {doc_time/len(chunks):.4f}s")
44
+
45
+ # Test 2: Vector Database Performance
46
+ print("\n2️⃣ VECTOR DATABASE PERFORMANCE")
47
+ print("-" * 40)
48
+
49
+ vector_db = VectorDatabase()
50
+ start_time = time.time()
51
+ success = vector_db.add_documents(chunks)
52
+ vector_time = time.time() - start_time
53
+
54
+ print(f"✅ Vector database addition: {vector_time:.2f}s")
55
+ print(f"📊 Success: {success}")
56
+ print(f"📊 Average time per chunk: {vector_time/len(chunks):.4f}s")
57
+
58
+ # Test 3: Query Parser Performance
59
+ print("\n3️⃣ QUERY PARSER PERFORMANCE")
60
+ print("-" * 40)
61
+
62
+ query_parser = AdvancedQueryParser()
63
+ test_query = "Does the policy cover newborn care after hospital discharge?"
64
+
65
+ start_time = time.time()
66
+ parsed = query_parser.parse_query(test_query)
67
+ parser_time = time.time() - start_time
68
+
69
+ print(f"✅ Query parsing: {parser_time:.2f}s")
70
+ print(f"📊 Query type: {parsed.query_type}")
71
+ print(f"📊 Confidence: {parsed.confidence}")
72
+
73
+ # Test 4: LLM Reasoning Performance
74
+ print("\n4️⃣ LLM REASONING PERFORMANCE")
75
+ print("-" * 40)
76
+
77
+ reasoning_engine = AdvancedLLMReasoning(use_gpu=False)
78
+ test_context = [{"content": "Sample policy content", "source_file": "test.pdf"}]
79
+
80
+ start_time = time.time()
81
+ result = reasoning_engine.analyze_query(test_query, test_context, "coverage_inquiry")
82
+ reasoning_time = time.time() - start_time
83
+
84
+ print(f"✅ LLM reasoning: {reasoning_time:.2f}s")
85
+ print(f"📊 Decision: {result.decision}")
86
+ print(f"📊 Confidence: {result.confidence_score}")
87
+
88
+ # Test 5: Full RAG System Performance
89
+ print("\n5️⃣ FULL RAG SYSTEM PERFORMANCE")
90
+ print("-" * 40)
91
+
92
+ rag_system = AdvancedRAGSystem(use_gpu=False)
93
+
94
+ # Document ingestion
95
+ start_time = time.time()
96
+ rag_chunks = rag_system.ingest_document(file_path, use_ocr=False)
97
+ ingestion_time = time.time() - start_time
98
+
99
+ print(f"✅ Document ingestion: {ingestion_time:.2f}s")
100
+ print(f"📊 Chunks ingested: {len(rag_chunks)}")
101
+
102
+ # Query processing
103
+ start_time = time.time()
104
+ query_result = rag_system.process_query(test_query)
105
+ query_time = time.time() - start_time
106
+
107
+ print(f"✅ Query processing: {query_time:.2f}s")
108
+ print(f"📊 Total time: {ingestion_time + query_time:.2f}s")
109
+
110
+ # Performance Summary
111
+ print("\n📊 PERFORMANCE SUMMARY")
112
+ print("=" * 50)
113
+ print(f"Document Processing: {doc_time:.2f}s ({doc_time/(ingestion_time + query_time)*100:.1f}%)")
114
+ print(f"Vector Database: {vector_time:.2f}s ({vector_time/(ingestion_time + query_time)*100:.1f}%)")
115
+ print(f"Query Parsing: {parser_time:.2f}s ({parser_time/(ingestion_time + query_time)*100:.1f}%)")
116
+ print(f"LLM Reasoning: {reasoning_time:.2f}s ({reasoning_time/(ingestion_time + query_time)*100:.1f}%)")
117
+ print(f"TOTAL TIME: {ingestion_time + query_time:.2f}s")
118
+
119
+ # Optimization Recommendations
120
+ print("\n💡 OPTIMIZATION RECOMMENDATIONS")
121
+ print("=" * 50)
122
+
123
+ if doc_time > 10:
124
+ print("🔧 Document processing is slow - consider:")
125
+ print(" - Reduce chunk size")
126
+ print(" - Use parallel processing")
127
+ print(" - Optimize OCR settings")
128
+
129
+ if vector_time > 20:
130
+ print("🔧 Vector database is slow - consider:")
131
+ print(" - Use GPU for embeddings")
132
+ print(" - Batch processing")
133
+ print(" - Reduce embedding dimensions")
134
+
135
+ if reasoning_time > 30:
136
+ print("🔧 LLM reasoning is slow - consider:")
137
+ print(" - Use smaller model")
138
+ print(" - Enable GPU acceleration")
139
+ print(" - Reduce max tokens")
140
+ print(" - Use caching")
141
+
142
+ if ingestion_time + query_time > 30:
143
+ print("🔧 Overall system is slow - consider:")
144
+ print(" - Enable GPU for all components")
145
+ print(" - Use model quantization")
146
+ print(" - Implement caching")
147
+ print(" - Parallel processing")
148
+
149
+ except Exception as e:
150
+ print(f"❌ Error: {e}")
151
+ import traceback
152
+ traceback.print_exc()
153
+
154
+ if __name__ == "__main__":
155
+ analyze_performance()
query_parser.py ADDED
@@ -0,0 +1,600 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Advanced Query Parser for Natural Language Processing
3
+ Handles vague, incomplete, and plain English queries with entity extraction
4
+ """
5
+
6
+ import re
7
+ import logging
8
+ from typing import List, Dict, Any, Optional, Tuple
9
+ from dataclasses import dataclass
10
+ from datetime import datetime
11
+ import json
12
+
13
+ # NLP and ML libraries
14
+ # import spacy # Temporarily commented out due to installation issues
15
+ # from transformers import pipeline # Temporarily commented out due to installation issues
16
+ import nltk
17
+ from nltk.tokenize import word_tokenize, sent_tokenize
18
+ from nltk.corpus import stopwords
19
+ from nltk.stem import WordNetLemmatizer
20
+ import numpy as np
21
+
22
+ # Optional imports with error handling
23
+ try:
24
+ from transformers import pipeline
25
+ TRANSFORMERS_AVAILABLE = True
26
+ except ImportError:
27
+ TRANSFORMERS_AVAILABLE = False
28
+ print("⚠️ Transformers not available. NER functionality will be limited.")
29
+
30
+ # Download required NLTK data
31
+ try:
32
+ nltk.data.find('tokenizers/punkt')
33
+ except LookupError:
34
+ nltk.download('punkt')
35
+
36
+ try:
37
+ nltk.data.find('corpora/stopwords')
38
+ except LookupError:
39
+ nltk.download('stopwords')
40
+
41
+ try:
42
+ nltk.data.find('corpora/wordnet')
43
+ except LookupError:
44
+ nltk.download('wordnet')
45
+
46
+ # Additional NLTK data that might be needed
47
+ try:
48
+ nltk.data.find('tokenizers/punkt_tab')
49
+ except LookupError:
50
+ try:
51
+ nltk.download('punkt_tab')
52
+ except:
53
+ pass # Ignore if punkt_tab is not available
54
+
55
+ # Configure logging
56
+ logging.basicConfig(level=logging.INFO)
57
+ logger = logging.getLogger(__name__)
58
+
59
+ @dataclass
60
+ class ParsedQuery:
61
+ """Represents a parsed query with extracted information"""
62
+ original_query: str
63
+ enhanced_query: str
64
+ query_type: str # claim, coverage, policy, general, etc.
65
+ entities: Dict[str, List[str]]
66
+ intent: str
67
+ confidence: float
68
+ keywords: List[str]
69
+ synonyms: List[str]
70
+ context: Dict[str, Any]
71
+ timestamp: datetime
72
+
73
+ @dataclass
74
+ class QueryEntity:
75
+ """Represents an extracted entity from a query"""
76
+ text: str
77
+ entity_type: str
78
+ confidence: float
79
+ start_pos: int
80
+ end_pos: int
81
+
82
+ class AdvancedQueryParser:
83
+ """Advanced query parser with entity extraction and query enhancement"""
84
+
85
+ def __init__(self,
86
+ spacy_model: str = "en_core_web_sm",
87
+ use_gpu: bool = True):
88
+
89
+ self.use_gpu = use_gpu
90
+ self.spacy_model = spacy_model
91
+
92
+ # Initialize NLP components
93
+ self._initialize_nlp_components()
94
+
95
+ # Initialize entity extractors
96
+ self._initialize_entity_extractors()
97
+
98
+ # Initialize query enhancement
99
+ self._initialize_query_enhancement()
100
+
101
+ logger.info("Advanced Query Parser initialized")
102
+
103
+ def _initialize_nlp_components(self):
104
+ """Initialize NLP components"""
105
+ try:
106
+ # Load spaCy model
107
+ # self.nlp = spacy.load(self.spacy_model) # Temporarily commented out due to installation issues
108
+ self.nlp = None # Set to None temporarily
109
+
110
+ # Initialize NLTK components
111
+ self.lemmatizer = WordNetLemmatizer()
112
+ self.stop_words = set(stopwords.words('english'))
113
+
114
+ # Add custom stop words for insurance domain
115
+ insurance_stop_words = {
116
+ 'policy', 'claim', 'coverage', 'insurance', 'document',
117
+ 'please', 'help', 'need', 'want', 'know', 'tell'
118
+ }
119
+ self.stop_words.update(insurance_stop_words)
120
+
121
+ logger.info("NLP components initialized")
122
+
123
+ except Exception as e:
124
+ logger.error(f"Error initializing NLP components: {e}")
125
+ raise
126
+
127
+ def _initialize_entity_extractors(self):
128
+ """Initialize entity extraction components"""
129
+ try:
130
+ # Initialize NER pipeline if transformers is available
131
+ if TRANSFORMERS_AVAILABLE:
132
+ try:
133
+ device = 0 if self.use_gpu else -1
134
+ self.ner_pipeline = pipeline(
135
+ "ner",
136
+ model="dbmdz/bert-large-cased-finetuned-conll03-english",
137
+ device=device
138
+ )
139
+ logger.info("NER pipeline initialized")
140
+ except Exception as e:
141
+ logger.warning(f"NER pipeline initialization failed: {e}")
142
+ self.ner_pipeline = None
143
+ else:
144
+ self.ner_pipeline = None
145
+ logger.info("NER pipeline not available (transformers not installed)")
146
+
147
+ # Insurance-specific entity patterns (always available)
148
+ self.insurance_entities = {
149
+ 'medical_condition': [
150
+ r'\b(heart attack|stroke|cancer|diabetes|hypertension|asthma|arthritis)\b',
151
+ r'\b(surgery|operation|procedure|treatment|therapy)\b',
152
+ r'\b(medication|prescription|drug|medicine)\b'
153
+ ],
154
+ 'coverage_type': [
155
+ r'\b(health|medical|dental|vision|life|auto|home|property)\s+(insurance|coverage|policy)\b',
156
+ r'\b(accident|disability|liability|comprehensive|collision)\b'
157
+ ],
158
+ 'amount': [
159
+ r'\$\d+(?:,\d{3})*(?:\.\d{2})?',
160
+ r'\b\d+\s*(?:dollars?|rupees?|euros?)\b',
161
+ r'\b(?:maximum|minimum|total|sum)\s+(?:of\s+)?\$\d+\b'
162
+ ],
163
+ 'time_period': [
164
+ r'\b(waiting period|grace period|coverage period|policy term)\b',
165
+ r'\b(\d+\s+(?:days?|weeks?|months?|years?))\b',
166
+ r'\b(immediate|urgent|emergency|routine)\b'
167
+ ],
168
+ 'document_type': [
169
+ r'\b(claim form|medical certificate|prescription|bill|receipt|invoice)\b',
170
+ r'\b(doctor|physician|specialist|hospital|clinic)\s+(?:report|note|letter)\b'
171
+ ]
172
+ }
173
+
174
+ logger.info("Entity extractors initialized")
175
+
176
+ except Exception as e:
177
+ logger.error(f"Error initializing entity extractors: {e}")
178
+ # Set defaults if initialization fails
179
+ self.ner_pipeline = None
180
+ self.insurance_entities = {}
181
+
182
+ def _initialize_query_enhancement(self):
183
+ """Initialize query enhancement components"""
184
+ try:
185
+ # Query enhancement patterns
186
+ self.enhancement_patterns = {
187
+ 'claim_related': {
188
+ 'keywords': ['claim', 'file', 'submit', 'process', 'approve', 'reject'],
189
+ 'synonyms': ['application', 'request', 'petition', 'appeal'],
190
+ 'context': 'claim_processing'
191
+ },
192
+ 'coverage_related': {
193
+ 'keywords': ['cover', 'include', 'exclude', 'limit', 'maximum', 'minimum'],
194
+ 'synonyms': ['protection', 'benefit', 'entitlement', 'eligibility'],
195
+ 'context': 'coverage_analysis'
196
+ },
197
+ 'policy_related': {
198
+ 'keywords': ['policy', 'terms', 'conditions', 'clause', 'section'],
199
+ 'synonyms': ['agreement', 'contract', 'document', 'provision'],
200
+ 'context': 'policy_review'
201
+ },
202
+ 'medical_related': {
203
+ 'keywords': ['medical', 'health', 'treatment', 'surgery', 'medication'],
204
+ 'synonyms': ['healthcare', 'therapeutic', 'clinical', 'pharmaceutical'],
205
+ 'context': 'medical_coverage'
206
+ }
207
+ }
208
+
209
+ # Query type classification patterns
210
+ self.query_types = {
211
+ 'claim_inquiry': [
212
+ r'\b(how|what|can|is|does)\s+(?:to\s+)?(?:file|submit|process|claim)\b',
213
+ r'\b(claim|file|submit|process)\s+(?:a\s+)?(?:claim|request)\b'
214
+ ],
215
+ 'coverage_check': [
216
+ r'\b(cover|include|exclude|limit|maximum|minimum)\b',
217
+ r'\b(is|does|can)\s+(?:.*?)\s+(?:cover|include|exclude)\b',
218
+ r'\b(waiting\s+period|grace\s+period|coverage\s+period)\b',
219
+ r'\b(what\'s|what\s+is)\s+(?:the\s+)?(?:waiting|grace|coverage)\b'
220
+ ],
221
+ 'policy_review': [
222
+ r'\b(policy|terms|conditions|clause|section)\b',
223
+ r'\b(what|which|where)\s+(?:in\s+)?(?:policy|document)\b'
224
+ ],
225
+ 'medical_coverage': [
226
+ r'\b(medical|health|treatment|surgery|medication|prescription)\b',
227
+ r'\b(doctor|hospital|clinic|physician|specialist)\b',
228
+ r'\b(dental|dental\s+procedures|dental\s+treatment)\b',
229
+ r'\b(heart\s+surgery|cardiac|surgical)\b'
230
+ ],
231
+ 'general_inquiry': [
232
+ r'\b(what|how|when|where|why|who)\b',
233
+ r'\b(explain|describe|tell|show)\b'
234
+ ]
235
+ }
236
+
237
+ logger.info("Query enhancement components initialized")
238
+
239
+ except Exception as e:
240
+ logger.error(f"Error initializing query enhancement: {e}")
241
+
242
+ def parse_query(self, query: str) -> ParsedQuery:
243
+ """Main method to parse and enhance a query"""
244
+ try:
245
+ # Clean and preprocess query
246
+ cleaned_query = self._preprocess_query(query)
247
+
248
+ # Extract entities
249
+ entities = self._extract_entities(cleaned_query)
250
+
251
+ # Determine query type and intent
252
+ query_type, intent, confidence = self._classify_query(cleaned_query)
253
+
254
+ # Extract keywords
255
+ keywords = self._extract_keywords(cleaned_query)
256
+
257
+ # Generate synonyms
258
+ synonyms = self._generate_synonyms(keywords)
259
+
260
+ # Enhance query
261
+ enhanced_query = self._enhance_query(cleaned_query, entities, query_type)
262
+
263
+ # Build context
264
+ context = self._build_context(cleaned_query, entities, query_type)
265
+
266
+ parsed_query = ParsedQuery(
267
+ original_query=query,
268
+ enhanced_query=enhanced_query,
269
+ query_type=query_type,
270
+ entities=entities,
271
+ intent=intent,
272
+ confidence=confidence,
273
+ keywords=keywords,
274
+ synonyms=synonyms,
275
+ context=context,
276
+ timestamp=datetime.now()
277
+ )
278
+
279
+ logger.info(f"Query parsed successfully: {query_type} ({confidence:.2f})")
280
+ return parsed_query
281
+
282
+ except Exception as e:
283
+ logger.error(f"Error parsing query: {e}")
284
+ # Return a basic parsed query
285
+ return self._create_basic_parsed_query(query)
286
+
287
+ def _preprocess_query(self, query: str) -> str:
288
+ """Preprocess and clean the query"""
289
+ try:
290
+ # Convert to lowercase
291
+ query = query.lower().strip()
292
+
293
+ # Remove extra whitespace
294
+ query = re.sub(r'\s+', ' ', query)
295
+
296
+ # Remove special characters but keep important ones
297
+ query = re.sub(r'[^\w\s\-\.\,\?\&]', '', query)
298
+
299
+ # Fix common abbreviations
300
+ query = self._fix_abbreviations(query)
301
+
302
+ return query
303
+
304
+ except Exception as e:
305
+ logger.error(f"Error preprocessing query: {e}")
306
+ return query
307
+
308
+ def _fix_abbreviations(self, query: str) -> str:
309
+ """Fix common abbreviations in insurance queries"""
310
+ abbreviations = {
311
+ 'dr.': 'doctor',
312
+ 'doc.': 'document',
313
+ 'med.': 'medical',
314
+ 'rx': 'prescription',
315
+ 'hosp.': 'hospital',
316
+ 'clinic.': 'clinic',
317
+ 'ins.': 'insurance',
318
+ 'pol.': 'policy',
319
+ 'claim.': 'claim',
320
+ 'coverage.': 'coverage'
321
+ }
322
+
323
+ for abbr, full in abbreviations.items():
324
+ query = query.replace(abbr, full)
325
+
326
+ return query
327
+
328
+ def _extract_entities(self, query: str) -> Dict[str, List[str]]:
329
+ """Extract entities from the query"""
330
+ entities = {}
331
+
332
+ try:
333
+ # Use spaCy for basic NER
334
+ # doc = self.nlp(query) # Temporarily commented out due to installation issues
335
+
336
+ # Extract named entities
337
+ # for ent in doc.ents: # Temporarily commented out due to installation issues
338
+ # entity_type = ent.label_.lower() # Temporarily commented out due to installation issues
339
+ # if entity_type not in entities: # Temporarily commented out due to installation issues
340
+ # entities[entity_type] = [] # Temporarily commented out due to installation issues
341
+ # entities[entity_type].append(ent.text) # Temporarily commented out due to installation issues
342
+
343
+ # Extract insurance-specific entities using patterns
344
+ for entity_type, patterns in self.insurance_entities.items():
345
+ entities[entity_type] = []
346
+ for pattern in patterns:
347
+ matches = re.findall(pattern, query, re.IGNORECASE)
348
+ entities[entity_type].extend(matches)
349
+
350
+ # Use BERT NER if available
351
+ if hasattr(self, 'ner_pipeline') and self.ner_pipeline is not None:
352
+ try:
353
+ ner_results = self.ner_pipeline(query)
354
+ for result in ner_results:
355
+ entity_type = result['entity'].lower()
356
+ if entity_type not in entities:
357
+ entities[entity_type] = []
358
+ entities[entity_type].append(result['word'])
359
+ except Exception as e:
360
+ logger.debug(f"BERT NER failed: {e}")
361
+
362
+ # Remove duplicates
363
+ for entity_type in entities:
364
+ entities[entity_type] = list(set(entities[entity_type]))
365
+
366
+ return entities
367
+
368
+ except Exception as e:
369
+ logger.error(f"Error extracting entities: {e}")
370
+ return {}
371
+
372
+ def _classify_query(self, query: str) -> Tuple[str, str, float]:
373
+ """Classify the query type and determine intent"""
374
+ try:
375
+ best_type = 'general_inquiry'
376
+ best_confidence = 0.0
377
+ intent = 'information_seeking'
378
+
379
+ # Check each query type
380
+ for query_type, patterns in self.query_types.items():
381
+ confidence = 0.0
382
+ matches = 0
383
+
384
+ for pattern in patterns:
385
+ if re.search(pattern, query, re.IGNORECASE):
386
+ matches += 1
387
+
388
+ if matches > 0:
389
+ confidence = matches / len(patterns)
390
+
391
+ if confidence > best_confidence:
392
+ best_confidence = confidence
393
+ best_type = query_type
394
+
395
+ # Determine intent based on query type
396
+ intent_mapping = {
397
+ 'claim_inquiry': 'claim_processing',
398
+ 'coverage_check': 'coverage_analysis',
399
+ 'policy_review': 'policy_review',
400
+ 'medical_coverage': 'medical_coverage',
401
+ 'general_inquiry': 'information_seeking'
402
+ }
403
+
404
+ intent = intent_mapping.get(best_type, 'information_seeking')
405
+
406
+ return best_type, intent, best_confidence
407
+
408
+ except Exception as e:
409
+ logger.error(f"Error classifying query: {e}")
410
+ return 'general_inquiry', 'information_seeking', 0.0
411
+
412
+ def _extract_keywords(self, query: str) -> List[str]:
413
+ """Extract important keywords from the query"""
414
+ try:
415
+ # Tokenize with fallback
416
+ try:
417
+ tokens = word_tokenize(query)
418
+ except Exception as tokenize_error:
419
+ logger.warning(f"Word tokenization failed, using simple split: {tokenize_error}")
420
+ tokens = query.split()
421
+
422
+ # Remove stop words and lemmatize
423
+ keywords = []
424
+ for token in tokens:
425
+ if token.lower() not in self.stop_words and len(token) > 2:
426
+ try:
427
+ lemmatized = self.lemmatizer.lemmatize(token.lower())
428
+ keywords.append(lemmatized)
429
+ except Exception as lemmatize_error:
430
+ logger.debug(f"Lemmatization failed for '{token}': {lemmatize_error}")
431
+ keywords.append(token.lower())
432
+
433
+ return keywords
434
+
435
+ except Exception as e:
436
+ logger.error(f"Error extracting keywords: {e}")
437
+ return []
438
+
439
+ def _generate_synonyms(self, keywords: List[str]) -> List[str]:
440
+ """Generate synonyms for keywords"""
441
+ synonyms = []
442
+
443
+ try:
444
+ # Simple synonym mapping for insurance domain
445
+ synonym_mapping = {
446
+ 'claim': ['application', 'request', 'petition'],
447
+ 'cover': ['include', 'protect', 'insure'],
448
+ 'policy': ['document', 'agreement', 'contract'],
449
+ 'medical': ['health', 'clinical', 'therapeutic'],
450
+ 'surgery': ['operation', 'procedure', 'treatment'],
451
+ 'hospital': ['clinic', 'medical center', 'facility'],
452
+ 'doctor': ['physician', 'specialist', 'medical practitioner'],
453
+ 'medicine': ['medication', 'drug', 'prescription'],
454
+ 'cost': ['expense', 'charge', 'fee', 'amount'],
455
+ 'limit': ['maximum', 'cap', 'ceiling', 'restriction']
456
+ }
457
+
458
+ for keyword in keywords:
459
+ if keyword in synonym_mapping:
460
+ synonyms.extend(synonym_mapping[keyword])
461
+
462
+ return list(set(synonyms))
463
+
464
+ except Exception as e:
465
+ logger.error(f"Error generating synonyms: {e}")
466
+ return []
467
+
468
+ def _enhance_query(self, query: str, entities: Dict[str, List[str]], query_type: str) -> str:
469
+ """Enhance the query with additional context and synonyms"""
470
+ try:
471
+ enhanced_parts = [query]
472
+
473
+ # Add entity context
474
+ for entity_type, entity_list in entities.items():
475
+ if entity_list:
476
+ enhanced_parts.append(f"related to {entity_type}: {', '.join(entity_list)}")
477
+
478
+ # Add query type context
479
+ if query_type in self.enhancement_patterns:
480
+ pattern = self.enhancement_patterns[query_type]
481
+ enhanced_parts.append(f"context: {pattern['context']}")
482
+
483
+ # Add synonyms for important terms
484
+ synonyms = self._generate_synonyms(self._extract_keywords(query))
485
+ if synonyms:
486
+ enhanced_parts.append(f"synonyms: {', '.join(synonyms[:5])}")
487
+
488
+ return " | ".join(enhanced_parts)
489
+
490
+ except Exception as e:
491
+ logger.error(f"Error enhancing query: {e}")
492
+ return query
493
+
494
+ def _build_context(self, query: str, entities: Dict[str, List[str]], query_type: str) -> Dict[str, Any]:
495
+ """Build context information for the query"""
496
+ try:
497
+ context = {
498
+ 'query_length': len(query),
499
+ 'has_entities': len(entities) > 0,
500
+ 'entity_types': list(entities.keys()),
501
+ 'query_type': query_type,
502
+ 'is_medical': any('medical' in entity_type for entity_type in entities.keys()),
503
+ 'has_amounts': 'amount' in entities,
504
+ 'has_time_periods': 'time_period' in entities
505
+ }
506
+
507
+ return context
508
+
509
+ except Exception as e:
510
+ logger.error(f"Error building context: {e}")
511
+ return {}
512
+
513
+ def _create_basic_parsed_query(self, query: str) -> ParsedQuery:
514
+ """Create a basic parsed query when parsing fails"""
515
+ return ParsedQuery(
516
+ original_query=query,
517
+ enhanced_query=query,
518
+ query_type='general_inquiry',
519
+ entities={},
520
+ intent='information_seeking',
521
+ confidence=0.0,
522
+ keywords=[],
523
+ synonyms=[],
524
+ context={},
525
+ timestamp=datetime.now()
526
+ )
527
+
528
+ def get_query_suggestions(self, query: str) -> List[str]:
529
+ """Generate query suggestions based on the input"""
530
+ try:
531
+ suggestions = []
532
+
533
+ # Basic suggestions based on query type
534
+ if 'claim' in query.lower():
535
+ suggestions.extend([
536
+ "How do I file a claim?",
537
+ "What documents are needed for claim submission?",
538
+ "What is the claim processing time?",
539
+ "Can I track my claim status?"
540
+ ])
541
+
542
+ if 'cover' in query.lower() or 'coverage' in query.lower():
543
+ suggestions.extend([
544
+ "What is covered under this policy?",
545
+ "What are the coverage limits?",
546
+ "Are pre-existing conditions covered?",
547
+ "What is not covered?"
548
+ ])
549
+
550
+ if 'medical' in query.lower() or 'health' in query.lower():
551
+ suggestions.extend([
552
+ "What medical procedures are covered?",
553
+ "Are prescription drugs covered?",
554
+ "What is the coverage for hospital stays?",
555
+ "Are specialist consultations covered?"
556
+ ])
557
+
558
+ # Add general suggestions if none specific
559
+ if not suggestions:
560
+ suggestions.extend([
561
+ "What is covered under this policy?",
562
+ "How do I file a claim?",
563
+ "What are the policy terms and conditions?",
564
+ "What documents do I need?"
565
+ ])
566
+
567
+ return suggestions[:5] # Return top 5 suggestions
568
+
569
+ except Exception as e:
570
+ logger.error(f"Error generating query suggestions: {e}")
571
+ return []
572
+
573
+ # Example usage
574
+ if __name__ == "__main__":
575
+ parser = AdvancedQueryParser()
576
+
577
+ # Test queries
578
+ test_queries = [
579
+ "Is heart surgery covered?",
580
+ "How do I file a claim?",
581
+ "What's the waiting period?",
582
+ "Can I claim for dental treatment?",
583
+ "What documents are needed?"
584
+ ]
585
+
586
+ for query in test_queries:
587
+ print(f"\n{'='*50}")
588
+ print(f"Original Query: {query}")
589
+
590
+ parsed = parser.parse_query(query)
591
+
592
+ print(f"Enhanced Query: {parsed.enhanced_query}")
593
+ print(f"Query Type: {parsed.query_type}")
594
+ print(f"Intent: {parsed.intent}")
595
+ print(f"Confidence: {parsed.confidence:.2f}")
596
+ print(f"Entities: {parsed.entities}")
597
+ print(f"Keywords: {parsed.keywords}")
598
+
599
+ suggestions = parser.get_query_suggestions(query)
600
+ print(f"Suggestions: {suggestions[:2]}")
quick_integration_test.py ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Quick Interactive Test for Integrated System
3
+ Test the complete workflow with user input
4
+ """
5
+
6
+ def quick_test():
7
+ """Quick interactive test of the integrated system"""
8
+ print("🚀 Quick Integration Test")
9
+ print("="*40)
10
+ print("Testing: Query Parser → Vector Database → LLM Reasoning")
11
+ print("="*40)
12
+
13
+ try:
14
+ # Import components
15
+ print("🔄 Loading components...")
16
+ from query_parser import AdvancedQueryParser
17
+ from vector_database import VectorDatabase
18
+ from llm_reasoning import AdvancedLLMReasoning
19
+ print("✅ Components loaded")
20
+
21
+ # Initialize components
22
+ print("🔄 Initializing...")
23
+ query_parser = AdvancedQueryParser(use_gpu=False)
24
+ vector_db = VectorDatabase(
25
+ collection_name="quick_test",
26
+ embedding_model="all-MiniLM-L6-v2",
27
+ persist_directory="./quick_test_db"
28
+ )
29
+
30
+ # Try to initialize LLM reasoning
31
+ try:
32
+ reasoning_engine = AdvancedLLMReasoning(use_gpu=False)
33
+ llm_available = True
34
+ print("✅ LLM reasoning available")
35
+ except Exception as e:
36
+ print(f"⚠️ LLM reasoning not available: {e}")
37
+ llm_available = False
38
+
39
+ # Add sample documents
40
+ print("🔄 Adding sample documents...")
41
+ sample_docs = [
42
+ {
43
+ 'content': 'Heart surgery is covered up to $50,000 with 90-day waiting period.',
44
+ 'metadata': {'source': 'policy.pdf', 'section': 'coverage'}
45
+ },
46
+ {
47
+ 'content': 'Dental treatment is covered up to $2,000 annually with 6-month waiting period.',
48
+ 'metadata': {'source': 'policy.pdf', 'section': 'dental'}
49
+ },
50
+ {
51
+ 'content': 'To file a claim, you need: claim form, medical certificate, receipts, and bills.',
52
+ 'metadata': {'source': 'claims.pdf', 'section': 'procedures'}
53
+ }
54
+ ]
55
+
56
+ for doc in sample_docs:
57
+ vector_db.add_document(doc['content'], doc['metadata'])
58
+ print(f"✅ Added {len(sample_docs)} documents")
59
+
60
+ # Interactive testing
61
+ print("\n🎯 Interactive Testing")
62
+ print("="*30)
63
+ print("Enter your queries (type 'quit' to exit):")
64
+
65
+ while True:
66
+ try:
67
+ query = input("\n❓ Your query: ").strip()
68
+
69
+ if query.lower() in ['quit', 'exit', 'q']:
70
+ break
71
+
72
+ if not query:
73
+ continue
74
+
75
+ print(f"\n🔄 Processing: {query}")
76
+ print("-" * 40)
77
+
78
+ # Step 1: Parse query
79
+ print("📝 Step 1: Parsing query...")
80
+ parsed = query_parser.parse_query(query)
81
+ print(f" Type: {parsed.query_type}")
82
+ print(f" Intent: {parsed.intent}")
83
+ print(f" Confidence: {parsed.confidence:.2f}")
84
+ if parsed.entities:
85
+ print(f" Entities: {list(parsed.entities.keys())}")
86
+
87
+ # Step 2: Search vector database
88
+ print("\n🔍 Step 2: Searching documents...")
89
+ results = vector_db.search_documents(query, n_results=2, similarity_threshold=0.3)
90
+ print(f" Found {len(results)} relevant documents")
91
+
92
+ for i, result in enumerate(results, 1):
93
+ print(f" {i}. Similarity: {result.get('similarity_score', 0):.2f}")
94
+ print(f" Source: {result.get('source_file', 'Unknown')}")
95
+ print(f" Content: {result.get('content', '')[:100]}...")
96
+
97
+ # Step 3: LLM reasoning
98
+ if llm_available and results:
99
+ print("\n🧠 Step 3: LLM reasoning...")
100
+ reasoning_result = reasoning_engine.analyze_query(
101
+ query=query,
102
+ context=results,
103
+ query_type=parsed.query_type
104
+ )
105
+
106
+ print(f" Decision: {reasoning_result.decision.upper()}")
107
+ print(f" Confidence: {reasoning_result.confidence_score:.2f}")
108
+ print(f" Justification: {reasoning_result.justification[:150]}...")
109
+
110
+ if reasoning_result.amount:
111
+ print(f" Amount: ${reasoning_result.amount:,.2f}")
112
+ if reasoning_result.waiting_period:
113
+ print(f" Waiting Period: {reasoning_result.waiting_period}")
114
+
115
+ # Show explanation
116
+ print(f"\n📋 Explanation:")
117
+ explanation = reasoning_engine.explain_decision(reasoning_result)
118
+ print(explanation)
119
+
120
+ else:
121
+ print("\n🧠 Step 3: LLM reasoning (not available)")
122
+ print(" Query parsing and document search completed successfully")
123
+
124
+ print("\n" + "="*50)
125
+
126
+ except KeyboardInterrupt:
127
+ print("\n\n👋 Goodbye!")
128
+ break
129
+ except Exception as e:
130
+ print(f"\n❌ Error processing query: {e}")
131
+
132
+ # Cleanup
133
+ print("\n🧹 Cleaning up...")
134
+ import shutil
135
+ if os.path.exists("./quick_test_db"):
136
+ shutil.rmtree("./quick_test_db")
137
+ print("✅ Cleanup completed")
138
+
139
+ print("\n🎉 Quick test completed!")
140
+
141
+ except Exception as e:
142
+ print(f"❌ Quick test failed: {e}")
143
+ import traceback
144
+ traceback.print_exc()
145
+
146
+ def test_specific_query(query_text):
147
+ """Test a specific query"""
148
+ print(f"🧪 Testing specific query: {query_text}")
149
+ print("="*50)
150
+
151
+ try:
152
+ from query_parser import AdvancedQueryParser
153
+ from vector_database import VectorDatabase
154
+ from llm_reasoning import AdvancedLLMReasoning
155
+
156
+ # Initialize
157
+ query_parser = AdvancedQueryParser(use_gpu=False)
158
+ vector_db = VectorDatabase(
159
+ collection_name="specific_test",
160
+ embedding_model="all-MiniLM-L6-v2",
161
+ persist_directory="./specific_test_db"
162
+ )
163
+
164
+ # Add test document
165
+ vector_db.add_document(
166
+ "Heart surgery is covered up to $50,000 with 90-day waiting period.",
167
+ {'source': 'test.pdf', 'type': 'coverage'}
168
+ )
169
+
170
+ # Process query
171
+ parsed = query_parser.parse_query(query_text)
172
+ results = vector_db.search_documents(query_text, n_results=1)
173
+
174
+ print(f"Query Type: {parsed.query_type}")
175
+ print(f"Confidence: {parsed.confidence:.2f}")
176
+ print(f"Search Results: {len(results)}")
177
+
178
+ if results:
179
+ print(f"Best Match: {results[0].get('content', '')[:100]}...")
180
+
181
+ # Try LLM reasoning
182
+ try:
183
+ reasoning_engine = AdvancedLLMReasoning(use_gpu=False)
184
+ reasoning_result = reasoning_engine.analyze_query(
185
+ query_text, results, parsed.query_type
186
+ )
187
+ print(f"LLM Decision: {reasoning_result.decision}")
188
+ print(f"LLM Confidence: {reasoning_result.confidence_score:.2f}")
189
+ except Exception as e:
190
+ print(f"LLM Reasoning failed: {e}")
191
+
192
+ # Cleanup
193
+ import shutil
194
+ if os.path.exists("./specific_test_db"):
195
+ shutil.rmtree("./specific_test_db")
196
+
197
+ except Exception as e:
198
+ print(f"❌ Test failed: {e}")
199
+
200
+ if __name__ == "__main__":
201
+ import os
202
+ import sys
203
+
204
+ # Check if specific query provided
205
+ if len(sys.argv) > 1:
206
+ query = " ".join(sys.argv[1:])
207
+ test_specific_query(query)
208
+ else:
209
+ quick_test()
quick_test.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Quick Test for app2.py Server
4
+ """
5
+
6
+ import requests
7
+ import time
8
+
9
+ BASE_URL = "https://0468c638cef7.ngrok-free.app"
10
+
11
+ def test_server():
12
+ print("🧪 Quick Server Test")
13
+ print("=" * 40)
14
+
15
+ # Test 1: Health Check
16
+ print("1. Testing health endpoint...")
17
+ try:
18
+ response = requests.get(f"{BASE_URL}/api/health", timeout=10)
19
+ print(f" Status: {response.status_code}")
20
+ if response.status_code == 200:
21
+ print(" ✅ Server is running!")
22
+ else:
23
+ print(f" ❌ Server error: {response.text}")
24
+ except Exception as e:
25
+ print(f" ❌ Connection failed: {e}")
26
+ print(" 💡 Make sure to run: python app2.py")
27
+ return False
28
+
29
+ # Test 2: Simple Query
30
+ print("\n2. Testing simple query...")
31
+ try:
32
+ headers = {
33
+ "Content-Type": "application/json",
34
+ "Authorization": "Bearer test_key_123"
35
+ }
36
+ payload = {
37
+ "questions": ["What is the grace period for premium payment?"]
38
+ }
39
+
40
+ response = requests.post(f"{BASE_URL}/hackrx/run",
41
+ json=payload, headers=headers, timeout=30)
42
+ print(f" Status: {response.status_code}")
43
+ if response.status_code == 200:
44
+ print(" ✅ Query processed successfully!")
45
+ result = response.json()
46
+ print(f" Answer: {result.get('answers', [''])[0][:100]}...")
47
+ else:
48
+ print(f" ❌ Query failed: {response.text}")
49
+ except Exception as e:
50
+ print(f" ❌ Query error: {e}")
51
+
52
+ print("\n" + "=" * 40)
53
+ print("📋 NEXT STEPS:")
54
+ print("1. Make sure app2.py is running: python app2.py")
55
+ print("2. Keep the server running in a separate terminal")
56
+ print("3. Test your Postman requests")
57
+ print("=" * 40)
58
+
59
+ if __name__ == "__main__":
60
+ test_server()
rag_system.py ADDED
@@ -0,0 +1,513 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Main RAG System - Orchestrates All Components
3
+ Integrates document processing, vector database, query parsing, and LLM reasoning
4
+ """
5
+
6
+ import os
7
+ import json
8
+ import logging
9
+ import time
10
+ from datetime import datetime
11
+ from typing import List, Dict, Any, Optional, Tuple
12
+ from dataclasses import dataclass, asdict
13
+ from pathlib import Path
14
+
15
+ # Import our custom components
16
+ from document_processer import AdvancedDocumentProcessor, DocumentChunk
17
+ from vector_database import VectorDatabase, SearchResult
18
+ from query_parser import AdvancedQueryParser, ParsedQuery
19
+ from llm_reasoning import AdvancedLLMReasoning, ReasoningResult
20
+
21
+ # Configure logging
22
+ logging.basicConfig(level=logging.INFO)
23
+ logger = logging.getLogger(__name__)
24
+
25
+ @dataclass
26
+ class QueryResult:
27
+ """Represents the complete result of a query processing"""
28
+ query: str
29
+ parsed_query: ParsedQuery
30
+ search_results: List[SearchResult]
31
+ reasoning_result: ReasoningResult
32
+ processing_time: float
33
+ timestamp: datetime
34
+ audit_trail: Dict[str, Any]
35
+
36
+ class AdvancedRAGSystem:
37
+ """Advanced RAG system that orchestrates all components"""
38
+
39
+ def __init__(self,
40
+ model_path: str = "./mistral-7b-instruct-v0.1.Q4_K_M.gguf",
41
+ use_gpu: bool = True,
42
+ vector_db_path: str = "./vector_db"):
43
+
44
+ self.model_path = model_path
45
+ self.use_gpu = use_gpu
46
+ self.vector_db_path = vector_db_path
47
+
48
+ # Initialize components
49
+ self._initialize_components()
50
+
51
+ # Audit trail storage
52
+ self.audit_log = []
53
+
54
+ logger.info("Advanced RAG System initialized successfully")
55
+
56
+ def _initialize_components(self):
57
+ """Initialize all system components"""
58
+ try:
59
+ # Initialize document processor
60
+ self.document_processor = AdvancedDocumentProcessor(
61
+ ocr_language='eng',
62
+ chunk_size=1000,
63
+ chunk_overlap=200
64
+ )
65
+
66
+ # Initialize vector database
67
+ self.vector_database = VectorDatabase(
68
+ embedding_model="all-MiniLM-L6-v2",
69
+ collection_name="documents",
70
+ persist_directory=self.vector_db_path,
71
+ use_gpu=self.use_gpu
72
+ )
73
+
74
+ # Initialize query parser (using NLTK instead of spaCy)
75
+ self.query_parser = AdvancedQueryParser(
76
+ use_gpu=self.use_gpu
77
+ )
78
+
79
+ # Initialize LLM reasoning engine
80
+ self.reasoning_engine = AdvancedLLMReasoning(
81
+ model_path=self.model_path,
82
+ use_gpu=self.use_gpu,
83
+ max_tokens=2048
84
+ )
85
+
86
+ logger.info("All components initialized successfully")
87
+
88
+ except Exception as e:
89
+ logger.error(f"Error initializing components: {e}")
90
+ raise
91
+
92
+ def ingest_document(self, file_path: str, use_ocr: bool = False) -> List[DocumentChunk]:
93
+ """Ingest and process a document"""
94
+ try:
95
+ logger.info(f"Starting document ingestion: {file_path}")
96
+
97
+ # Process document
98
+ chunks = self.document_processor.process_document(file_path, use_ocr)
99
+ logger.info(f"Document processor created {len(chunks)} chunks")
100
+
101
+ if not chunks:
102
+ logger.warning("No chunks created by document processor")
103
+ return []
104
+
105
+ # Add to vector database
106
+ logger.info(f"Adding {len(chunks)} chunks to vector database...")
107
+ success = self.vector_database.add_documents(chunks)
108
+ logger.info(f"Vector database add_documents returned: {success}")
109
+
110
+ if success:
111
+ logger.info(f"Successfully ingested {len(chunks)} chunks from {file_path}")
112
+
113
+ # Add to audit trail
114
+ self._add_audit_entry({
115
+ 'action': 'document_ingestion',
116
+ 'file_path': file_path,
117
+ 'chunks_processed': len(chunks),
118
+ 'use_ocr': use_ocr,
119
+ 'timestamp': datetime.now().isoformat(),
120
+ 'status': 'success'
121
+ })
122
+
123
+ return chunks
124
+ else:
125
+ logger.error(f"Failed to add documents to vector database, but returning chunks anyway")
126
+ # Return chunks even if vector database fails, so the user can still see the processing worked
127
+ return chunks
128
+
129
+ except Exception as e:
130
+ logger.error(f"Error ingesting document {file_path}: {e}")
131
+
132
+ # Add error to audit trail
133
+ self._add_audit_entry({
134
+ 'action': 'document_ingestion',
135
+ 'file_path': file_path,
136
+ 'error': str(e),
137
+ 'timestamp': datetime.now().isoformat(),
138
+ 'status': 'error'
139
+ })
140
+
141
+ raise
142
+
143
+ def process_query(self, query: str, n_results: int = 5) -> QueryResult:
144
+ """Process a natural language query"""
145
+ try:
146
+ start_time = time.time()
147
+
148
+ logger.info(f"Processing query: {query}")
149
+
150
+ # Step 1: Parse the query
151
+ parsed_query = self.query_parser.parse_query(query)
152
+
153
+ # Step 2: Search for relevant documents
154
+ search_results = self.vector_database.hybrid_search(
155
+ query=parsed_query.enhanced_query,
156
+ n_results=n_results,
157
+ semantic_weight=0.7,
158
+ keyword_weight=0.3
159
+ )
160
+
161
+ # Step 3: Prepare context for reasoning
162
+ context = self._prepare_context_for_reasoning(search_results)
163
+
164
+ # Step 4: Analyze with LLM reasoning
165
+ reasoning_result = self.reasoning_engine.analyze_query(
166
+ query=query,
167
+ context=context,
168
+ query_type=parsed_query.query_type
169
+ )
170
+
171
+ processing_time = time.time() - start_time
172
+
173
+ # Step 5: Create audit trail
174
+ audit_trail = self._create_audit_trail(
175
+ query, parsed_query, search_results, reasoning_result, processing_time
176
+ )
177
+
178
+ # Step 6: Build result
179
+ result = QueryResult(
180
+ query=query,
181
+ parsed_query=parsed_query,
182
+ search_results=search_results,
183
+ reasoning_result=reasoning_result,
184
+ processing_time=processing_time,
185
+ timestamp=datetime.now(),
186
+ audit_trail=audit_trail
187
+ )
188
+
189
+ # Add to audit log
190
+ self._add_audit_entry(audit_trail)
191
+
192
+ logger.info(f"Query processed successfully in {processing_time:.2f}s")
193
+ return result
194
+
195
+ except Exception as e:
196
+ logger.error(f"Error processing query: {e}")
197
+
198
+ # Create fallback result
199
+ return self._create_fallback_result(query, str(e))
200
+
201
+ def _prepare_context_for_reasoning(self, search_results: List[SearchResult]) -> List[Dict[str, Any]]:
202
+ """Prepare search results for LLM reasoning"""
203
+ try:
204
+ context = []
205
+
206
+ for result in search_results:
207
+ context_item = {
208
+ 'content': result.content,
209
+ 'source_file': result.source_file,
210
+ 'similarity_score': result.similarity_score,
211
+ 'section_type': result.section_type,
212
+ 'metadata': result.metadata
213
+ }
214
+
215
+ # Add table data if present
216
+ if result.table_data:
217
+ context_item['table_data'] = result.table_data
218
+
219
+ context.append(context_item)
220
+
221
+ return context
222
+
223
+ except Exception as e:
224
+ logger.error(f"Error preparing context: {e}")
225
+ return []
226
+
227
+ def _create_audit_trail(self,
228
+ query: str,
229
+ parsed_query: ParsedQuery,
230
+ search_results: List[SearchResult],
231
+ reasoning_result: ReasoningResult,
232
+ processing_time: float) -> Dict[str, Any]:
233
+ """Create comprehensive audit trail"""
234
+ try:
235
+ audit_trail = {
236
+ 'action': 'query_processing',
237
+ 'query': query,
238
+ 'parsed_query': {
239
+ 'query_type': parsed_query.query_type,
240
+ 'intent': parsed_query.intent,
241
+ 'confidence': parsed_query.confidence,
242
+ 'entities': parsed_query.entities,
243
+ 'keywords': parsed_query.keywords
244
+ },
245
+ 'search_results': {
246
+ 'count': len(search_results),
247
+ 'top_results': [
248
+ {
249
+ 'content_preview': result.content[:100] + "...",
250
+ 'source_file': result.source_file,
251
+ 'similarity_score': result.similarity_score,
252
+ 'section_type': result.section_type
253
+ }
254
+ for result in search_results[:3]
255
+ ]
256
+ },
257
+ 'reasoning_result': {
258
+ 'decision': reasoning_result.decision,
259
+ 'confidence_score': reasoning_result.confidence_score,
260
+ 'relevant_clauses': reasoning_result.relevant_clauses,
261
+ 'amount': reasoning_result.amount,
262
+ 'waiting_period': reasoning_result.waiting_period
263
+ },
264
+ 'processing_time': processing_time,
265
+ 'timestamp': datetime.now().isoformat(),
266
+ 'status': 'success'
267
+ }
268
+
269
+ return audit_trail
270
+
271
+ except Exception as e:
272
+ logger.error(f"Error creating audit trail: {e}")
273
+ return {
274
+ 'action': 'query_processing',
275
+ 'query': query,
276
+ 'error': str(e),
277
+ 'timestamp': datetime.now().isoformat(),
278
+ 'status': 'error'
279
+ }
280
+
281
+ def _create_fallback_result(self, query: str, error: str) -> QueryResult:
282
+ """Create a fallback result when processing fails"""
283
+ try:
284
+ # Create basic parsed query
285
+ parsed_query = ParsedQuery(
286
+ original_query=query,
287
+ enhanced_query=query,
288
+ query_type='general_inquiry',
289
+ entities={},
290
+ intent='information_seeking',
291
+ confidence=0.0,
292
+ keywords=[],
293
+ synonyms=[],
294
+ context={},
295
+ timestamp=datetime.now()
296
+ )
297
+
298
+ # Create fallback reasoning result
299
+ reasoning_result = ReasoningResult(
300
+ decision='pending',
301
+ confidence_score=0.0,
302
+ justification=f'Processing failed: {error}',
303
+ relevant_clauses=[],
304
+ reasoning_steps=['Processing failed'],
305
+ source_references=[]
306
+ )
307
+
308
+ return QueryResult(
309
+ query=query,
310
+ parsed_query=parsed_query,
311
+ search_results=[],
312
+ reasoning_result=reasoning_result,
313
+ processing_time=0.0,
314
+ timestamp=datetime.now(),
315
+ audit_trail={
316
+ 'action': 'query_processing',
317
+ 'query': query,
318
+ 'error': error,
319
+ 'timestamp': datetime.now().isoformat(),
320
+ 'status': 'error'
321
+ }
322
+ )
323
+
324
+ except Exception as e:
325
+ logger.error(f"Error creating fallback result: {e}")
326
+ raise
327
+
328
+ def _add_audit_entry(self, entry: Dict[str, Any]):
329
+ """Add entry to audit log"""
330
+ try:
331
+ self.audit_log.append(entry)
332
+
333
+ # Keep audit log size manageable
334
+ if len(self.audit_log) > 1000:
335
+ self.audit_log = self.audit_log[-500:]
336
+
337
+ except Exception as e:
338
+ logger.error(f"Error adding audit entry: {e}")
339
+
340
+ def get_audit_trail(self) -> List[Dict[str, Any]]:
341
+ """Get the complete audit trail"""
342
+ return self.audit_log.copy()
343
+
344
+ def save_audit_trail(self, file_path: str) -> bool:
345
+ """Save audit trail to file"""
346
+ try:
347
+ with open(file_path, 'w') as f:
348
+ json.dump(self.audit_log, f, indent=2)
349
+
350
+ logger.info(f"Audit trail saved to: {file_path}")
351
+ return True
352
+
353
+ except Exception as e:
354
+ logger.error(f"Error saving audit trail: {e}")
355
+ return False
356
+
357
+ def get_system_statistics(self) -> Dict[str, Any]:
358
+ """Get comprehensive system statistics"""
359
+ try:
360
+ # Get vector database statistics
361
+ db_stats = self.vector_database.get_document_statistics()
362
+
363
+ # Get audit trail statistics
364
+ audit_stats = {
365
+ 'total_entries': len(self.audit_log),
366
+ 'successful_queries': len([e for e in self.audit_log if e.get('status') == 'success']),
367
+ 'failed_queries': len([e for e in self.audit_log if e.get('status') == 'error']),
368
+ 'document_ingestions': len([e for e in self.audit_log if e.get('action') == 'document_ingestion']),
369
+ 'query_processings': len([e for e in self.audit_log if e.get('action') == 'query_processing'])
370
+ }
371
+
372
+ # Get component information
373
+ component_info = {
374
+ 'document_processor': 'AdvancedDocumentProcessor',
375
+ 'vector_database': 'AdvancedVectorDatabase',
376
+ 'query_parser': 'AdvancedQueryParser',
377
+ 'reasoning_engine': 'AdvancedLLMReasoning',
378
+ 'model_path': self.model_path,
379
+ 'use_gpu': self.use_gpu
380
+ }
381
+
382
+ stats = {
383
+ 'vector_database': db_stats,
384
+ 'audit_trail': audit_stats,
385
+ 'components': component_info,
386
+ 'timestamp': datetime.now().isoformat()
387
+ }
388
+
389
+ return stats
390
+
391
+ except Exception as e:
392
+ logger.error(f"Error getting system statistics: {e}")
393
+ return {}
394
+
395
+ def clear_system(self) -> bool:
396
+ """Clear all data from the system"""
397
+ try:
398
+ # Clear vector database
399
+ self.vector_database.clear_database()
400
+
401
+ # Clear audit log
402
+ self.audit_log = []
403
+
404
+ logger.info("System cleared successfully")
405
+ return True
406
+
407
+ except Exception as e:
408
+ logger.error(f"Error clearing system: {e}")
409
+ return False
410
+
411
+ def export_system_data(self, export_path: str) -> bool:
412
+ """Export system data for backup or analysis"""
413
+ try:
414
+ # Get system statistics
415
+ stats = self.get_system_statistics()
416
+
417
+ # Add audit trail
418
+ export_data = {
419
+ 'statistics': stats,
420
+ 'audit_trail': self.audit_log,
421
+ 'export_timestamp': datetime.now().isoformat()
422
+ }
423
+
424
+ with open(export_path, 'w') as f:
425
+ json.dump(export_data, f, indent=2)
426
+
427
+ logger.info(f"System data exported to: {export_path}")
428
+ return True
429
+
430
+ except Exception as e:
431
+ logger.error(f"Error exporting system data: {e}")
432
+ return False
433
+
434
+ def validate_system(self) -> Dict[str, Any]:
435
+ """Validate system components and return status"""
436
+ try:
437
+ validation_results = {
438
+ 'document_processor': True,
439
+ 'vector_database': True,
440
+ 'query_parser': True,
441
+ 'reasoning_engine': True,
442
+ 'overall_status': True,
443
+ 'errors': []
444
+ }
445
+
446
+ # Test document processor
447
+ try:
448
+ # This is a basic test - in practice you might want more comprehensive tests
449
+ pass
450
+ except Exception as e:
451
+ validation_results['document_processor'] = False
452
+ validation_results['errors'].append(f"Document processor: {e}")
453
+
454
+ # Test vector database
455
+ try:
456
+ stats = self.vector_database.get_document_statistics()
457
+ except Exception as e:
458
+ validation_results['vector_database'] = False
459
+ validation_results['errors'].append(f"Vector database: {e}")
460
+
461
+ # Test query parser
462
+ try:
463
+ test_parsed = self.query_parser.parse_query("test query")
464
+ except Exception as e:
465
+ validation_results['query_parser'] = False
466
+ validation_results['errors'].append(f"Query parser: {e}")
467
+
468
+ # Test reasoning engine
469
+ try:
470
+ # Basic test - check if model file exists
471
+ if not os.path.exists(self.model_path):
472
+ validation_results['reasoning_engine'] = False
473
+ validation_results['errors'].append("LLM model file not found")
474
+ except Exception as e:
475
+ validation_results['reasoning_engine'] = False
476
+ validation_results['errors'].append(f"Reasoning engine: {e}")
477
+
478
+ # Overall status
479
+ validation_results['overall_status'] = all([
480
+ validation_results['document_processor'],
481
+ validation_results['vector_database'],
482
+ validation_results['query_parser'],
483
+ validation_results['reasoning_engine']
484
+ ])
485
+
486
+ return validation_results
487
+
488
+ except Exception as e:
489
+ logger.error(f"Error validating system: {e}")
490
+ return {
491
+ 'overall_status': False,
492
+ 'errors': [f"Validation failed: {e}"]
493
+ }
494
+
495
+ # Example usage
496
+ if __name__ == "__main__":
497
+ # Initialize RAG system
498
+ rag_system = AdvancedRAGSystem(use_gpu=True)
499
+
500
+ # Test system validation
501
+ validation = rag_system.validate_system()
502
+ print(f"System validation: {validation['overall_status']}")
503
+
504
+ if validation['overall_status']:
505
+ print("✅ All components are working correctly")
506
+ else:
507
+ print("❌ Some components have issues:")
508
+ for error in validation['errors']:
509
+ print(f" - {error}")
510
+
511
+ # Get system statistics
512
+ stats = rag_system.get_system_statistics()
513
+ print(f"\nSystem statistics: {stats}")
rag_system_gpp.py ADDED
@@ -0,0 +1,633 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ GPU-Optimized RAG System for High Performance
3
+ """
4
+
5
+ import os
6
+ import json
7
+ import logging
8
+ import hashlib
9
+ from datetime import datetime
10
+ from typing import List, Dict, Any, Optional, Tuple
11
+ from dataclasses import dataclass, asdict
12
+ from pathlib import Path
13
+
14
+ import fitz # PyMuPDF
15
+ import pytesseract
16
+ from pdf2image import convert_from_path
17
+ from PIL import Image
18
+ import cv2
19
+ import numpy as np
20
+ from docx import Document
21
+ from bs4 import BeautifulSoup
22
+ import requests
23
+
24
+ from sentence_transformers import SentenceTransformer
25
+ import chromadb
26
+ from chromadb.config import Settings
27
+
28
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
29
+ from langchain.schema import Document
30
+ from langchain_huggingface import HuggingFaceEmbeddings
31
+ from langchain_chroma import Chroma
32
+ from langchain.retrievers import ContextualCompressionRetriever
33
+ from langchain.retrievers.document_compressors import LLMChainExtractor
34
+
35
+ # Disable ChromaDB telemetry
36
+ os.environ["ANONYMIZED_TELEMETRY"] = "False"
37
+
38
+ # Configure logging
39
+ logging.basicConfig(level=logging.INFO)
40
+ logger = logging.getLogger(__name__)
41
+
42
+ @dataclass
43
+ class DocumentChunk:
44
+ """Represents a chunk of processed document with metadata"""
45
+ chunk_id: str
46
+ content: str
47
+ source_file: str
48
+ page_number: Optional[int] = None
49
+ chunk_index: Optional[int] = None
50
+ category: Optional[str] = None
51
+ embedding: Optional[List[float]] = None
52
+
53
+ @dataclass
54
+ class QueryResult:
55
+ """Structured result from query processing"""
56
+ decision: str # approved/rejected/conditional
57
+ justification: str
58
+ relevant_clauses: List[str]
59
+ confidence_score: float
60
+ audit_trail: Dict[str, Any]
61
+ amount: Optional[float] = None
62
+
63
+ class DocumentProcessor:
64
+ """Handles document ingestion and preprocessing with OCR support"""
65
+
66
+ def __init__(self, ocr_language='eng'):
67
+ self.ocr_language = ocr_language
68
+ self.text_splitter = RecursiveCharacterTextSplitter(
69
+ chunk_size=1000,
70
+ chunk_overlap=200,
71
+ separators=["\n\n", "\n", ". ", " ", ""]
72
+ )
73
+
74
+ def extract_text_from_pdf(self, pdf_path: str, use_ocr: bool = False) -> str:
75
+ """Extract text from PDF with optional OCR for scanned documents"""
76
+ try:
77
+ doc = fitz.open(pdf_path)
78
+ text = ""
79
+
80
+ for page_num in range(len(doc)):
81
+ page = doc.load_page(page_num)
82
+
83
+ # Try to extract text normally first
84
+ page_text = page.get_text()
85
+
86
+ # If no text found or very little text, use OCR
87
+ if use_ocr or len(page_text.strip()) < 50:
88
+ logger.info(f"Using OCR for page {page_num + 1}")
89
+ try:
90
+ pix = page.get_pixmap()
91
+ img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
92
+
93
+ # Convert to grayscale for better OCR
94
+ img_gray = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2GRAY)
95
+
96
+ # Apply preprocessing for better OCR
97
+ img_processed = cv2.threshold(img_gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
98
+
99
+ # Extract text using OCR
100
+ page_text = pytesseract.image_to_string(
101
+ img_processed,
102
+ lang=self.ocr_language,
103
+ config='--psm 6'
104
+ )
105
+ except Exception as ocr_error:
106
+ logger.warning(f"OCR failed for page {page_num + 1}: {ocr_error}")
107
+ logger.warning("Continuing with existing text extraction")
108
+ # Keep the existing page_text (from normal extraction)
109
+
110
+ text += f"\n\n--- Page {page_num + 1} ---\n{page_text}"
111
+
112
+ doc.close()
113
+ return text
114
+
115
+ except Exception as e:
116
+ logger.error(f"Error extracting text from PDF {pdf_path}: {e}")
117
+ raise
118
+
119
+ def chunk_document(self, text: str, source_file: str) -> List[DocumentChunk]:
120
+ """Chunk document into semantically coherent passages"""
121
+ try:
122
+ # Use LangChain's text splitter for better semantic chunking
123
+ docs = [Document(page_content=text, metadata={"source": source_file})]
124
+ split_docs = self.text_splitter.split_documents(docs)
125
+
126
+ chunks = []
127
+ for i, doc in enumerate(split_docs):
128
+ chunk = DocumentChunk(
129
+ chunk_id=f"chunk_{i+1}_{hashlib.md5(doc.page_content.encode()).hexdigest()[:8]}",
130
+ content=doc.page_content.strip(),
131
+ source_file=source_file,
132
+ chunk_index=i
133
+ )
134
+ chunks.append(chunk)
135
+
136
+ return chunks
137
+
138
+ except Exception as e:
139
+ logger.error(f"Error chunking document: {e}")
140
+ raise
141
+
142
+ class VectorDatabase:
143
+ """Manages vector storage and retrieval with GPU optimization"""
144
+
145
+ def _clean_metadata(self, metadata_dict: Dict[str, Any]) -> Dict[str, Any]:
146
+ """Clean metadata by removing None values and converting to proper types"""
147
+ clean_metadata = {}
148
+ for key, value in metadata_dict.items():
149
+ if value is not None:
150
+ if isinstance(value, (int, float)):
151
+ clean_metadata[key] = value
152
+ else:
153
+ clean_metadata[key] = str(value)
154
+ return clean_metadata
155
+
156
+ def __init__(self, persist_directory: str = "./vector_db", use_gpu: bool = True):
157
+ self.persist_directory = persist_directory
158
+
159
+ # GPU-optimized embeddings
160
+ device = 'cuda' if use_gpu else 'cpu'
161
+ self.embeddings = HuggingFaceEmbeddings(
162
+ model_name="all-MiniLM-L6-v2",
163
+ model_kwargs={'device': device}
164
+ )
165
+
166
+ # Initialize ChromaDB
167
+ import chromadb
168
+ from chromadb.config import Settings
169
+
170
+ # Create ChromaDB client with proper settings
171
+ client = chromadb.PersistentClient(
172
+ path=persist_directory,
173
+ settings=Settings(
174
+ anonymized_telemetry=False,
175
+ is_persistent=True
176
+ )
177
+ )
178
+
179
+ # Create collection for documents
180
+ collection_name = "insurance_documents"
181
+ try:
182
+ self.collection = client.get_collection(collection_name)
183
+ except:
184
+ self.collection = client.create_collection(collection_name)
185
+
186
+ self.vectorstore = Chroma(
187
+ embedding_function=self.embeddings,
188
+ persist_directory=persist_directory,
189
+ client=client,
190
+ collection_name=collection_name
191
+ )
192
+
193
+ def add_documents(self, chunks: List[DocumentChunk]) -> None:
194
+ """Add document chunks to vector database"""
195
+ try:
196
+ documents = []
197
+ metadatas = []
198
+ ids = []
199
+
200
+ for chunk in chunks:
201
+ documents.append(chunk.content)
202
+
203
+ # Create metadata dictionary
204
+ metadata = {
205
+ "chunk_id": chunk.chunk_id,
206
+ "source_file": chunk.source_file,
207
+ "page_number": chunk.page_number,
208
+ "chunk_index": chunk.chunk_index,
209
+ "category": chunk.category
210
+ }
211
+
212
+ # Clean metadata using helper function
213
+ clean_metadata = self._clean_metadata(metadata)
214
+ metadatas.append(clean_metadata)
215
+ ids.append(chunk.chunk_id)
216
+
217
+ # Get embeddings for documents (GPU-accelerated)
218
+ embeddings = self.embeddings.embed_documents(documents)
219
+
220
+ # Add to collection
221
+ self.collection.add(
222
+ documents=documents,
223
+ metadatas=metadatas,
224
+ embeddings=embeddings,
225
+ ids=ids
226
+ )
227
+
228
+ logger.info(f"Added {len(chunks)} chunks to vector database")
229
+
230
+ except Exception as e:
231
+ logger.error(f"Error adding documents to vector database: {e}")
232
+ raise
233
+
234
+ def search(self, query: str, k: int = 5) -> List[Dict[str, Any]]:
235
+ """Search for relevant document chunks"""
236
+ try:
237
+ # Get query embedding (GPU-accelerated)
238
+ query_embedding = self.embeddings.embed_query(query)
239
+
240
+ # Search in collection
241
+ results = self.collection.query(
242
+ query_embeddings=[query_embedding],
243
+ n_results=k,
244
+ include=["documents", "metadatas", "distances"]
245
+ )
246
+
247
+ search_results = []
248
+ if results['documents'] and results['documents'][0]:
249
+ for i, (doc, metadata, distance) in enumerate(zip(
250
+ results['documents'][0],
251
+ results['metadatas'][0],
252
+ results['distances'][0]
253
+ )):
254
+ # Clean metadata using helper function
255
+ clean_metadata = self._clean_metadata(metadata)
256
+
257
+ search_results.append({
258
+ "content": doc,
259
+ "metadata": clean_metadata,
260
+ "similarity_score": 1.0 - float(distance) # Convert distance to similarity
261
+ })
262
+
263
+ return search_results
264
+
265
+ except Exception as e:
266
+ logger.error(f"Error searching vector database: {e}")
267
+ raise
268
+
269
+ class QueryParser:
270
+ """Parses and structures natural language queries"""
271
+
272
+ def __init__(self, llm_model):
273
+ self.llm = llm_model
274
+
275
+ def extract_entities(self, query: str) -> Dict[str, Any]:
276
+ """Extract structured entities from natural language query"""
277
+ try:
278
+ prompt = f"""
279
+ Extract structured information from the following query about insurance/policy:
280
+
281
+ Query: {query}
282
+
283
+ Extract the following information in JSON format:
284
+ {{
285
+ "age": <age if mentioned>,
286
+ "procedure": <medical procedure if mentioned>,
287
+ "location": <location if mentioned>,
288
+ "policy_type": <type of policy mentioned>,
289
+ "claim_amount": <amount if mentioned>,
290
+ "condition": <medical condition if mentioned>,
291
+ "intent": <what the user is asking about>
292
+ }}
293
+
294
+ JSON Response:
295
+ """
296
+
297
+ # Call LLM with proper format
298
+ if hasattr(self.llm, 'generate_content'): # Gemini
299
+ response = self.llm.generate_content(prompt).text
300
+ else: # Llama
301
+ response = self.llm(prompt, max_tokens=200, temperature=0.1, stop=["\n\n"])
302
+ if isinstance(response, dict):
303
+ response = response.get('choices', [{}])[0].get('text', '')
304
+ elif hasattr(response, 'choices'):
305
+ response = response.choices[0].text
306
+
307
+ # Parse response - handle both string and dict responses
308
+ try:
309
+ if isinstance(response, dict):
310
+ entities = response
311
+ else:
312
+ entities = json.loads(response)
313
+ return entities
314
+ except (json.JSONDecodeError, TypeError):
315
+ # Fallback parsing
316
+ return self._fallback_entity_extraction(query)
317
+
318
+ except Exception as e:
319
+ logger.error(f"Error extracting entities: {e}")
320
+ return self._fallback_entity_extraction(query)
321
+
322
+ def _fallback_entity_extraction(self, query: str) -> Dict[str, Any]:
323
+ """Simple fallback entity extraction"""
324
+ entities = {
325
+ "age": None,
326
+ "procedure": None,
327
+ "location": None,
328
+ "policy_type": None,
329
+ "claim_amount": None,
330
+ "condition": None,
331
+ "intent": "general_inquiry"
332
+ }
333
+
334
+ # Simple keyword-based extraction
335
+ query_lower = query.lower()
336
+
337
+ # Extract age
338
+ import re
339
+ age_match = re.search(r'(\d+)\s*(?:years?|yrs?)', query_lower)
340
+ if age_match:
341
+ entities["age"] = int(age_match.group(1))
342
+
343
+ # Extract amount
344
+ amount_match = re.search(r'(\d+(?:,\d+)*(?:\.\d+)?)\s*(?:rs?|rupees?|inr)', query_lower)
345
+ if amount_match:
346
+ entities["claim_amount"] = float(amount_match.group(1).replace(',', ''))
347
+
348
+ return entities
349
+
350
+ class LLMReasoning:
351
+ """Handles LLM-based reasoning and decision logic"""
352
+
353
+ def __init__(self, llm_model):
354
+ self.llm = llm_model
355
+
356
+ def analyze_query(self, query: str, relevant_chunks: List[Dict], parsed_entities: Dict) -> QueryResult:
357
+ """Analyze query against relevant document chunks"""
358
+ try:
359
+ # Prepare context from relevant chunks
360
+ context = "\n\n".join([
361
+ f"Document {i+1}:\n{chunk['content']}\nClause ID: {chunk['metadata'].get('chunk_id', 'N/A')}"
362
+ for i, chunk in enumerate(relevant_chunks)
363
+ ])
364
+
365
+ # Create reasoning prompt
366
+ prompt = f"""
367
+ You are an insurance policy analyzer. Analyze the following query against the provided policy documents.
368
+
369
+ User Query: {query}
370
+ Extracted Entities: {json.dumps(parsed_entities, indent=2)}
371
+
372
+ Relevant Policy Clauses:
373
+ {context}
374
+
375
+ Please provide a structured analysis in the following JSON format:
376
+ {{
377
+ "decision": "approved/rejected/conditional",
378
+ "amount": <amount if applicable, null otherwise>,
379
+ "justification": "<detailed explanation with specific clause references>",
380
+ "relevant_clauses": ["<list of clause IDs that support the decision>"],
381
+ "confidence_score": <0.0 to 1.0>,
382
+ "conditions": ["<any conditions that must be met>"]
383
+ }}
384
+
385
+ Base your decision on:
386
+ 1. Policy coverage and exclusions
387
+ 2. Eligibility criteria
388
+ 3. Waiting periods
389
+ 4. Pre-existing conditions
390
+ 5. Specific terms and conditions
391
+
392
+ JSON Response:
393
+ """
394
+
395
+ # Call LLM with proper format
396
+ if hasattr(self.llm, 'generate_content'): # Gemini
397
+ response = self.llm.generate_content(prompt).text
398
+ else: # Llama
399
+ response = self.llm(prompt, max_tokens=500, temperature=0.1, stop=["\n\n"])
400
+ if isinstance(response, dict):
401
+ response = response.get('choices', [{}])[0].get('text', '')
402
+ elif hasattr(response, 'choices'):
403
+ response = response.choices[0].text
404
+
405
+ # Parse response - handle both string and dict responses
406
+ try:
407
+ if isinstance(response, dict):
408
+ result_data = response
409
+ else:
410
+ result_data = json.loads(response)
411
+
412
+ # Create audit trail
413
+ audit_trail = {
414
+ "timestamp": datetime.now().isoformat(),
415
+ "query": query,
416
+ "parsed_entities": parsed_entities,
417
+ "relevant_chunks_count": len(relevant_chunks),
418
+ "llm_prompt": prompt,
419
+ "llm_response": response,
420
+ "chunk_ids": [chunk['metadata'].get('chunk_id') for chunk in relevant_chunks]
421
+ }
422
+
423
+ return QueryResult(
424
+ decision=result_data.get("decision", "conditional"),
425
+ amount=result_data.get("amount"),
426
+ justification=result_data.get("justification", "Analysis incomplete"),
427
+ relevant_clauses=result_data.get("relevant_clauses", []),
428
+ confidence_score=result_data.get("confidence_score", 0.5),
429
+ audit_trail=audit_trail
430
+ )
431
+
432
+ except (json.JSONDecodeError, TypeError, AttributeError) as e:
433
+ # Fallback response
434
+ return QueryResult(
435
+ decision="conditional",
436
+ amount=None,
437
+ justification=f"Unable to parse LLM response: {str(e)}. Raw response: {str(response)[:200]}...",
438
+ relevant_clauses=[],
439
+ confidence_score=0.3,
440
+ audit_trail={
441
+ "timestamp": datetime.now().isoformat(),
442
+ "query": query,
443
+ "error": f"JSON parsing failed: {str(e)}",
444
+ "raw_response": str(response)[:500]
445
+ }
446
+ )
447
+
448
+ except Exception as e:
449
+ logger.error(f"Error in LLM reasoning: {e}")
450
+ return QueryResult(
451
+ decision="conditional",
452
+ amount=None,
453
+ justification=f"Error in analysis: {str(e)}",
454
+ relevant_clauses=[],
455
+ confidence_score=0.0,
456
+ audit_trail={
457
+ "timestamp": datetime.now().isoformat(),
458
+ "query": query,
459
+ "error": str(e)
460
+ }
461
+ )
462
+
463
+ class RAGSystem:
464
+ """GPU-Optimized RAG system orchestrating all components"""
465
+
466
+ def __init__(self, model_path: str = "./mistral-7b-instruct-v0.1.Q4_K_M.gguf", use_gpu: bool = True):
467
+ # Initialize components
468
+ self.document_processor = DocumentProcessor()
469
+
470
+ # Initialize vector database with GPU optimization
471
+ try:
472
+ self.vector_db = VectorDatabase(use_gpu=use_gpu)
473
+ logger.info(f"Vector database initialized successfully (GPU: {use_gpu})")
474
+ except Exception as e:
475
+ logger.error(f"Failed to initialize vector database: {e}")
476
+ raise
477
+
478
+ # Initialize LLM with GPU optimization
479
+ try:
480
+ logger.info(f"Loading model from: {model_path} (GPU: {use_gpu})")
481
+
482
+ from llama_cpp import Llama
483
+
484
+ # GPU-optimized configuration
485
+ if use_gpu:
486
+ self.llm = Llama(
487
+ model_path=model_path,
488
+ n_ctx=4096,
489
+ n_threads=8, # More threads for GPU
490
+ n_gpu_layers=35, # Use GPU layers
491
+ verbose=False,
492
+ use_mmap=True,
493
+ use_mlock=False,
494
+ seed=42
495
+ )
496
+ logger.info("GPU-optimized LLM model loaded successfully")
497
+ else:
498
+ # CPU fallback
499
+ self.llm = Llama(
500
+ model_path=model_path,
501
+ n_ctx=4096,
502
+ n_threads=4,
503
+ n_gpu_layers=0,
504
+ verbose=False,
505
+ use_mmap=True,
506
+ use_mlock=False,
507
+ seed=42
508
+ )
509
+ logger.info("CPU LLM model loaded successfully")
510
+
511
+ except Exception as e:
512
+ logger.warning(f"Could not load local model: {e}")
513
+ logger.info("Falling back to Gemini model")
514
+ try:
515
+ import google.generativeai as genai
516
+ genai.configure(api_key=os.getenv("GEMINI_API_KEY"))
517
+ self.llm = genai.GenerativeModel("gemini-1.5-pro")
518
+ logger.info("Gemini model loaded successfully")
519
+ except ImportError:
520
+ logger.error("Google Generative AI not available. Install with: pip install google-generativeai")
521
+ raise Exception("No LLM model available. Please install google-generativeai or ensure local model file exists.")
522
+ except Exception as gemini_error:
523
+ logger.error(f"Failed to load both local and Gemini models: {gemini_error}")
524
+ raise Exception("No LLM model available. Please check model file or API key.")
525
+
526
+ self.query_parser = QueryParser(self.llm)
527
+ self.reasoning_engine = LLMReasoning(self.llm)
528
+
529
+ # Audit trail storage
530
+ self.audit_log = []
531
+
532
+ def ingest_document(self, file_path: str, use_ocr: bool = False) -> List[DocumentChunk]:
533
+ """Ingest and process a document"""
534
+ try:
535
+ file_path = Path(file_path)
536
+
537
+ # Extract text based on file type
538
+ if file_path.suffix.lower() == '.pdf':
539
+ text = self.document_processor.extract_text_from_pdf(str(file_path), use_ocr)
540
+ elif file_path.suffix.lower() == '.docx':
541
+ text = self.document_processor.extract_text_from_docx(str(file_path))
542
+ elif file_path.suffix.lower() == '.html':
543
+ text = self.document_processor.extract_text_from_html(str(file_path))
544
+ elif file_path.suffix.lower() == '.eml':
545
+ text = self.document_processor.extract_text_from_email(str(file_path))
546
+ else:
547
+ raise ValueError(f"Unsupported file type: {file_path.suffix}")
548
+
549
+ # Chunk the document
550
+ chunks = self.document_processor.chunk_document(text, str(file_path))
551
+
552
+ # Add to vector database
553
+ self.vector_db.add_documents(chunks)
554
+
555
+ logger.info(f"Successfully ingested {len(chunks)} chunks from {file_path}")
556
+ return chunks
557
+
558
+ except Exception as e:
559
+ logger.error(f"Error ingesting document {file_path}: {e}")
560
+ raise
561
+
562
+ def process_query(self, query: str) -> QueryResult:
563
+ """Process a natural language query"""
564
+ try:
565
+ # Step 1: Parse and structure the query
566
+ parsed_entities = self.query_parser.extract_entities(query)
567
+
568
+ # Step 2: Semantic retrieval
569
+ relevant_chunks = self.vector_db.search(query, k=5)
570
+
571
+ # Step 3: LLM reasoning and decision logic
572
+ result = self.reasoning_engine.analyze_query(query, relevant_chunks, parsed_entities)
573
+
574
+ # Step 4: Store audit trail
575
+ self.audit_log.append(result.audit_trail)
576
+
577
+ return result
578
+
579
+ except Exception as e:
580
+ logger.error(f"Error processing query: {e}")
581
+ return QueryResult(
582
+ decision="error",
583
+ amount=None,
584
+ justification=f"System error: {str(e)}",
585
+ relevant_clauses=[],
586
+ confidence_score=0.0,
587
+ audit_trail={
588
+ "timestamp": datetime.now().isoformat(),
589
+ "query": query,
590
+ "error": str(e)
591
+ }
592
+ )
593
+
594
+ def get_audit_trail(self) -> List[Dict]:
595
+ """Get complete audit trail"""
596
+ return self.audit_log
597
+
598
+ def save_audit_trail(self, file_path: str):
599
+ """Save audit trail to file"""
600
+ try:
601
+ with open(file_path, 'w') as f:
602
+ json.dump(self.audit_log, f, indent=2)
603
+ logger.info(f"Audit trail saved to {file_path}")
604
+ except Exception as e:
605
+ logger.error(f"Error saving audit trail: {e}")
606
+
607
+ # Example usage
608
+ if __name__ == "__main__":
609
+ # Initialize GPU-optimized RAG system
610
+ rag_system = RAGSystem(use_gpu=True) # Set to False for CPU-only
611
+
612
+ # Ingest sample document
613
+ print("Ingesting sample document...")
614
+ chunks = rag_system.ingest_document("sample.pdf", use_ocr=False)
615
+
616
+ # Process example queries
617
+ example_queries = [
618
+ "Is heart surgery covered under this policy?",
619
+ "What is the waiting period for pre-existing diseases?",
620
+ "Can I claim for dental treatment?",
621
+ "What is the maximum coverage amount?"
622
+ ]
623
+
624
+ print("\nProcessing queries...")
625
+ for query in example_queries:
626
+ print(f"\nQuery: {query}")
627
+ result = rag_system.process_query(query)
628
+ print(f"Decision: {result.decision}")
629
+ print(f"Justification: {result.justification}")
630
+ print(f"Confidence: {result.confidence_score}")
631
+
632
+ # Save audit trail
633
+ rag_system.save_audit_trail("audit_trail.json")
startup.sh ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ #!/bin/bash
2
+ uvicorn app:app --host 0.0.0.0 --port $PORT
test_api.py ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test script for the Flask API server
4
+ Demonstrates how to use the various endpoints
5
+ """
6
+
7
+ import requests
8
+ import json
9
+ import os
10
+
11
+ # API Configuration
12
+ BASE_URL = "http://127.0.0.1:5000"
13
+ HEADERS = {
14
+ "Content-Type": "application/json"
15
+ }
16
+
17
+ def test_health_check():
18
+ """Test the health check endpoint"""
19
+ print("🔍 Testing health check...")
20
+ try:
21
+ response = requests.get(f"{BASE_URL}/health")
22
+ print(f"Status: {response.status_code}")
23
+ print(f"Response: {response.json()}")
24
+ return response.status_code == 200
25
+ except Exception as e:
26
+ print(f"❌ Health check failed: {e}")
27
+ return False
28
+
29
+ def test_system_status():
30
+ """Test the system status endpoint"""
31
+ print("\n📊 Testing system status...")
32
+ try:
33
+ response = requests.get(f"{BASE_URL}/hackrx/status")
34
+ print(f"Status: {response.status_code}")
35
+ print(f"Response: {json.dumps(response.json(), indent=2)}")
36
+ return response.status_code == 200
37
+ except Exception as e:
38
+ print(f"❌ System status failed: {e}")
39
+ return False
40
+
41
+ def test_query_processing(questions):
42
+ """Test query processing"""
43
+ print(f"\n🤔 Testing query processing...")
44
+
45
+ payload = {
46
+ "questions": questions
47
+ }
48
+
49
+ try:
50
+ response = requests.post(f"{BASE_URL}/hackrx/run",
51
+ json=payload,
52
+ headers=HEADERS)
53
+
54
+ print(f"Status: {response.status_code}")
55
+ print(f"Response: {json.dumps(response.json(), indent=2)}")
56
+ return response.status_code == 200
57
+ except Exception as e:
58
+ print(f"❌ Query processing failed: {e}")
59
+ return False
60
+
61
+ def test_upload_document(file_path):
62
+ """Test document upload"""
63
+ print(f"\n📄 Testing document upload: {file_path}")
64
+
65
+ if not os.path.exists(file_path):
66
+ print(f"❌ File not found: {file_path}")
67
+ return False
68
+
69
+ try:
70
+ with open(file_path, 'rb') as f:
71
+ files = {'file': f}
72
+
73
+ response = requests.post(f"{BASE_URL}/hackrx/upload",
74
+ files=files)
75
+
76
+ print(f"Status: {response.status_code}")
77
+ print(f"Response: {json.dumps(response.json(), indent=2)}")
78
+ return response.status_code == 200
79
+ except Exception as e:
80
+ print(f"❌ Upload failed: {e}")
81
+ return False
82
+
83
+ def test_query_processing(questions):
84
+ """Test query processing"""
85
+ print(f"\n🤔 Testing query processing...")
86
+
87
+ payload = {
88
+ "questions": questions
89
+ }
90
+
91
+ try:
92
+ response = requests.post(f"{BASE_URL}/hackrx/run",
93
+ json=payload,
94
+ headers=HEADERS)
95
+
96
+ print(f"Status: {response.status_code}")
97
+ print(f"Response: {json.dumps(response.json(), indent=2)}")
98
+ return response.status_code == 200
99
+ except Exception as e:
100
+ print(f"❌ Query processing failed: {e}")
101
+ return False
102
+
103
+
104
+
105
+ def main():
106
+ """Main test function"""
107
+ print("🚀 Starting API Tests")
108
+ print("=" * 50)
109
+
110
+ # Test 1: Health check
111
+ if not test_health_check():
112
+ print("❌ Health check failed. Make sure the server is running.")
113
+ return
114
+
115
+ # Test 2: System status
116
+ test_system_status()
117
+
118
+
119
+
120
+ # Test 4: Document upload (if file exists)
121
+ test_files = ["doc2.pdf", "test_document.txt"]
122
+ for test_file in test_files:
123
+ if os.path.exists(test_file):
124
+ test_upload_document(test_file)
125
+ break
126
+
127
+ # Test 3: Query processing
128
+ test_questions = [
129
+ "What is covered under this policy?",
130
+ "What is the maximum coverage amount?",
131
+ "What documents are required for claims?"
132
+ ]
133
+ test_query_processing(test_questions)
134
+
135
+ print("\n✅ API tests completed!")
136
+
137
+ if __name__ == "__main__":
138
+ main()
test_app2.py ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test app2.py API Server
4
+ This script tests the new app2.py Flask API server
5
+ """
6
+
7
+ import requests
8
+ import json
9
+ import time
10
+
11
+ # Your ngrok URL
12
+ BASE_URL = "https://0468c638cef7.ngrok-free.app"
13
+
14
+ def test_health():
15
+ """Test health endpoint"""
16
+ print("🧪 Testing Health Endpoint...")
17
+ try:
18
+ response = requests.get(f"{BASE_URL}/api/health", timeout=10)
19
+ print(f"Status: {response.status_code}")
20
+ if response.status_code == 200:
21
+ print("✅ Health check passed!")
22
+ print(f"Response: {response.json()}")
23
+ else:
24
+ print(f"❌ Health check failed: {response.text}")
25
+ return response.status_code == 200
26
+ except Exception as e:
27
+ print(f"❌ Error: {e}")
28
+ return False
29
+
30
+ def test_root():
31
+ """Test root endpoint"""
32
+ print("\n🧪 Testing Root Endpoint...")
33
+ try:
34
+ response = requests.get(f"{BASE_URL}/", timeout=10)
35
+ print(f"Status: {response.status_code}")
36
+ if response.status_code == 200:
37
+ print("✅ Root endpoint passed!")
38
+ data = response.json()
39
+ print(f"Message: {data.get('message')}")
40
+ print(f"Version: {data.get('version')}")
41
+ print(f"Endpoints: {len(data.get('endpoints', {}))} available")
42
+ else:
43
+ print(f"❌ Root endpoint failed: {response.text}")
44
+ return response.status_code == 200
45
+ except Exception as e:
46
+ print(f"❌ Error: {e}")
47
+ return False
48
+
49
+ def test_simple_query():
50
+ """Test simple query without document URL"""
51
+ print("\n🧪 Testing Simple Query...")
52
+ url = f"{BASE_URL}/hackrx/run"
53
+ headers = {
54
+ "Content-Type": "application/json",
55
+ "Accept": "application/json",
56
+ "Authorization": "Bearer test_key_123"
57
+ }
58
+ payload = {
59
+ "questions": [
60
+ "What is the grace period for premium payment?",
61
+ "What is the waiting period for pre-existing diseases?"
62
+ ]
63
+ }
64
+
65
+ try:
66
+ response = requests.post(url, json=payload, headers=headers, timeout=60)
67
+ print(f"Status: {response.status_code}")
68
+ if response.status_code == 200:
69
+ print("✅ Simple query passed!")
70
+ result = response.json()
71
+ print(f"Answers: {len(result.get('answers', []))} received")
72
+ for i, answer in enumerate(result.get('answers', [])):
73
+ print(f" {i+1}. {answer[:100]}...")
74
+ else:
75
+ print(f"❌ Simple query failed: {response.text}")
76
+ return response.status_code == 200
77
+ except Exception as e:
78
+ print(f"❌ Error: {e}")
79
+ return False
80
+
81
+ def test_hackathon_format():
82
+ """Test hackathon format with document URL"""
83
+ print("\n🧪 Testing Hackathon Format...")
84
+ url = f"{BASE_URL}/hackrx/run"
85
+ headers = {
86
+ "Content-Type": "application/json",
87
+ "Accept": "application/json",
88
+ "Authorization": "Bearer test_key_123"
89
+ }
90
+ payload = {
91
+ "documents": "https://hackrx.blob.core.windows.net/assets/policy.pdf?sv=2023-01-03&st=2025-07-04T09%3A11%3A24Z&se=2027-07-05T09%3A11%3A00Z&sr=b&sp=r&sig=N4a9OU0w0QXO6AOIBiu4bpl7AXvEZogeT%2FjUHNO7HzQ%3D",
92
+ "questions": [
93
+ "What is the grace period for premium payment under the National Parivar Mediclaim Plus Policy?",
94
+ "What is the waiting period for pre-existing diseases (PED) to be covered?",
95
+ "Does this policy cover maternity expenses, and what are the conditions?",
96
+ "What is the waiting period for cataract surgery?",
97
+ "Are the medical expenses for an organ donor covered under this policy?"
98
+ ]
99
+ }
100
+
101
+ try:
102
+ print("⏳ Processing (this may take 30-60 seconds)...")
103
+ response = requests.post(url, json=payload, headers=headers, timeout=120)
104
+ print(f"Status: {response.status_code}")
105
+ if response.status_code == 200:
106
+ print("✅ Hackathon format passed!")
107
+ result = response.json()
108
+ print(f"Answers: {len(result.get('answers', []))} received")
109
+ for i, answer in enumerate(result.get('answers', [])):
110
+ print(f" {i+1}. {answer[:100]}...")
111
+ else:
112
+ print(f"❌ Hackathon format failed: {response.text}")
113
+ return response.status_code == 200
114
+ except Exception as e:
115
+ print(f"❌ Error: {e}")
116
+ return False
117
+
118
+ def test_api_query():
119
+ """Test API query endpoint"""
120
+ print("\n🧪 Testing API Query Endpoint...")
121
+ url = f"{BASE_URL}/api/query"
122
+ headers = {
123
+ "Content-Type": "application/json",
124
+ "Accept": "application/json"
125
+ }
126
+ payload = {
127
+ "query": "What is the grace period for premium payment?"
128
+ }
129
+
130
+ try:
131
+ response = requests.post(url, json=payload, headers=headers, timeout=60)
132
+ print(f"Status: {response.status_code}")
133
+ if response.status_code == 200:
134
+ print("✅ API query passed!")
135
+ result = response.json()
136
+ print(f"Answer: {result.get('answer', '')[:100]}...")
137
+ print(f"Confidence: {result.get('confidence', 0)}")
138
+ print(f"Processing time: {result.get('processing_time', 0):.2f}s")
139
+ else:
140
+ print(f"❌ API query failed: {response.text}")
141
+ return response.status_code == 200
142
+ except Exception as e:
143
+ print(f"❌ Error: {e}")
144
+ return False
145
+
146
+ def main():
147
+ """Run all tests"""
148
+ print("🚀 Testing app2.py API Server")
149
+ print("=" * 50)
150
+ print(f"Testing API at: {BASE_URL}")
151
+ print("=" * 50)
152
+
153
+ # Test health first
154
+ if not test_health():
155
+ print("\n❌ Health check failed! Make sure app2.py is running.")
156
+ print("Run: python app2.py")
157
+ return
158
+
159
+ # Test root endpoint
160
+ test_root()
161
+
162
+ # Test simple query
163
+ test_simple_query()
164
+
165
+ # Test hackathon format
166
+ test_hackathon_format()
167
+
168
+ # Test API query
169
+ test_api_query()
170
+
171
+ print("\n" + "=" * 50)
172
+ print("📋 APP2.PY TESTING GUIDE")
173
+ print("=" * 50)
174
+ print("1. Start the server:")
175
+ print(" python app2.py")
176
+ print()
177
+ print("2. Health Check:")
178
+ print(f" GET {BASE_URL}/api/health")
179
+ print()
180
+ print("3. Root Endpoint:")
181
+ print(f" GET {BASE_URL}/")
182
+ print()
183
+ print("4. Simple Query:")
184
+ print(f" POST {BASE_URL}/hackrx/run")
185
+ print(" Headers: Content-Type: application/json")
186
+ print(" Headers: Authorization: Bearer test_key_123")
187
+ print(" Body: {\"questions\": [\"Your question here\"]}")
188
+ print()
189
+ print("5. Hackathon Format:")
190
+ print(f" POST {BASE_URL}/hackrx/run")
191
+ print(" Headers: Content-Type: application/json")
192
+ print(" Headers: Authorization: Bearer test_key_123")
193
+ print(" Body: {\"documents\": \"URL\", \"questions\": [\"Q1\", \"Q2\"]}")
194
+ print()
195
+ print("6. API Query:")
196
+ print(f" POST {BASE_URL}/api/query")
197
+ print(" Headers: Content-Type: application/json")
198
+ print(" Body: {\"query\": \"Your question here\"}")
199
+ print()
200
+ print("7. Upload Document:")
201
+ print(f" POST {BASE_URL}/hackrx/upload")
202
+ print(" Body: form-data with 'file' field")
203
+ print("=" * 50)
204
+
205
+ if __name__ == "__main__":
206
+ main()
test_document_processing.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test script to isolate document processing issues
4
+ """
5
+
6
+ import os
7
+ import sys
8
+ import time
9
+ from pathlib import Path
10
+
11
+ def test_document_processor():
12
+ """Test the document processor directly"""
13
+ print("🔍 TESTING DOCUMENT PROCESSOR")
14
+ print("=" * 40)
15
+
16
+ try:
17
+ from document_processer import AdvancedDocumentProcessor
18
+
19
+ # Initialize processor
20
+ processor = AdvancedDocumentProcessor()
21
+ print("✅ Document processor initialized")
22
+
23
+ # Test with doc2.pdf
24
+ file_path = "doc2.pdf"
25
+ if not os.path.exists(file_path):
26
+ print(f"❌ File not found: {file_path}")
27
+ return
28
+
29
+ print(f"📄 Processing file: {file_path}")
30
+
31
+ # Test without OCR
32
+ print("\n--- Testing without OCR ---")
33
+ start_time = time.time()
34
+ try:
35
+ chunks = processor.process_document(file_path, use_ocr=False)
36
+ processing_time = time.time() - start_time
37
+ print(f"✅ Success! Processed {len(chunks)} chunks in {processing_time:.2f}s")
38
+
39
+ # Show first chunk
40
+ if chunks:
41
+ print(f"📋 First chunk preview:")
42
+ print(f" ID: {chunks[0].chunk_id}")
43
+ print(f" Content: {chunks[0].content[:100]}...")
44
+ print(f" Source: {chunks[0].source_file}")
45
+ except Exception as e:
46
+ print(f"❌ Failed without OCR: {e}")
47
+
48
+ # Test with OCR
49
+ print("\n--- Testing with OCR ---")
50
+ start_time = time.time()
51
+ try:
52
+ chunks = processor.process_document(file_path, use_ocr=True)
53
+ processing_time = time.time() - start_time
54
+ print(f"✅ Success! Processed {len(chunks)} chunks in {processing_time:.2f}s")
55
+
56
+ # Show first chunk
57
+ if chunks:
58
+ print(f"📋 First chunk preview:")
59
+ print(f" ID: {chunks[0].chunk_id}")
60
+ print(f" Content: {chunks[0].content[:100]}...")
61
+ print(f" Source: {chunks[0].source_file}")
62
+ except Exception as e:
63
+ print(f"❌ Failed with OCR: {e}")
64
+
65
+ except Exception as e:
66
+ print(f"❌ Error initializing document processor: {e}")
67
+
68
+ def test_vector_database():
69
+ """Test the vector database directly"""
70
+ print("\n🔍 TESTING VECTOR DATABASE")
71
+ print("=" * 40)
72
+
73
+ try:
74
+ from vector_database import VectorDatabase
75
+
76
+ # Initialize vector database
77
+ vector_db = VectorDatabase()
78
+ print("✅ Vector database initialized")
79
+
80
+ # Test adding a simple document
81
+ test_content = "This is a test document for vector database testing."
82
+ test_metadata = {
83
+ 'source_file': 'test.txt',
84
+ 'file_type': 'text',
85
+ 'section_type': 'test'
86
+ }
87
+
88
+ print("📝 Adding test document...")
89
+ success = vector_db.add_document(test_content, test_metadata)
90
+
91
+ if success:
92
+ print("✅ Successfully added test document")
93
+
94
+ # Test search
95
+ print("🔍 Testing search...")
96
+ results = vector_db.search_documents("test document", n_results=3)
97
+ print(f"✅ Search returned {len(results)} results")
98
+ else:
99
+ print("❌ Failed to add test document")
100
+
101
+ except Exception as e:
102
+ print(f"❌ Error with vector database: {e}")
103
+
104
+ def test_rag_system():
105
+ """Test the RAG system directly"""
106
+ print("\n🔍 TESTING RAG SYSTEM")
107
+ print("=" * 40)
108
+
109
+ try:
110
+ from rag_system import AdvancedRAGSystem
111
+
112
+ # Initialize RAG system
113
+ print("🔄 Initializing RAG system...")
114
+ rag_system = AdvancedRAGSystem(use_gpu=False) # Use CPU for testing
115
+ print("✅ RAG system initialized")
116
+
117
+ # Test document ingestion
118
+ file_path = "doc2.pdf"
119
+ if os.path.exists(file_path):
120
+ print(f"📄 Testing document ingestion: {file_path}")
121
+ try:
122
+ chunks = rag_system.ingest_document(file_path, use_ocr=False)
123
+ print(f"✅ Successfully ingested {len(chunks)} chunks")
124
+ except Exception as e:
125
+ print(f"❌ Document ingestion failed: {e}")
126
+ else:
127
+ print(f"❌ File not found: {file_path}")
128
+
129
+ except Exception as e:
130
+ print(f"❌ Error with RAG system: {e}")
131
+
132
+ def main():
133
+ """Run all tests"""
134
+ print("🧪 DOCUMENT PROCESSING DIAGNOSTICS")
135
+ print("=" * 50)
136
+
137
+ # Test 1: Document processor
138
+ test_document_processor()
139
+
140
+ # Test 2: Vector database
141
+ test_vector_database()
142
+
143
+ # Test 3: RAG system
144
+ test_rag_system()
145
+
146
+ print("\n✅ Diagnostics completed!")
147
+
148
+ if __name__ == "__main__":
149
+ main()
test_document_to_vector.py ADDED
@@ -0,0 +1,370 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Document to Vector Database Integration Test
3
+ Demonstrates the complete workflow: document_processer.py -> vector_database.py
4
+ """
5
+
6
+ import os
7
+ import sys
8
+ import tkinter as tk
9
+ from tkinter import filedialog
10
+ from pathlib import Path
11
+ import tempfile
12
+ import json
13
+ from datetime import datetime
14
+
15
+ def select_file():
16
+ """Open file dialog to select any supported document file"""
17
+ root = tk.Tk()
18
+ root.withdraw()
19
+
20
+ file_path = filedialog.askopenfilename(
21
+ title="Select a document to process and store in vector database",
22
+ filetypes=[
23
+ ("All supported files", "*.pdf;*.txt;*.docx;*.html;*.htm;*.eml;*.msg;*.csv;*.json"),
24
+ ("PDF files", "*.pdf"),
25
+ ("Text files", "*.txt"),
26
+ ("Word documents", "*.docx"),
27
+ ("HTML files", "*.html;*.htm"),
28
+ ("Email files", "*.eml;*.msg"),
29
+ ("CSV files", "*.csv"),
30
+ ("JSON files", "*.json"),
31
+ ("All files", "*.*")
32
+ ]
33
+ )
34
+
35
+ root.destroy()
36
+ return file_path
37
+
38
+ def process_and_store_document(file_path, use_ocr=False):
39
+ """Process a document and store it in the vector database"""
40
+ try:
41
+ print(f"🔄 Step 1: Processing document with document_processer.py")
42
+ print(f"📄 File: {file_path}")
43
+ print(f"📏 File size: {os.path.getsize(file_path) / 1024:.1f} KB")
44
+
45
+ # Import and use document processor
46
+ from document_processer import AdvancedDocumentProcessor
47
+
48
+ # Initialize document processor
49
+ doc_processor = AdvancedDocumentProcessor()
50
+
51
+ # Process the document
52
+ chunks = doc_processor.process_document(file_path, use_ocr=use_ocr)
53
+
54
+ if not chunks:
55
+ print("❌ No chunks extracted from document")
56
+ return False, "No chunks extracted"
57
+
58
+ print(f"✅ Successfully processed {len(chunks)} chunks")
59
+
60
+ # Display chunk information
61
+ print(f"\n📋 Chunk Analysis:")
62
+ text_chunks = [c for c in chunks if c.section_type == 'main_text']
63
+ table_chunks = [c for c in chunks if c.section_type == 'table']
64
+ metadata_chunks = [c for c in chunks if c.section_type == 'metadata']
65
+
66
+ print(f" 📝 Text chunks: {len(text_chunks)}")
67
+ print(f" 📊 Table chunks: {len(table_chunks)}")
68
+ print(f" 🏷️ Metadata chunks: {len(metadata_chunks)}")
69
+
70
+ # Show sample chunks
71
+ for i, chunk in enumerate(chunks[:3]):
72
+ print(f"\n Chunk {i+1}:")
73
+ print(f" ID: {chunk.chunk_id}")
74
+ print(f" Type: {chunk.section_type}")
75
+ print(f" Content: {chunk.content[:100]}...")
76
+
77
+ print(f"\n🔄 Step 2: Storing in vector database")
78
+
79
+ # Import and use vector database
80
+ from vector_database import VectorDatabase
81
+
82
+ # Initialize vector database
83
+ vector_db = VectorDatabase(
84
+ embedding_model="all-MiniLM-L6-v2",
85
+ collection_name="processed_documents",
86
+ persist_directory="./vector_db",
87
+ use_gpu=True
88
+ )
89
+
90
+ # Add documents to vector database
91
+ success = vector_db.add_documents(chunks)
92
+
93
+ if not success:
94
+ print("❌ Failed to store documents in vector database")
95
+ return False, "Vector database storage failed"
96
+
97
+ print(f"✅ Successfully stored {len(chunks)} chunks in vector database")
98
+
99
+ # Get database statistics
100
+ stats = vector_db.get_document_statistics()
101
+ print(f"\n📊 Vector Database Statistics:")
102
+ print(f" Total chunks: {stats.get('total_chunks', 0)}")
103
+ print(f" Unique sources: {stats.get('unique_sources', 0)}")
104
+ print(f" File types: {stats.get('file_types', [])}")
105
+
106
+ return True, chunks
107
+
108
+ except Exception as e:
109
+ print(f"❌ Error in process_and_store_document: {e}")
110
+ import traceback
111
+ traceback.print_exc()
112
+ return False, str(e)
113
+
114
+ def test_search_functionality(vector_db, original_file):
115
+ """Test search functionality with the stored document"""
116
+ print(f"\n🔍 Step 3: Testing search functionality")
117
+
118
+ # Get filename for search terms
119
+ filename = Path(original_file).stem
120
+
121
+ # Create some test queries
122
+ test_queries = [
123
+ filename, # Search by filename
124
+ "document", # Generic search
125
+ "text content", # Content search
126
+ ]
127
+
128
+ for query in test_queries:
129
+ print(f"\n🔍 Searching for: '{query}'")
130
+
131
+ # Semantic search
132
+ semantic_results = vector_db.search_similar(query, n_results=3)
133
+ print(f" 📝 Semantic search results: {len(semantic_results)}")
134
+
135
+ for i, result in enumerate(semantic_results[:2]):
136
+ print(f" Result {i+1}: Score {result.similarity_score:.3f}")
137
+ print(f" Source: {result.source_file}")
138
+ print(f" Content: {result.content[:80]}...")
139
+
140
+ # Hybrid search
141
+ hybrid_results = vector_db.hybrid_search(query, n_results=3)
142
+ print(f" 🔄 Hybrid search results: {len(hybrid_results)}")
143
+
144
+ for i, result in enumerate(hybrid_results[:2]):
145
+ print(f" Result {i+1}: Score {result.similarity_score:.3f}")
146
+ print(f" Source: {result.source_file}")
147
+ print(f" Content: {result.content[:80]}...")
148
+
149
+ def create_sample_documents():
150
+ """Create sample documents for testing"""
151
+ test_dir = tempfile.mkdtemp()
152
+ print(f"📁 Created test directory: {test_dir}")
153
+
154
+ # Create sample TXT file
155
+ txt_content = """
156
+ Sample Document for Testing
157
+
158
+ This is a sample text document that will be processed and stored in the vector database.
159
+ It contains multiple paragraphs with various topics including:
160
+
161
+ 1. Technology and AI
162
+ 2. Business processes
163
+ 3. Data analysis
164
+ 4. Machine learning applications
165
+
166
+ The document processor should extract this content and create chunks.
167
+ The vector database should then store these chunks with embeddings.
168
+ """
169
+
170
+ txt_path = os.path.join(test_dir, "sample_document.txt")
171
+ with open(txt_path, 'w', encoding='utf-8') as f:
172
+ f.write(txt_content)
173
+
174
+ # Create sample JSON file
175
+ json_data = {
176
+ "title": "Sample JSON Document",
177
+ "author": "Test User",
178
+ "content": "This is a sample JSON document for testing the document processor and vector database integration.",
179
+ "topics": ["document processing", "vector database", "AI", "machine learning"],
180
+ "metadata": {
181
+ "created": datetime.now().isoformat(),
182
+ "version": "1.0",
183
+ "tags": ["test", "sample", "integration"]
184
+ }
185
+ }
186
+
187
+ json_path = os.path.join(test_dir, "sample_data.json")
188
+ with open(json_path, 'w', encoding='utf-8') as f:
189
+ json.dump(json_data, f, indent=2)
190
+
191
+ return test_dir, {
192
+ 'txt': txt_path,
193
+ 'json': json_path
194
+ }
195
+
196
+ def main():
197
+ """Main function to test document to vector database workflow"""
198
+ print("🚀 Document to Vector Database Integration Test")
199
+ print("="*60)
200
+ print("This test demonstrates the complete workflow:")
201
+ print("1. Process document with document_processer.py")
202
+ print("2. Store processed chunks in vector_database.py")
203
+ print("3. Test search functionality")
204
+ print()
205
+
206
+ # Check if required modules are available
207
+ try:
208
+ from document_processer import AdvancedDocumentProcessor
209
+ print("✅ Document processor available")
210
+ except ImportError as e:
211
+ print(f"❌ Document processor not available: {e}")
212
+ return
213
+
214
+ try:
215
+ from vector_database import VectorDatabase
216
+ print("✅ Vector database available")
217
+ except ImportError as e:
218
+ print(f"❌ Vector database not available: {e}")
219
+ return
220
+
221
+ print("\nChoose an option:")
222
+ print("1. Select a file to process")
223
+ print("2. Use sample documents")
224
+ print("3. Process from command line")
225
+
226
+ choice = input("Enter choice (1, 2, or 3): ").strip()
227
+
228
+ if choice == "1":
229
+ # Select file
230
+ print("\n📁 Please select a document to process...")
231
+ file_path = select_file()
232
+
233
+ if not file_path:
234
+ print("❌ No file selected")
235
+ return
236
+
237
+ # Ask about OCR
238
+ use_ocr = input("Use OCR for PDFs? (y/n): ").lower().strip() in ['y', 'yes']
239
+
240
+ # Process and store
241
+ success, result = process_and_store_document(file_path, use_ocr)
242
+
243
+ if success:
244
+ # Test search functionality
245
+ vector_db = VectorDatabase()
246
+ test_search_functionality(vector_db, file_path)
247
+
248
+ print(f"\n🎉 Complete workflow successful!")
249
+ print(f"📄 Processed: {file_path}")
250
+ print(f"📊 Stored in vector database")
251
+ print(f"🔍 Search functionality tested")
252
+
253
+ elif choice == "2":
254
+ # Use sample documents
255
+ test_dir, sample_files = create_sample_documents()
256
+
257
+ print(f"\n🧪 Testing with sample documents...")
258
+
259
+ for file_type, file_path in sample_files.items():
260
+ print(f"\n📄 Processing {file_type.upper()} file...")
261
+ success, result = process_and_store_document(file_path)
262
+
263
+ if success:
264
+ print(f"✅ {file_type.upper()} file processed successfully")
265
+ else:
266
+ print(f"❌ {file_type.upper()} file failed: {result}")
267
+
268
+ # Clean up
269
+ import shutil
270
+ shutil.rmtree(test_dir, ignore_errors=True)
271
+ print(f"\n🧹 Cleaned up test directory")
272
+
273
+ elif choice == "3":
274
+ # Command line processing
275
+ if len(sys.argv) < 2:
276
+ print("❌ Usage: python test_document_to_vector.py <file_path> [--ocr]")
277
+ return
278
+
279
+ file_path = sys.argv[1]
280
+ use_ocr = "--ocr" in sys.argv
281
+
282
+ if not os.path.exists(file_path):
283
+ print(f"❌ File not found: {file_path}")
284
+ return
285
+
286
+ print(f"🔄 Processing file from command line: {file_path}")
287
+ success, result = process_and_store_document(file_path, use_ocr)
288
+
289
+ if success:
290
+ print(f"🎉 Successfully processed and stored: {file_path}")
291
+ else:
292
+ print(f"❌ Failed: {result}")
293
+
294
+ else:
295
+ print("❌ Invalid choice")
296
+
297
+ def batch_process_directory():
298
+ """Process all supported files in a directory"""
299
+ print("🔄 Batch Processing Directory")
300
+ print("="*40)
301
+
302
+ # Select directory
303
+ root = tk.Tk()
304
+ root.withdraw()
305
+ directory = filedialog.askdirectory(title="Select directory containing documents")
306
+ root.destroy()
307
+
308
+ if not directory:
309
+ print("❌ No directory selected")
310
+ return
311
+
312
+ # Find supported files
313
+ supported_extensions = {'.pdf', '.txt', '.docx', '.html', '.htm', '.eml', '.msg', '.csv', '.json'}
314
+ files_to_process = []
315
+
316
+ for ext in supported_extensions:
317
+ files_to_process.extend(Path(directory).glob(f"*{ext}"))
318
+
319
+ if not files_to_process:
320
+ print("❌ No supported files found in directory")
321
+ return
322
+
323
+ print(f"📁 Found {len(files_to_process)} files to process")
324
+
325
+ # Initialize vector database
326
+ vector_db = VectorDatabase()
327
+
328
+ # Process each file
329
+ results = {}
330
+ for file_path in files_to_process:
331
+ print(f"\n🔄 Processing: {file_path.name}")
332
+
333
+ success, result = process_and_store_document(str(file_path))
334
+
335
+ if success:
336
+ print(f"✅ Success: {len(result)} chunks stored")
337
+ results[file_path.name] = len(result)
338
+ else:
339
+ print(f"❌ Failed: {result}")
340
+ results[file_path.name] = "ERROR"
341
+
342
+ # Summary
343
+ print(f"\n📊 BATCH PROCESSING SUMMARY")
344
+ print(f"{'='*40}")
345
+ successful = sum(1 for result in results.values() if isinstance(result, int))
346
+ total = len(results)
347
+
348
+ for filename, result in results.items():
349
+ status = f"{result} chunks" if isinstance(result, int) else result
350
+ print(f"{filename}: {status}")
351
+
352
+ print(f"\n✅ Successfully processed: {successful}/{total} files")
353
+
354
+ # Test search with all documents
355
+ print(f"\n🔍 Testing search with all processed documents...")
356
+ test_search_functionality(vector_db, "batch_processed")
357
+
358
+ if __name__ == "__main__":
359
+ print("Choose workflow:")
360
+ print("1. Single file processing")
361
+ print("2. Batch directory processing")
362
+
363
+ workflow_choice = input("Enter choice (1 or 2): ").strip()
364
+
365
+ if workflow_choice == "1":
366
+ main()
367
+ elif workflow_choice == "2":
368
+ batch_process_directory()
369
+ else:
370
+ print("❌ Invalid choice")
test_hackathon_format.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test script for hackathon API format
4
+ Matches the exact requirements from the hackathon
5
+ """
6
+
7
+ import requests
8
+ import json
9
+
10
+ # Your ngrok URL
11
+ BASE_URL = "https://0468c638cef7.ngrok-free.app"
12
+
13
+ def test_hackathon_format():
14
+ """Test the hackathon API format"""
15
+ print("🧪 Testing Hackathon API Format...")
16
+
17
+ url = f"{BASE_URL}/hackrx/run"
18
+
19
+ # Headers as required by hackathon
20
+ headers = {
21
+ "Content-Type": "application/json",
22
+ "Accept": "application/json",
23
+ "Authorization": "Bearer e6dfe3fbc81eaccabae37a2960e15bc85abe1d3e710777adbbae47ee6b4b4fae" # Any API key works for testing
24
+ }
25
+
26
+ # Request body as required by hackathon
27
+ payload = {
28
+ "documents": "https://hackrx.blob.core.windows.net/assets/policy.pdf?sv=2023-01-03&st=2025-07-04T09%3A11%3A24Z&se=2027-07-05T09%3A11%3A00Z&sr=b&sp=r&sig=N4a9OU0w0QXO6AOIBiu4bpl7AXvEZogeT%2FjUHNO7HzQ%3D",
29
+ "questions": [
30
+ "What is the grace period for premium payment under the National Parivar Mediclaim Plus Policy?",
31
+ "What is the waiting period for pre-existing diseases (PED) to be covered?",
32
+ "Does this policy cover maternity expenses, and what are the conditions?",
33
+ "What is the waiting period for cataract surgery?",
34
+ "Are the medical expenses for an organ donor covered under this policy?"
35
+ ]
36
+ }
37
+
38
+ try:
39
+ print(f"URL: {url}")
40
+ print(f"Headers: {headers}")
41
+ print(f"Payload: {json.dumps(payload, indent=2)}")
42
+
43
+ response = requests.post(
44
+ url,
45
+ json=payload,
46
+ headers=headers,
47
+ timeout=60 # 60 second timeout
48
+ )
49
+
50
+ print(f"Status Code: {response.status_code}")
51
+ print(f"Response: {json.dumps(response.json(), indent=2)}")
52
+
53
+ if response.status_code == 200:
54
+ print("✅ Hackathon API format test passed!")
55
+ return True
56
+ else:
57
+ print("❌ Hackathon API format test failed")
58
+ return False
59
+
60
+ except requests.exceptions.ConnectionError:
61
+ print("❌ Connection error - server might not be running")
62
+ return False
63
+ except Exception as e:
64
+ print(f"❌ Error: {e}")
65
+ return False
66
+
67
+ def test_simple_format():
68
+ """Test with simple questions (no document URL)"""
69
+ print("\n🧪 Testing Simple Format (No Document URL)...")
70
+
71
+ url = f"{BASE_URL}/hackrx/run"
72
+
73
+ headers = {
74
+ "Content-Type": "application/json",
75
+ "Accept": "application/json",
76
+ "Authorization": "Bearer test_key_123"
77
+ }
78
+
79
+ payload = {
80
+ "questions": [
81
+ "What is covered under this policy?",
82
+ "What is the maximum coverage amount?"
83
+ ]
84
+ }
85
+
86
+ try:
87
+ response = requests.post(
88
+ url,
89
+ json=payload,
90
+ headers=headers,
91
+ timeout=30
92
+ )
93
+
94
+ print(f"Status Code: {response.status_code}")
95
+ print(f"Response: {json.dumps(response.json(), indent=2)}")
96
+
97
+ if response.status_code == 200:
98
+ print("✅ Simple format test passed!")
99
+ return True
100
+ else:
101
+ print("❌ Simple format test failed")
102
+ return False
103
+
104
+ except Exception as e:
105
+ print(f"❌ Error: {e}")
106
+ return False
107
+
108
+ def main():
109
+ """Run all tests"""
110
+ print("🚀 Testing Hackathon API Format")
111
+ print("=" * 60)
112
+
113
+ # Test simple format first
114
+ simple_ok = test_simple_format()
115
+
116
+ # Test full hackathon format
117
+ hackathon_ok = test_hackathon_format()
118
+
119
+ print("\n" + "=" * 60)
120
+ print("📊 TEST RESULTS")
121
+ print("=" * 60)
122
+ print(f"Simple Format: {'✅ PASS' if simple_ok else '❌ FAIL'}")
123
+ print(f"Hackathon Format: {'✅ PASS' if hackathon_ok else '❌ FAIL'}")
124
+
125
+ if hackathon_ok:
126
+ print("\n🎉 Your API is ready for hackathon submission!")
127
+ print(f"URL: {BASE_URL}/hackrx/run")
128
+ print("\n📋 Submission Details:")
129
+ print(f"Webhook URL: {BASE_URL}/hackrx/run")
130
+ print("Method: POST")
131
+ print("Headers: Authorization: Bearer <api_key>")
132
+ print("Content-Type: application/json")
133
+ else:
134
+ print("\n⚠️ Please fix the issues and try again.")
135
+
136
+ if __name__ == "__main__":
137
+ main()
test_hackrx.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test script for hackrx/run endpoint
4
+ """
5
+
6
+ import requests
7
+ import json
8
+
9
+ # Your ngrok URL
10
+ BASE_URL = "https://c1c8ea4c476e.ngrok-free.app"
11
+
12
+ def test_hackrx_run():
13
+ """Test the hackrx/run endpoint"""
14
+ print("🧪 Testing hackrx/run endpoint...")
15
+
16
+ url = f"{BASE_URL}/hackrx/run"
17
+ payload = {
18
+ "questions": [
19
+ "What is covered under this policy?",
20
+ "What is the maximum coverage amount?"
21
+ ]
22
+ }
23
+
24
+ try:
25
+ print(f"URL: {url}")
26
+ print(f"Payload: {json.dumps(payload, indent=2)}")
27
+
28
+ response = requests.post(
29
+ url,
30
+ json=payload,
31
+ headers={"Content-Type": "application/json"},
32
+ timeout=30
33
+ )
34
+
35
+ print(f"Status Code: {response.status_code}")
36
+ print(f"Response: {json.dumps(response.json(), indent=2)}")
37
+
38
+ if response.status_code == 200:
39
+ print("✅ hackrx/run endpoint is working!")
40
+ return True
41
+ else:
42
+ print("❌ hackrx/run endpoint failed")
43
+ return False
44
+
45
+ except requests.exceptions.ConnectionError:
46
+ print("❌ Connection error - server might not be running")
47
+ return False
48
+ except Exception as e:
49
+ print(f"❌ Error: {e}")
50
+ return False
51
+
52
+ def test_health():
53
+ """Test health endpoint"""
54
+ print("\n🔍 Testing health endpoint...")
55
+
56
+ try:
57
+ response = requests.get(f"{BASE_URL}/api/health")
58
+ print(f"Health Status: {response.status_code}")
59
+ print(f"Health Response: {json.dumps(response.json(), indent=2)}")
60
+ return response.status_code == 200
61
+ except Exception as e:
62
+ print(f"❌ Health check failed: {e}")
63
+ return False
64
+
65
+ def test_root():
66
+ """Test root endpoint"""
67
+ print("\n🏠 Testing root endpoint...")
68
+
69
+ try:
70
+ response = requests.get(f"{BASE_URL}/")
71
+ print(f"Root Status: {response.status_code}")
72
+ print(f"Root Response: {json.dumps(response.json(), indent=2)}")
73
+ return response.status_code == 200
74
+ except Exception as e:
75
+ print(f"❌ Root check failed: {e}")
76
+ return False
77
+
78
+ def main():
79
+ """Run all tests"""
80
+ print("🚀 Testing HackRX Endpoints")
81
+ print("=" * 50)
82
+
83
+ # Test basic endpoints first
84
+ health_ok = test_health()
85
+ root_ok = test_root()
86
+
87
+ if not health_ok:
88
+ print("❌ Server is not responding. Please restart the Flask server.")
89
+ return
90
+
91
+ # Test the main hackrx/run endpoint
92
+ hackrx_ok = test_hackrx_run()
93
+
94
+ print("\n" + "=" * 50)
95
+ print("📊 TEST RESULTS")
96
+ print("=" * 50)
97
+ print(f"Health Check: {'✅ PASS' if health_ok else '❌ FAIL'}")
98
+ print(f"Root Endpoint: {'✅ PASS' if root_ok else '❌ FAIL'}")
99
+ print(f"HackRX Run: {'✅ PASS' if hackrx_ok else '❌ FAIL'}")
100
+
101
+ if hackrx_ok:
102
+ print("\n🎉 Your endpoint is ready for hackathon submission!")
103
+ print(f"URL: {BASE_URL}/hackrx/run")
104
+ else:
105
+ print("\n⚠️ Please restart your Flask server and try again.")
106
+
107
+ if __name__ == "__main__":
108
+ main()
test_integrated_system.py ADDED
@@ -0,0 +1,543 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Integrated System Test: Query Parser + Vector Database + LLM Reasoning
3
+ Tests the complete workflow from query parsing to reasoning
4
+ """
5
+
6
+ import os
7
+ import sys
8
+ import tempfile
9
+ import shutil
10
+ from pathlib import Path
11
+
12
+ def test_integrated_system():
13
+ """Test the complete integrated system workflow"""
14
+ print("🚀 Integrated System Test")
15
+ print("="*50)
16
+ print("Testing: Query Parser → Vector Database → LLM Reasoning")
17
+ print("="*50)
18
+
19
+ try:
20
+ # Import all components
21
+ print("🔄 Importing components...")
22
+ from query_parser import AdvancedQueryParser
23
+ from vector_database import VectorDatabase
24
+ from llm_reasoning import AdvancedLLMReasoning
25
+ print("✅ All components imported successfully")
26
+
27
+ # Initialize components
28
+ print("\n🔄 Initializing components...")
29
+
30
+ # Initialize query parser
31
+ query_parser = AdvancedQueryParser(use_gpu=False)
32
+ print("✅ Query parser initialized")
33
+
34
+ # Initialize vector database
35
+ vector_db = VectorDatabase(
36
+ collection_name="test_policy_docs",
37
+ embedding_model="all-MiniLM-L6-v2",
38
+ persist_directory="./test_vector_db"
39
+ )
40
+ print("✅ Vector database initialized")
41
+
42
+ # Initialize LLM reasoning (with fallback for missing model)
43
+ try:
44
+ reasoning_engine = AdvancedLLMReasoning(use_gpu=False)
45
+ llm_available = True
46
+ print("✅ LLM reasoning engine initialized")
47
+ except Exception as e:
48
+ print(f"⚠️ LLM reasoning not available: {e}")
49
+ llm_available = False
50
+
51
+ # Create test documents
52
+ print("\n🔄 Creating test documents...")
53
+ test_docs = create_test_documents()
54
+
55
+ # Store documents in vector database
56
+ print("🔄 Storing documents in vector database...")
57
+ for doc in test_docs:
58
+ vector_db.add_document(
59
+ content=doc['content'],
60
+ metadata={
61
+ 'source_file': doc['filename'],
62
+ 'doc_type': 'policy_section',
63
+ 'section': doc['section']
64
+ }
65
+ )
66
+ print(f"✅ Stored {len(test_docs)} documents")
67
+
68
+ # Test queries
69
+ test_queries = [
70
+ "Is heart surgery covered?",
71
+ "What's the waiting period for claims?",
72
+ "How much coverage do I have for dental treatment?",
73
+ "What documents do I need to file a claim?",
74
+ "Are pre-existing conditions covered?"
75
+ ]
76
+
77
+ print(f"\n🔄 Testing {len(test_queries)} queries...")
78
+
79
+ results = []
80
+ for i, query in enumerate(test_queries, 1):
81
+ print(f"\n--- Query {i}: {query} ---")
82
+
83
+ # Step 1: Parse query
84
+ print("🔄 Step 1: Parsing query...")
85
+ parsed_query = query_parser.parse_query(query)
86
+ print(f" Query Type: {parsed_query.query_type}")
87
+ print(f" Intent: {parsed_query.intent}")
88
+ print(f" Entities: {list(parsed_query.entities.keys())}")
89
+ print(f" Keywords: {parsed_query.keywords[:5]}")
90
+
91
+ # Step 2: Search vector database
92
+ print("🔄 Step 2: Searching vector database...")
93
+ search_results = vector_db.search_documents(
94
+ query=query,
95
+ n_results=3,
96
+ similarity_threshold=0.1 # Lower threshold for better matching
97
+ )
98
+ print(f" Found {len(search_results)} relevant documents")
99
+
100
+ # Step 3: LLM reasoning (if available)
101
+ if llm_available:
102
+ print("🔄 Step 3: LLM reasoning...")
103
+
104
+ # Use search results if available, otherwise use fallback context
105
+ if search_results:
106
+ context = search_results
107
+ else:
108
+ # Create fallback context based on query type
109
+ context = [{
110
+ 'content': f"Based on the query '{query}', this appears to be a {parsed_query.query_type} inquiry.",
111
+ 'source_file': 'fallback_context',
112
+ 'similarity_score': 0.5
113
+ }]
114
+
115
+ reasoning_result = reasoning_engine.analyze_query(
116
+ query=query,
117
+ context=context,
118
+ query_type=parsed_query.query_type
119
+ )
120
+ print(f" Decision: {reasoning_result.decision}")
121
+ print(f" Confidence: {reasoning_result.confidence_score:.2f}")
122
+ print(f" Justification: {reasoning_result.justification[:100]}...")
123
+
124
+ # Validate reasoning result
125
+ is_valid = reasoning_engine.validate_decision(reasoning_result)
126
+ print(f" Valid Result: {'✅' if is_valid else '❌'}")
127
+
128
+ results.append({
129
+ 'query': query,
130
+ 'parsed': parsed_query,
131
+ 'search_results': search_results,
132
+ 'reasoning': reasoning_result,
133
+ 'valid': is_valid
134
+ })
135
+ else:
136
+ print("🔄 Step 3: LLM reasoning (not available)")
137
+ results.append({
138
+ 'query': query,
139
+ 'parsed': parsed_query,
140
+ 'search_results': search_results,
141
+ 'reasoning': None,
142
+ 'valid': False
143
+ })
144
+
145
+ # Generate summary report
146
+ print(f"\n{'='*50}")
147
+ print("📊 INTEGRATION TEST RESULTS")
148
+ print(f"{'='*50}")
149
+
150
+ successful_queries = sum(1 for r in results if r['valid'])
151
+ total_queries = len(results)
152
+
153
+ print(f"Total Queries Tested: {total_queries}")
154
+ print(f"Successful Reasoning: {successful_queries}")
155
+ print(f"Success Rate: {successful_queries/total_queries*100:.1f}%")
156
+
157
+ # Detailed results
158
+ print(f"\n📋 DETAILED RESULTS:")
159
+ for i, result in enumerate(results, 1):
160
+ status = "✅" if result['valid'] else "⚠️"
161
+ print(f"{i}. {status} {result['query']}")
162
+ if result['reasoning']:
163
+ print(f" Decision: {result['reasoning'].decision}")
164
+ print(f" Confidence: {result['reasoning'].confidence_score:.2f}")
165
+
166
+ # Test specific functionality
167
+ print(f"\n🧪 FUNCTIONALITY TESTS:")
168
+
169
+ # Test 1: Query parsing
170
+ print("🔄 Test 1: Query parsing functionality...")
171
+ test_parsing()
172
+
173
+ # Test 2: Vector search
174
+ print("🔄 Test 2: Vector search functionality...")
175
+ test_vector_search(vector_db)
176
+
177
+ # Test 3: LLM reasoning (if available)
178
+ if llm_available:
179
+ print("🔄 Test 3: LLM reasoning functionality...")
180
+ test_reasoning(reasoning_engine)
181
+
182
+ # Cleanup
183
+ print(f"\n🧹 Cleaning up...")
184
+ cleanup_test_data()
185
+
186
+ print(f"\n🎉 Integration test completed!")
187
+ return True
188
+
189
+ except Exception as e:
190
+ print(f"❌ Integration test failed: {e}")
191
+ import traceback
192
+ traceback.print_exc()
193
+ return False
194
+
195
+ def create_test_documents():
196
+ """Create test insurance policy documents"""
197
+ docs = [
198
+ {
199
+ 'filename': 'coverage_policy.txt',
200
+ 'section': 'coverage',
201
+ 'content': '''
202
+ MEDICAL COVERAGE POLICY
203
+
204
+ This policy provides comprehensive medical coverage including:
205
+ - Heart surgery and cardiac procedures: Up to $50,000
206
+ - Dental treatment: Up to $2,000 annually
207
+ - Prescription medications: 80% coverage
208
+ - Hospital stays: Up to $1,000 per day
209
+ - Specialist consultations: $100 per visit
210
+
211
+ WAITING PERIODS:
212
+ - General medical: 30 days
213
+ - Pre-existing conditions: 12 months
214
+ - Dental procedures: 6 months
215
+ - Major surgeries: 90 days
216
+
217
+ EXCLUSIONS:
218
+ - Cosmetic procedures
219
+ - Experimental treatments
220
+ - Injuries from dangerous activities
221
+ - Pre-existing conditions (first 12 months)
222
+ '''
223
+ },
224
+ {
225
+ 'filename': 'claim_process.txt',
226
+ 'section': 'claims',
227
+ 'content': '''
228
+ CLAIM PROCESSING PROCEDURES
229
+
230
+ To file a claim, you must provide:
231
+ 1. Completed claim form
232
+ 2. Medical certificate from doctor
233
+ 3. Original receipts and bills
234
+ 4. Prescription details (if applicable)
235
+ 5. Hospital discharge summary (if hospitalized)
236
+
237
+ PROCESSING TIMES:
238
+ - Standard claims: 10-15 business days
239
+ - Urgent claims: 3-5 business days
240
+ - Complex cases: 20-30 business days
241
+
242
+ CLAIM LIMITS:
243
+ - Maximum annual benefit: $100,000
244
+ - Maximum per claim: $25,000
245
+ - Deductible: $500 per year
246
+ '''
247
+ },
248
+ {
249
+ 'filename': 'policy_terms.txt',
250
+ 'section': 'terms',
251
+ 'content': '''
252
+ POLICY TERMS AND CONDITIONS
253
+
254
+ ELIGIBILITY:
255
+ - Age 18-65 years
256
+ - No pre-existing conditions (first year)
257
+ - Must be employed or have alternative coverage
258
+
259
+ COVERAGE PERIOD:
260
+ - Policy term: 12 months
261
+ - Renewable annually
262
+ - Grace period: 30 days for premium payment
263
+
264
+ CANCELLATION:
265
+ - 30 days written notice required
266
+ - Pro-rated refund for unused period
267
+ - No refund after claim submission
268
+
269
+ DISPUTE RESOLUTION:
270
+ - Internal review process
271
+ - External arbitration available
272
+ - 60-day response time for appeals
273
+ '''
274
+ },
275
+ {
276
+ 'filename': 'dental_coverage.txt',
277
+ 'section': 'dental',
278
+ 'content': '''
279
+ DENTAL COVERAGE DETAILS
280
+
281
+ Dental procedures covered:
282
+ - Routine cleanings: 100% coverage
283
+ - Fillings and basic procedures: 80% coverage
284
+ - Root canals: 70% coverage
285
+ - Crowns and bridges: 50% coverage
286
+ - Annual limit: $2,000
287
+
288
+ Waiting period: 6 months for major procedures
289
+ Pre-existing conditions: Not covered for first 12 months
290
+ '''
291
+ },
292
+ {
293
+ 'filename': 'waiting_periods.txt',
294
+ 'section': 'waiting_periods',
295
+ 'content': '''
296
+ WAITING PERIODS AND TIMELINES
297
+
298
+ General Medical Coverage:
299
+ - Waiting period: 30 days
300
+ - Coverage begins after 30 days of policy start
301
+
302
+ Pre-existing Conditions:
303
+ - Waiting period: 12 months
304
+ - No coverage for first 12 months of policy
305
+
306
+ Dental Procedures:
307
+ - Basic procedures: 6 months waiting period
308
+ - Major procedures: 12 months waiting period
309
+
310
+ Major Surgeries:
311
+ - Waiting period: 90 days
312
+ - Pre-authorization required
313
+ '''
314
+ }
315
+ ]
316
+ return docs
317
+
318
+ def test_parsing():
319
+ """Test query parsing functionality"""
320
+ try:
321
+ from query_parser import AdvancedQueryParser
322
+
323
+ parser = AdvancedQueryParser(use_gpu=False)
324
+
325
+ test_cases = [
326
+ ("Is heart surgery covered?", "medical_coverage"),
327
+ ("How do I file a claim?", "claim_inquiry"),
328
+ ("What's the waiting period?", "coverage_check"),
329
+ ("Are dental procedures covered?", "medical_coverage")
330
+ ]
331
+
332
+ passed = 0
333
+ for query, expected_type in test_cases:
334
+ parsed = parser.parse_query(query)
335
+ if parsed.query_type == expected_type or parsed.confidence > 0.3:
336
+ passed += 1
337
+ print(f" ✅ {query}")
338
+ else:
339
+ print(f" ❌ {query} (got {parsed.query_type})")
340
+
341
+ print(f" Parsing Test: {passed}/{len(test_cases)} passed")
342
+
343
+ except Exception as e:
344
+ print(f" ❌ Parsing test failed: {e}")
345
+
346
+ def test_vector_search(vector_db):
347
+ """Test vector search functionality"""
348
+ try:
349
+ # Test basic search with lower threshold
350
+ results = vector_db.search_documents("heart surgery", n_results=2, similarity_threshold=0.05)
351
+ if results:
352
+ print(f" ✅ Vector search working ({len(results)} results)")
353
+ else:
354
+ print(f" ⚠️ Vector search returned no results")
355
+
356
+ # Test similarity threshold
357
+ results = vector_db.search_documents("dental treatment", n_results=5, similarity_threshold=0.05)
358
+ print(f" ✅ Similarity threshold test ({len(results)} results)")
359
+
360
+ except Exception as e:
361
+ print(f" ❌ Vector search test failed: {e}")
362
+
363
+ def test_reasoning(reasoning_engine):
364
+ """Test LLM reasoning functionality"""
365
+ try:
366
+ test_context = [
367
+ {
368
+ 'content': 'Heart surgery is covered up to $50,000 with 90-day waiting period.',
369
+ 'source_file': 'test.pdf',
370
+ 'similarity_score': 0.9
371
+ }
372
+ ]
373
+
374
+ result = reasoning_engine.analyze_query(
375
+ "Is heart surgery covered?",
376
+ test_context,
377
+ 'coverage_check'
378
+ )
379
+
380
+ if result.decision in ['approved', 'denied', 'pending']:
381
+ print(f" ✅ Reasoning working (Decision: {result.decision})")
382
+ else:
383
+ print(f" ⚠️ Unexpected decision: {result.decision}")
384
+
385
+ # Test explanation
386
+ explanation = reasoning_engine.explain_decision(result)
387
+ if len(explanation) > 50:
388
+ print(f" ✅ Explanation generation working")
389
+ else:
390
+ print(f" ⚠️ Short explanation: {len(explanation)} chars")
391
+
392
+ except Exception as e:
393
+ print(f" ❌ Reasoning test failed: {e}")
394
+
395
+ def cleanup_test_data():
396
+ """Clean up test data"""
397
+ try:
398
+ import time
399
+ import gc
400
+
401
+ # Force garbage collection to release file handles
402
+ gc.collect()
403
+ time.sleep(2) # Give more time for file handles to close
404
+
405
+ # Remove test vector database
406
+ if os.path.exists("./test_vector_db"):
407
+ try:
408
+ shutil.rmtree("./test_vector_db", ignore_errors=True)
409
+ print(" ✅ Test vector database cleaned")
410
+ except Exception as e:
411
+ print(f" ⚠️ Could not clean test vector database: {e}")
412
+
413
+ # Remove any temporary files
414
+ temp_files = [f for f in os.listdir('.') if f.startswith('temp_')]
415
+ for file in temp_files:
416
+ try:
417
+ os.remove(file)
418
+ print(f" ✅ Removed {file}")
419
+ except Exception as e:
420
+ print(f" ⚠️ Could not remove {file}: {e}")
421
+
422
+ # Try to remove any remaining test directories
423
+ test_dirs = ["./temp_test_db", "./integration_test_db", "./quick_test_db"]
424
+ for dir_path in test_dirs:
425
+ if os.path.exists(dir_path):
426
+ try:
427
+ shutil.rmtree(dir_path, ignore_errors=True)
428
+ print(f" ✅ Cleaned {dir_path}")
429
+ except Exception as e:
430
+ print(f" ⚠️ Could not clean {dir_path}: {e}")
431
+
432
+ except Exception as e:
433
+ print(f" ⚠️ Cleanup warning: {e}")
434
+
435
+ def test_individual_components():
436
+ """Test individual components separately"""
437
+ print("\n🧪 INDIVIDUAL COMPONENT TESTS")
438
+ print("="*40)
439
+
440
+ # Test Query Parser
441
+ print("\n1️⃣ Testing Query Parser...")
442
+ try:
443
+ from query_parser import AdvancedQueryParser
444
+ parser = AdvancedQueryParser(use_gpu=False)
445
+
446
+ test_query = "Is heart surgery covered under my policy?"
447
+ parsed = parser.parse_query(test_query)
448
+
449
+ print(f" ✅ Query parsing: {parsed.query_type}")
450
+ print(f" ✅ Entities found: {len(parsed.entities)}")
451
+ print(f" ✅ Keywords: {len(parsed.keywords)}")
452
+
453
+ except Exception as e:
454
+ print(f" ❌ Query parser test failed: {e}")
455
+
456
+ # Test Vector Database
457
+ print("\n2️⃣ Testing Vector Database...")
458
+ try:
459
+ from vector_database import VectorDatabase
460
+
461
+ # Create temporary database
462
+ temp_db = VectorDatabase(
463
+ collection_name="temp_test",
464
+ embedding_model="all-MiniLM-L6-v2",
465
+ persist_directory="./temp_test_db"
466
+ )
467
+
468
+ # Add test document
469
+ temp_db.add_document(
470
+ content="Heart surgery is covered up to $50,000.",
471
+ metadata={'source': 'test', 'type': 'coverage'}
472
+ )
473
+
474
+ # Search
475
+ results = temp_db.search_documents("heart surgery", n_results=1)
476
+ if results:
477
+ print(f" ✅ Vector database: {len(results)} results")
478
+ else:
479
+ print(f" ⚠️ Vector database: No results")
480
+
481
+ # Cleanup
482
+ if os.path.exists("./temp_test_db"):
483
+ shutil.rmtree("./temp_test_db")
484
+
485
+ except Exception as e:
486
+ print(f" ❌ Vector database test failed: {e}")
487
+
488
+ # Test LLM Reasoning
489
+ print("\n3️⃣ Testing LLM Reasoning...")
490
+ try:
491
+ from llm_reasoning import AdvancedLLMReasoning
492
+
493
+ reasoning_engine = AdvancedLLMReasoning(use_gpu=False)
494
+
495
+ test_context = [
496
+ {
497
+ 'content': 'Heart surgery is covered up to $50,000.',
498
+ 'source_file': 'test.pdf',
499
+ 'similarity_score': 0.9
500
+ }
501
+ ]
502
+
503
+ result = reasoning_engine.analyze_query(
504
+ "Is heart surgery covered?",
505
+ test_context,
506
+ 'coverage_check'
507
+ )
508
+
509
+ print(f" ✅ LLM reasoning: {result.decision}")
510
+ print(f" ✅ Confidence: {result.confidence_score:.2f}")
511
+
512
+ except Exception as e:
513
+ print(f" ❌ LLM reasoning test failed: {e}")
514
+
515
+ def main():
516
+ """Main test runner"""
517
+ print("🚀 Integrated System Test Suite")
518
+ print("="*50)
519
+
520
+ # Test individual components first
521
+ test_individual_components()
522
+
523
+ # Test full integration
524
+ print(f"\n{'='*50}")
525
+ print("🔄 RUNNING FULL INTEGRATION TEST")
526
+ print(f"{'='*50}")
527
+
528
+ success = test_integrated_system()
529
+
530
+ if success:
531
+ print(f"\n🎉 All tests completed successfully!")
532
+ print("✅ Query Parser → Vector Database → LLM Reasoning integration working")
533
+ else:
534
+ print(f"\n⚠️ Some tests failed. Check the output above for details.")
535
+
536
+ print(f"\n💡 Next steps:")
537
+ print(" 1. Install missing dependencies if any")
538
+ print(" 2. Download required model files")
539
+ print(" 3. Adjust configuration parameters")
540
+ print(" 4. Run with your actual documents")
541
+
542
+ if __name__ == "__main__":
543
+ main()
test_llm_reasoning.py ADDED
@@ -0,0 +1 @@
 
 
1
+
test_pdf_processor.py ADDED
@@ -0,0 +1,345 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ PDF Document Processor Test
3
+ Allows you to choose any PDF file and process it with the document processor
4
+ """
5
+
6
+ import os
7
+ import sys
8
+ import tkinter as tk
9
+ from tkinter import filedialog, messagebox
10
+ from pathlib import Path
11
+ import tempfile
12
+ import shutil
13
+
14
+ def select_pdf_file():
15
+ """Open file dialog to select a PDF file"""
16
+ root = tk.Tk()
17
+ root.withdraw() # Hide the main window
18
+
19
+ file_path = filedialog.askopenfilename(
20
+ title="Select a PDF file to process",
21
+ filetypes=[("PDF files", "*.pdf"), ("All files", "*.*")]
22
+ )
23
+
24
+ root.destroy()
25
+ return file_path
26
+
27
+ def process_pdf_with_ocr(pdf_path, use_ocr=False):
28
+ """Process a PDF file with optional OCR"""
29
+ try:
30
+ from document_processer import AdvancedDocumentProcessor
31
+
32
+ print(f"🔄 Processing PDF: {pdf_path}")
33
+ print(f"📄 File size: {os.path.getsize(pdf_path) / 1024:.1f} KB")
34
+
35
+ # Initialize processor
36
+ processor = AdvancedDocumentProcessor()
37
+
38
+ # Process the document
39
+ chunks = processor.process_document(pdf_path, use_ocr=use_ocr)
40
+
41
+ return chunks, None
42
+
43
+ except Exception as e:
44
+ return None, str(e)
45
+
46
+ def display_results(chunks, pdf_path):
47
+ """Display processing results"""
48
+ print(f"\n{'='*60}")
49
+ print("📊 PROCESSING RESULTS")
50
+ print(f"{'='*60}")
51
+
52
+ print(f"📄 PDF File: {pdf_path}")
53
+ print(f"📊 Total Chunks: {len(chunks)}")
54
+
55
+ # Analyze chunks
56
+ text_chunks = [c for c in chunks if c.section_type == 'main_text']
57
+ table_chunks = [c for c in chunks if c.section_type == 'table']
58
+ metadata_chunks = [c for c in chunks if c.section_type == 'metadata']
59
+
60
+ print(f"📝 Text Chunks: {len(text_chunks)}")
61
+ print(f"📊 Table Chunks: {len(table_chunks)}")
62
+ print(f"🏷️ Metadata Chunks: {len(metadata_chunks)}")
63
+
64
+ # Show sample chunks
65
+ print(f"\n📋 SAMPLE CHUNKS:")
66
+ for i, chunk in enumerate(chunks[:5]): # Show first 5 chunks
67
+ print(f"\nChunk {i+1}:")
68
+ print(f" ID: {chunk.chunk_id}")
69
+ print(f" Type: {chunk.section_type}")
70
+ print(f" Content Preview: {chunk.content[:150]}...")
71
+
72
+ if chunk.table_data:
73
+ print(f" Table Data: {len(chunk.table_data.get('data', []))} rows")
74
+
75
+ if len(chunks) > 5:
76
+ print(f"\n... and {len(chunks) - 5} more chunks")
77
+
78
+ # Save results to file
79
+ save_results_to_file(chunks, pdf_path)
80
+
81
+ def save_results_to_file(chunks, pdf_path):
82
+ """Save processing results to a text file"""
83
+ try:
84
+ # Create output filename
85
+ pdf_name = Path(pdf_path).stem
86
+ output_file = f"{pdf_name}_processed_results.txt"
87
+
88
+ with open(output_file, 'w', encoding='utf-8') as f:
89
+ f.write(f"PDF Processing Results\n")
90
+ f.write(f"="*50 + "\n")
91
+ f.write(f"Source PDF: {pdf_path}\n")
92
+ f.write(f"Total Chunks: {len(chunks)}\n\n")
93
+
94
+ for i, chunk in enumerate(chunks):
95
+ f.write(f"Chunk {i+1}:\n")
96
+ f.write(f" ID: {chunk.chunk_id}\n")
97
+ f.write(f" Type: {chunk.section_type}\n")
98
+ f.write(f" Source: {chunk.source_file}\n")
99
+ f.write(f" File Type: {chunk.file_type}\n")
100
+ f.write(f" Content:\n{chunk.content}\n")
101
+ f.write(f" {'-'*40}\n\n")
102
+
103
+ print(f"\n💾 Results saved to: {output_file}")
104
+
105
+ except Exception as e:
106
+ print(f"⚠️ Could not save results to file: {e}")
107
+
108
+ def analyze_pdf_content(chunks):
109
+ """Analyze the content of processed chunks"""
110
+ print(f"\n🔍 CONTENT ANALYSIS")
111
+ print(f"{'='*40}")
112
+
113
+ total_text_length = sum(len(chunk.content) for chunk in chunks)
114
+ avg_chunk_size = total_text_length / len(chunks) if chunks else 0
115
+
116
+ print(f"📏 Total Text Length: {total_text_length:,} characters")
117
+ print(f"📊 Average Chunk Size: {avg_chunk_size:.0f} characters")
118
+
119
+ # Find longest and shortest chunks
120
+ if chunks:
121
+ longest_chunk = max(chunks, key=lambda x: len(x.content))
122
+ shortest_chunk = min(chunks, key=lambda x: len(x.content))
123
+
124
+ print(f"📏 Longest Chunk: {len(longest_chunk.content)} characters")
125
+ print(f"📏 Shortest Chunk: {len(shortest_chunk.content)} characters")
126
+
127
+ # Count unique words
128
+ all_text = " ".join(chunk.content for chunk in chunks)
129
+ unique_words = len(set(all_text.lower().split()))
130
+ total_words = len(all_text.split())
131
+
132
+ print(f"📝 Total Words: {total_words:,}")
133
+ print(f"📝 Unique Words: {unique_words:,}")
134
+
135
+ def main():
136
+ """Main function to run the PDF processor test"""
137
+ print("🚀 PDF Document Processor Test")
138
+ print("="*50)
139
+ print("This tool allows you to process any PDF file by specifying its path.")
140
+ print("You can choose whether to use OCR for better text extraction.")
141
+ print()
142
+
143
+ # Check if document processor is available
144
+ try:
145
+ from document_processer import AdvancedDocumentProcessor
146
+ print("✅ Document processor loaded successfully")
147
+ except ImportError as e:
148
+ print(f"❌ Error loading document processor: {e}")
149
+ print("💡 Make sure document_processer.py is in the same directory")
150
+ return
151
+
152
+ # Get PDF file path
153
+ print("\n📁 Enter the path to your PDF file:")
154
+ print(" Examples:")
155
+ print(" - C:\\Users\\YourName\\Documents\\document.pdf")
156
+ print(" - /home/username/documents/document.pdf")
157
+ print(" - ./local_file.pdf")
158
+ print(" - Or press Enter to use file dialog")
159
+
160
+ pdf_path = input("PDF file path: ").strip()
161
+
162
+ # If no path provided, use file dialog
163
+ if not pdf_path:
164
+ print("\n📁 Opening file dialog...")
165
+ pdf_path = select_pdf_file()
166
+
167
+ if not pdf_path:
168
+ print("❌ No file selected. Exiting.")
169
+ return
170
+
171
+ # Expand relative paths and resolve to absolute path
172
+ pdf_path = os.path.abspath(os.path.expanduser(pdf_path))
173
+
174
+ if not os.path.exists(pdf_path):
175
+ print(f"❌ File not found: {pdf_path}")
176
+ print("💡 Please check the file path and try again.")
177
+ return
178
+
179
+ # Check if it's actually a PDF file
180
+ if not pdf_path.lower().endswith('.pdf'):
181
+ print(f"⚠️ Warning: File doesn't have .pdf extension: {pdf_path}")
182
+ proceed = input("Continue anyway? (y/n): ").lower().strip()
183
+ if proceed not in ['y', 'yes']:
184
+ print("❌ Exiting.")
185
+ return
186
+
187
+ print(f"✅ Found file: {pdf_path}")
188
+ print(f"📄 File size: {os.path.getsize(pdf_path) / 1024:.1f} KB")
189
+
190
+ # Ask about OCR
191
+ print("\n🤔 Do you want to use OCR for better text extraction?")
192
+ print(" OCR is useful for scanned PDFs or PDFs with images")
193
+ print(" OCR takes longer but provides better results for image-based PDFs")
194
+
195
+ use_ocr = input("Use OCR? (y/n): ").lower().strip() in ['y', 'yes']
196
+
197
+ if use_ocr:
198
+ print("🔍 Will use OCR for text extraction")
199
+ else:
200
+ print("📝 Will use standard text extraction")
201
+
202
+ # Process the PDF
203
+ print(f"\n🔄 Processing PDF...")
204
+ chunks, error = process_pdf_with_ocr(pdf_path, use_ocr)
205
+
206
+ if error:
207
+ print(f"❌ Error processing PDF: {error}")
208
+ print("\n💡 Troubleshooting tips:")
209
+ print("1. Make sure the PDF file is not corrupted")
210
+ print("2. Try without OCR if the PDF has text")
211
+ print("3. Check if all dependencies are installed")
212
+ print("4. Verify the file path is correct")
213
+ return
214
+
215
+ if not chunks:
216
+ print("❌ No chunks were extracted from the PDF")
217
+ print("💡 This might be because:")
218
+ print(" - The PDF is password protected")
219
+ print(" - The PDF contains only images")
220
+ print(" - The PDF is corrupted")
221
+ return
222
+
223
+ # Display results
224
+ display_results(chunks, pdf_path)
225
+
226
+ # Analyze content
227
+ analyze_pdf_content(chunks)
228
+
229
+ print(f"\n🎉 PDF processing completed successfully!")
230
+ print(f"📄 Processed: {pdf_path}")
231
+ print(f"📊 Extracted: {len(chunks)} chunks")
232
+
233
+ def batch_process_pdfs():
234
+ """Process multiple PDF files in a directory"""
235
+ print("🔄 Batch PDF Processing")
236
+ print("="*40)
237
+
238
+ # Select directory
239
+ root = tk.Tk()
240
+ root.withdraw()
241
+ directory = filedialog.askdirectory(title="Select directory containing PDF files")
242
+ root.destroy()
243
+
244
+ if not directory:
245
+ print("❌ No directory selected")
246
+ return
247
+
248
+ # Find PDF files
249
+ pdf_files = list(Path(directory).glob("*.pdf"))
250
+
251
+ if not pdf_files:
252
+ print("❌ No PDF files found in the selected directory")
253
+ return
254
+
255
+ print(f"📁 Found {len(pdf_files)} PDF files in {directory}")
256
+
257
+ # Process each PDF
258
+ results = {}
259
+ for pdf_file in pdf_files:
260
+ print(f"\n🔄 Processing: {pdf_file.name}")
261
+ chunks, error = process_pdf_with_ocr(str(pdf_file), use_ocr=False)
262
+
263
+ if error:
264
+ print(f"❌ Error: {error}")
265
+ results[pdf_file.name] = "ERROR"
266
+ else:
267
+ print(f"✅ Processed: {len(chunks)} chunks")
268
+ results[pdf_file.name] = len(chunks)
269
+
270
+ # Summary
271
+ print(f"\n📊 BATCH PROCESSING SUMMARY")
272
+ print(f"{'='*40}")
273
+ successful = sum(1 for result in results.values() if isinstance(result, int))
274
+ total = len(results)
275
+
276
+ for filename, result in results.items():
277
+ status = f"{result} chunks" if isinstance(result, int) else result
278
+ print(f"{filename}: {status}")
279
+
280
+ print(f"\n✅ Successfully processed: {successful}/{total} files")
281
+
282
+ def process_from_command_line():
283
+ """Process PDF from command line arguments"""
284
+ import sys
285
+
286
+ if len(sys.argv) < 2:
287
+ print("❌ Usage: python test_pdf_processor.py <pdf_file_path> [--ocr]")
288
+ print(" Example: python test_pdf_processor.py C:\\path\\to\\document.pdf --ocr")
289
+ return
290
+
291
+ pdf_path = sys.argv[1]
292
+ use_ocr = "--ocr" in sys.argv
293
+
294
+ # Expand relative paths and resolve to absolute path
295
+ pdf_path = os.path.abspath(os.path.expanduser(pdf_path))
296
+
297
+ if not os.path.exists(pdf_path):
298
+ print(f"❌ File not found: {pdf_path}")
299
+ return
300
+
301
+ print(f"🚀 Processing PDF from command line: {pdf_path}")
302
+ print(f"🔍 OCR enabled: {use_ocr}")
303
+
304
+ # Process the PDF
305
+ chunks, error = process_pdf_with_ocr(pdf_path, use_ocr)
306
+
307
+ if error:
308
+ print(f"❌ Error processing PDF: {error}")
309
+ return
310
+
311
+ if not chunks:
312
+ print("❌ No chunks were extracted from the PDF")
313
+ return
314
+
315
+ # Display results
316
+ display_results(chunks, pdf_path)
317
+ analyze_pdf_content(chunks)
318
+
319
+ print(f"\n🎉 PDF processing completed successfully!")
320
+
321
+ if __name__ == "__main__":
322
+ # Check if command line arguments are provided
323
+ if len(sys.argv) > 1 and not sys.argv[1].startswith("--"):
324
+ process_from_command_line()
325
+ else:
326
+ print("Choose an option:")
327
+ print("1. Process a single PDF file")
328
+ print("2. Batch process all PDFs in a directory")
329
+ print("3. Process from command line (usage: python test_pdf_processor.py <pdf_path> [--ocr])")
330
+
331
+ choice = input("Enter choice (1, 2, or 3): ").strip()
332
+
333
+ if choice == "1":
334
+ main()
335
+ elif choice == "2":
336
+ batch_process_pdfs()
337
+ elif choice == "3":
338
+ print("\nCommand line usage:")
339
+ print("python test_pdf_processor.py <pdf_file_path> [--ocr]")
340
+ print("\nExamples:")
341
+ print("python test_pdf_processor.py C:\\path\\to\\document.pdf")
342
+ print("python test_pdf_processor.py /home/user/document.pdf --ocr")
343
+ print("python test_pdf_processor.py ./local_file.pdf")
344
+ else:
345
+ print("❌ Invalid choice. Exiting.")
test_query_parser.py ADDED
@@ -0,0 +1,474 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Test file for Query Parser
3
+ Checks if query_parser.py is working correctly with various test cases
4
+ """
5
+
6
+ import sys
7
+ import os
8
+ from datetime import datetime
9
+
10
+ def test_query_parser_import():
11
+ """Test if query parser can be imported without errors"""
12
+ print("🧪 Testing Query Parser Import")
13
+ print("="*40)
14
+
15
+ try:
16
+ from query_parser import AdvancedQueryParser, ParsedQuery, QueryEntity
17
+ print("✅ Query parser imported successfully")
18
+ return True
19
+ except Exception as e:
20
+ print(f"❌ Error importing query parser: {e}")
21
+ import traceback
22
+ traceback.print_exc()
23
+ return False
24
+
25
+ def test_query_parser_initialization():
26
+ """Test if query parser can be initialized"""
27
+ print("\n🧪 Testing Query Parser Initialization")
28
+ print("="*50)
29
+
30
+ try:
31
+ from query_parser import AdvancedQueryParser
32
+
33
+ # Test initialization with default parameters
34
+ print("🔄 Initializing query parser...")
35
+ parser = AdvancedQueryParser(use_gpu=False) # Use CPU for testing
36
+ print("✅ Query parser initialized successfully")
37
+
38
+ # Test basic attributes
39
+ print("🔄 Checking parser attributes...")
40
+ assert hasattr(parser, 'lemmatizer'), "Lemmatizer not found"
41
+ assert hasattr(parser, 'stop_words'), "Stop words not found"
42
+ assert hasattr(parser, 'insurance_entities'), "Insurance entities not found"
43
+ assert hasattr(parser, 'query_types'), "Query types not found"
44
+ print("✅ All required attributes present")
45
+
46
+ return True
47
+
48
+ except Exception as e:
49
+ print(f"❌ Error initializing query parser: {e}")
50
+ import traceback
51
+ traceback.print_exc()
52
+ return False
53
+
54
+ def test_basic_query_parsing():
55
+ """Test basic query parsing functionality"""
56
+ print("\n🧪 Testing Basic Query Parsing")
57
+ print("="*40)
58
+
59
+ try:
60
+ from query_parser import AdvancedQueryParser
61
+
62
+ parser = AdvancedQueryParser(use_gpu=False)
63
+
64
+ # Test queries
65
+ test_queries = [
66
+ "Is heart surgery covered?",
67
+ "How do I file a claim?",
68
+ "What's the waiting period?",
69
+ "Can I claim for dental treatment?",
70
+ "What documents are needed?"
71
+ ]
72
+
73
+ results = []
74
+ for i, query in enumerate(test_queries):
75
+ print(f"\n📝 Test Query {i+1}: {query}")
76
+
77
+ try:
78
+ parsed = parser.parse_query(query)
79
+
80
+ # Check if parsed query has required attributes
81
+ assert hasattr(parsed, 'original_query'), "Missing original_query"
82
+ assert hasattr(parsed, 'enhanced_query'), "Missing enhanced_query"
83
+ assert hasattr(parsed, 'query_type'), "Missing query_type"
84
+ assert hasattr(parsed, 'entities'), "Missing entities"
85
+ assert hasattr(parsed, 'intent'), "Missing intent"
86
+ assert hasattr(parsed, 'confidence'), "Missing confidence"
87
+ assert hasattr(parsed, 'keywords'), "Missing keywords"
88
+ assert hasattr(parsed, 'synonyms'), "Missing synonyms"
89
+ assert hasattr(parsed, 'context'), "Missing context"
90
+ assert hasattr(parsed, 'timestamp'), "Missing timestamp"
91
+
92
+ print(f" ✅ Parsed successfully")
93
+ print(f" 📊 Type: {parsed.query_type}")
94
+ print(f" 🎯 Intent: {parsed.intent}")
95
+ print(f" 📈 Confidence: {parsed.confidence:.2f}")
96
+ print(f" 🔑 Keywords: {parsed.keywords[:3]}")
97
+ print(f" 🏷️ Entities: {list(parsed.entities.keys())}")
98
+
99
+ results.append(True)
100
+
101
+ except Exception as e:
102
+ print(f" ❌ Failed to parse: {e}")
103
+ results.append(False)
104
+
105
+ success_count = sum(results)
106
+ total_count = len(results)
107
+
108
+ print(f"\n📊 Basic Parsing Results: {success_count}/{total_count} successful")
109
+ return success_count == total_count
110
+
111
+ except Exception as e:
112
+ print(f"❌ Error in basic query parsing: {e}")
113
+ return False
114
+
115
+ def test_entity_extraction():
116
+ """Test entity extraction functionality"""
117
+ print("\n🧪 Testing Entity Extraction")
118
+ print("="*40)
119
+
120
+ try:
121
+ from query_parser import AdvancedQueryParser
122
+
123
+ parser = AdvancedQueryParser(use_gpu=False)
124
+
125
+ # Test queries with specific entities
126
+ test_cases = [
127
+ {
128
+ 'query': "Is heart surgery covered under my policy?",
129
+ 'expected_entities': ['medical_condition', 'coverage_type']
130
+ },
131
+ {
132
+ 'query': "Can I claim $5000 for dental treatment?",
133
+ 'expected_entities': ['amount', 'medical_condition']
134
+ },
135
+ {
136
+ 'query': "What's the 30-day waiting period for pre-existing conditions?",
137
+ 'expected_entities': ['time_period']
138
+ },
139
+ {
140
+ 'query': "Do I need a doctor's report for this claim?",
141
+ 'expected_entities': ['document_type']
142
+ }
143
+ ]
144
+
145
+ results = []
146
+ for i, test_case in enumerate(test_cases):
147
+ query = test_case['query']
148
+ expected_entities = test_case['expected_entities']
149
+
150
+ print(f"\n📝 Test Case {i+1}: {query}")
151
+
152
+ try:
153
+ parsed = parser.parse_query(query)
154
+ extracted_entities = list(parsed.entities.keys())
155
+
156
+ print(f" 🏷️ Extracted entities: {extracted_entities}")
157
+ print(f" 🎯 Expected entities: {expected_entities}")
158
+
159
+ # Check if any expected entities were found
160
+ found_entities = [entity for entity in expected_entities if entity in extracted_entities]
161
+
162
+ if found_entities:
163
+ print(f" ✅ Found expected entities: {found_entities}")
164
+ results.append(True)
165
+ else:
166
+ print(f" ⚠️ No expected entities found")
167
+ results.append(False)
168
+
169
+ except Exception as e:
170
+ print(f" ❌ Error: {e}")
171
+ results.append(False)
172
+
173
+ success_count = sum(results)
174
+ total_count = len(results)
175
+
176
+ print(f"\n📊 Entity Extraction Results: {success_count}/{total_count} successful")
177
+ return success_count > 0 # At least some entities should be found
178
+
179
+ except Exception as e:
180
+ print(f"❌ Error in entity extraction: {e}")
181
+ return False
182
+
183
+ def test_query_classification():
184
+ """Test query type classification"""
185
+ print("\n🧪 Testing Query Classification")
186
+ print("="*40)
187
+
188
+ try:
189
+ from query_parser import AdvancedQueryParser
190
+
191
+ parser = AdvancedQueryParser(use_gpu=False)
192
+
193
+ # Test queries for different types
194
+ test_cases = [
195
+ {
196
+ 'query': "How do I file a claim?",
197
+ 'expected_type': 'claim_inquiry'
198
+ },
199
+ {
200
+ 'query': "What is covered under my policy?",
201
+ 'expected_type': 'coverage_check'
202
+ },
203
+ {
204
+ 'query': "What are the policy terms?",
205
+ 'expected_type': 'policy_review'
206
+ },
207
+ {
208
+ 'query': "Is dental treatment covered?",
209
+ 'expected_type': 'medical_coverage'
210
+ },
211
+ {
212
+ 'query': "What is this document about?",
213
+ 'expected_type': 'general_inquiry'
214
+ }
215
+ ]
216
+
217
+ results = []
218
+ for i, test_case in enumerate(test_cases):
219
+ query = test_case['query']
220
+ expected_type = test_case['expected_type']
221
+
222
+ print(f"\n📝 Test Case {i+1}: {query}")
223
+
224
+ try:
225
+ parsed = parser.parse_query(query)
226
+ actual_type = parsed.query_type
227
+
228
+ print(f" 🎯 Expected type: {expected_type}")
229
+ print(f" 📊 Actual type: {actual_type}")
230
+ print(f" 📈 Confidence: {parsed.confidence:.2f}")
231
+
232
+ if actual_type == expected_type:
233
+ print(f" ✅ Classification correct")
234
+ results.append(True)
235
+ else:
236
+ print(f" ⚠️ Classification mismatch")
237
+ results.append(False)
238
+
239
+ except Exception as e:
240
+ print(f" ❌ Error: {e}")
241
+ results.append(False)
242
+
243
+ success_count = sum(results)
244
+ total_count = len(results)
245
+
246
+ print(f"\n📊 Classification Results: {success_count}/{total_count} correct")
247
+ return success_count > 0 # At least some classifications should work
248
+
249
+ except Exception as e:
250
+ print(f"❌ Error in query classification: {e}")
251
+ return False
252
+
253
+ def test_query_suggestions():
254
+ """Test query suggestion functionality"""
255
+ print("\n🧪 Testing Query Suggestions")
256
+ print("="*40)
257
+
258
+ try:
259
+ from query_parser import AdvancedQueryParser
260
+
261
+ parser = AdvancedQueryParser(use_gpu=False)
262
+
263
+ # Test queries for suggestions
264
+ test_queries = [
265
+ "claim",
266
+ "coverage",
267
+ "medical",
268
+ "policy",
269
+ "documents"
270
+ ]
271
+
272
+ results = []
273
+ for i, query in enumerate(test_queries):
274
+ print(f"\n📝 Test Query {i+1}: {query}")
275
+
276
+ try:
277
+ suggestions = parser.get_query_suggestions(query)
278
+
279
+ print(f" 💡 Suggestions: {len(suggestions)} found")
280
+ for j, suggestion in enumerate(suggestions[:2]):
281
+ print(f" {j+1}. {suggestion}")
282
+
283
+ if suggestions:
284
+ print(f" ✅ Suggestions generated successfully")
285
+ results.append(True)
286
+ else:
287
+ print(f" ⚠️ No suggestions generated")
288
+ results.append(False)
289
+
290
+ except Exception as e:
291
+ print(f" ❌ Error: {e}")
292
+ results.append(False)
293
+
294
+ success_count = sum(results)
295
+ total_count = len(results)
296
+
297
+ print(f"\n📊 Suggestion Results: {success_count}/{total_count} successful")
298
+ return success_count > 0 # At least some suggestions should work
299
+
300
+ except Exception as e:
301
+ print(f"❌ Error in query suggestions: {e}")
302
+ return False
303
+
304
+ def test_error_handling():
305
+ """Test error handling with invalid inputs"""
306
+ print("\n🧪 Testing Error Handling")
307
+ print("="*40)
308
+
309
+ try:
310
+ from query_parser import AdvancedQueryParser
311
+
312
+ parser = AdvancedQueryParser(use_gpu=False)
313
+
314
+ # Test with invalid inputs
315
+ invalid_inputs = [
316
+ "", # Empty string
317
+ " ", # Whitespace only
318
+ "a", # Single character
319
+ "123", # Numbers only
320
+ "!@#$%", # Special characters only
321
+ None # None value
322
+ ]
323
+
324
+ results = []
325
+ for i, invalid_input in enumerate(invalid_inputs):
326
+ print(f"\n📝 Test Case {i+1}: {repr(invalid_input)}")
327
+
328
+ try:
329
+ parsed = parser.parse_query(invalid_input)
330
+
331
+ # Should return a basic parsed query
332
+ assert parsed is not None, "Should return a parsed query"
333
+ assert hasattr(parsed, 'original_query'), "Should have original_query"
334
+ assert hasattr(parsed, 'enhanced_query'), "Should have enhanced_query"
335
+
336
+ print(f" ✅ Handled gracefully")
337
+ results.append(True)
338
+
339
+ except Exception as e:
340
+ print(f" ❌ Error: {e}")
341
+ results.append(False)
342
+
343
+ success_count = sum(results)
344
+ total_count = len(results)
345
+
346
+ print(f"\n📊 Error Handling Results: {success_count}/{total_count} handled")
347
+ return success_count > 0 # At least some should be handled
348
+
349
+ except Exception as e:
350
+ print(f"❌ Error in error handling test: {e}")
351
+ return False
352
+
353
+ def test_comprehensive_workflow():
354
+ """Test a comprehensive workflow with real-world queries"""
355
+ print("\n🧪 Testing Comprehensive Workflow")
356
+ print("="*50)
357
+
358
+ try:
359
+ from query_parser import AdvancedQueryParser
360
+
361
+ parser = AdvancedQueryParser(use_gpu=False)
362
+
363
+ # Real-world insurance queries
364
+ real_queries = [
365
+ "I need to file a claim for my recent heart surgery that cost $25,000",
366
+ "What's covered under my health insurance policy for dental procedures?",
367
+ "How long is the waiting period for pre-existing conditions?",
368
+ "Do I need a doctor's report and medical certificate for this claim?",
369
+ "Can I claim for prescription medications and hospital stays?"
370
+ ]
371
+
372
+ results = []
373
+ for i, query in enumerate(real_queries):
374
+ print(f"\n📝 Real Query {i+1}: {query}")
375
+
376
+ try:
377
+ parsed = parser.parse_query(query)
378
+
379
+ print(f" 📊 Type: {parsed.query_type}")
380
+ print(f" 🎯 Intent: {parsed.intent}")
381
+ print(f" 📈 Confidence: {parsed.confidence:.2f}")
382
+ print(f" 🔑 Keywords: {parsed.keywords[:5]}")
383
+ print(f" 🏷️ Entities: {list(parsed.entities.keys())}")
384
+
385
+ # Check if enhanced query is different from original
386
+ if parsed.enhanced_query != parsed.original_query:
387
+ print(f" ✨ Query enhanced successfully")
388
+
389
+ # Check if we have meaningful results
390
+ if parsed.confidence > 0.0 or parsed.keywords or parsed.entities:
391
+ print(f" ✅ Meaningful results extracted")
392
+ results.append(True)
393
+ else:
394
+ print(f" ⚠️ Limited results")
395
+ results.append(False)
396
+
397
+ except Exception as e:
398
+ print(f" ❌ Error: {e}")
399
+ results.append(False)
400
+
401
+ success_count = sum(results)
402
+ total_count = len(results)
403
+
404
+ print(f"\n📊 Comprehensive Results: {success_count}/{total_count} successful")
405
+ return success_count > 0 # At least some should work
406
+
407
+ except Exception as e:
408
+ print(f"❌ Error in comprehensive workflow: {e}")
409
+ return False
410
+
411
+ def main():
412
+ """Run all tests"""
413
+ print("🚀 Query Parser Test Suite")
414
+ print("="*60)
415
+ print("Testing query_parser.py functionality")
416
+ print()
417
+
418
+ # Run all tests
419
+ tests = [
420
+ ("Import Test", test_query_parser_import),
421
+ ("Initialization Test", test_query_parser_initialization),
422
+ ("Basic Parsing Test", test_basic_query_parsing),
423
+ ("Entity Extraction Test", test_entity_extraction),
424
+ ("Query Classification Test", test_query_classification),
425
+ ("Query Suggestions Test", test_query_suggestions),
426
+ ("Error Handling Test", test_error_handling),
427
+ ("Comprehensive Workflow Test", test_comprehensive_workflow)
428
+ ]
429
+
430
+ results = {}
431
+ for test_name, test_func in tests:
432
+ print(f"\n{'='*60}")
433
+ print(f"🧪 Running {test_name}")
434
+ print(f"{'='*60}")
435
+
436
+ success = test_func()
437
+ results[test_name] = success
438
+
439
+ if success:
440
+ print(f"✅ {test_name} PASSED")
441
+ else:
442
+ print(f"❌ {test_name} FAILED")
443
+
444
+ # Summary
445
+ print(f"\n{'='*60}")
446
+ print("📊 TEST SUMMARY")
447
+ print(f"{'='*60}")
448
+
449
+ passed = sum(results.values())
450
+ total = len(results)
451
+
452
+ for test_name, success in results.items():
453
+ status = "✅ PASS" if success else "❌ FAIL"
454
+ print(f"{test_name:<30}: {status}")
455
+
456
+ print(f"\nOverall: {passed}/{total} tests passed")
457
+
458
+ if passed == total:
459
+ print("🎉 All tests passed! Your query parser is working correctly.")
460
+ elif passed > total // 2:
461
+ print("⚠️ Most tests passed, but some issues need attention.")
462
+ print("\n💡 Areas to check:")
463
+ for test_name, success in results.items():
464
+ if not success:
465
+ print(f" - {test_name}")
466
+ else:
467
+ print("❌ Many tests failed. Check the error messages above.")
468
+ print("\n💡 Common issues:")
469
+ print(" - Missing dependencies (NLTK, spaCy)")
470
+ print(" - Import errors")
471
+ print(" - Initialization problems")
472
+
473
+ if __name__ == "__main__":
474
+ main()
test_rag_system.py ADDED
@@ -0,0 +1,367 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Test RAG System Integration
3
+ Tests the complete flow: Document Processor → Vector Database → Query Parser → LLM Reasoning → RAG System
4
+ """
5
+
6
+ import os
7
+ import tempfile
8
+ import shutil
9
+ from pathlib import Path
10
+
11
+ def test_rag_system():
12
+ """Test the complete RAG system workflow"""
13
+ print("🚀 RAG System Integration Test")
14
+ print("="*50)
15
+ print("Testing: Document Processor → Vector Database → Query Parser → LLM Reasoning → RAG System")
16
+ print("="*50)
17
+
18
+ try:
19
+ # Import RAG system
20
+ print("🔄 Importing RAG system...")
21
+ from rag_system import AdvancedRAGSystem
22
+ print("✅ RAG system imported successfully")
23
+
24
+ # Initialize RAG system
25
+ print("🔄 Initializing RAG system...")
26
+ rag_system = AdvancedRAGSystem(
27
+ use_gpu=False, # Use CPU for testing
28
+ vector_db_path="./test_rag_db"
29
+ )
30
+ print("✅ RAG system initialized")
31
+
32
+ # Validate system
33
+ print("🔄 Validating system components...")
34
+ validation = rag_system.validate_system()
35
+
36
+ if validation['overall_status']:
37
+ print("✅ All components validated successfully")
38
+ else:
39
+ print("⚠️ Some components have issues:")
40
+ for error in validation['errors']:
41
+ print(f" - {error}")
42
+
43
+ # Create test documents
44
+ print("\n🔄 Creating test documents...")
45
+ test_docs = create_test_documents()
46
+
47
+ # Ingest documents
48
+ print("🔄 Ingesting documents...")
49
+ total_chunks = 0
50
+ for doc_info in test_docs:
51
+ try:
52
+ chunks = rag_system.ingest_document(doc_info['file_path'])
53
+ total_chunks += len(chunks)
54
+ print(f" ✅ Ingested {len(chunks)} chunks from {doc_info['name']}")
55
+ except Exception as e:
56
+ print(f" ❌ Failed to ingest {doc_info['name']}: {e}")
57
+
58
+ print(f"✅ Total chunks ingested: {total_chunks}")
59
+
60
+ # Test queries
61
+ test_queries = [
62
+ "Is heart surgery covered under this policy?",
63
+ "What's the waiting period for dental procedures?",
64
+ "How do I file a claim?",
65
+ "What documents are needed for medical claims?",
66
+ "Are pre-existing conditions covered?"
67
+ ]
68
+
69
+ print(f"\n🔄 Testing {len(test_queries)} queries...")
70
+
71
+ results = []
72
+ for i, query in enumerate(test_queries, 1):
73
+ print(f"\n--- Query {i}: {query} ---")
74
+
75
+ try:
76
+ # Process query through RAG system
77
+ result = rag_system.process_query(query, n_results=3)
78
+
79
+ print(f" Processing Time: {result.processing_time:.2f}s")
80
+ print(f" Query Type: {result.parsed_query.query_type}")
81
+ print(f" Intent: {result.parsed_query.intent}")
82
+ print(f" Confidence: {result.parsed_query.confidence:.2f}")
83
+ print(f" Search Results: {len(result.search_results)}")
84
+ print(f" Decision: {result.reasoning_result.decision}")
85
+ print(f" Reasoning Confidence: {result.reasoning_result.confidence_score:.2f}")
86
+
87
+ # Show top search result
88
+ if result.search_results:
89
+ top_result = result.search_results[0]
90
+ print(f" Top Result: {top_result.content[:100]}...")
91
+ print(f" Source: {top_result.source_file}")
92
+ print(f" Similarity: {top_result.similarity_score:.3f}")
93
+
94
+ # Show reasoning justification
95
+ if result.reasoning_result.justification:
96
+ print(f" Justification: {result.reasoning_result.justification[:150]}...")
97
+
98
+ results.append({
99
+ 'query': query,
100
+ 'result': result,
101
+ 'success': True
102
+ })
103
+
104
+ except Exception as e:
105
+ print(f" ❌ Query processing failed: {e}")
106
+ results.append({
107
+ 'query': query,
108
+ 'result': None,
109
+ 'success': False,
110
+ 'error': str(e)
111
+ })
112
+
113
+ # Generate summary report
114
+ print(f"\n{'='*50}")
115
+ print("📊 RAG SYSTEM TEST RESULTS")
116
+ print(f"{'='*50}")
117
+
118
+ successful_queries = sum(1 for r in results if r['success'])
119
+ total_queries = len(results)
120
+
121
+ print(f"Total Queries Tested: {total_queries}")
122
+ print(f"Successful Queries: {successful_queries}")
123
+ print(f"Success Rate: {successful_queries/total_queries*100:.1f}%")
124
+
125
+ # Detailed results
126
+ print(f"\n📋 DETAILED RESULTS:")
127
+ for i, result in enumerate(results, 1):
128
+ if result['success']:
129
+ rag_result = result['result']
130
+ status = "✅"
131
+ decision = rag_result.reasoning_result.decision
132
+ confidence = rag_result.reasoning_result.confidence_score
133
+ print(f"{i}. {status} {result['query']}")
134
+ print(f" Decision: {decision}")
135
+ print(f" Confidence: {confidence:.2f}")
136
+ print(f" Search Results: {len(rag_result.search_results)}")
137
+ else:
138
+ print(f"{i}. ❌ {result['query']}")
139
+ print(f" Error: {result['error']}")
140
+
141
+ # Test system statistics
142
+ print(f"\n📊 SYSTEM STATISTICS:")
143
+ stats = rag_system.get_system_statistics()
144
+ print(f" Vector Database: {stats.get('vector_database', {}).get('total_chunks', 0)} chunks")
145
+ print(f" Audit Trail: {stats.get('audit_trail', {}).get('total_entries', 0)} entries")
146
+ print(f" Successful Queries: {stats.get('audit_trail', {}).get('successful_queries', 0)}")
147
+
148
+ # Test audit trail
149
+ print(f"\n📋 AUDIT TRAIL SAMPLE:")
150
+ audit_trail = rag_system.get_audit_trail()
151
+ if audit_trail:
152
+ latest_entry = audit_trail[-1]
153
+ print(f" Latest Action: {latest_entry.get('action', 'unknown')}")
154
+ print(f" Status: {latest_entry.get('status', 'unknown')}")
155
+ print(f" Timestamp: {latest_entry.get('timestamp', 'unknown')}")
156
+
157
+ # Cleanup
158
+ print(f"\n🧹 Cleaning up...")
159
+ cleanup_test_data()
160
+
161
+ print(f"\n🎉 RAG system test completed!")
162
+
163
+ if successful_queries == total_queries:
164
+ print("✅ All queries processed successfully!")
165
+ print("🎯 RAG System is working perfectly!")
166
+ else:
167
+ print("⚠️ Some queries failed. Check the detailed results above.")
168
+
169
+ return successful_queries == total_queries
170
+
171
+ except Exception as e:
172
+ print(f"❌ RAG system test failed: {e}")
173
+ import traceback
174
+ traceback.print_exc()
175
+ return False
176
+
177
+ def create_test_documents():
178
+ """Create test documents for RAG system"""
179
+ test_dir = tempfile.mkdtemp()
180
+ print(f"📁 Created test directory: {test_dir}")
181
+
182
+ docs = []
183
+
184
+ # Create policy document
185
+ policy_content = """
186
+ MEDICAL INSURANCE POLICY
187
+
188
+ COVERAGE DETAILS:
189
+ - Heart surgery: Covered up to $50,000
190
+ - Dental procedures: Covered up to $2,000 annually
191
+ - Prescription medications: 80% coverage
192
+ - Hospital stays: Up to $1,000 per day
193
+ - Specialist consultations: $100 per visit
194
+
195
+ WAITING PERIODS:
196
+ - General medical: 30 days
197
+ - Pre-existing conditions: 12 months
198
+ - Dental procedures: 6 months
199
+ - Major surgeries: 90 days
200
+
201
+ CLAIM PROCEDURES:
202
+ - Submit claim form within 30 days
203
+ - Include medical certificate
204
+ - Provide original receipts and bills
205
+ - Processing time: 10-15 business days
206
+
207
+ EXCLUSIONS:
208
+ - Cosmetic procedures
209
+ - Experimental treatments
210
+ - Injuries from dangerous activities
211
+ - Pre-existing conditions (first 12 months)
212
+ """
213
+
214
+ policy_path = os.path.join(test_dir, "medical_policy.txt")
215
+ with open(policy_path, 'w', encoding='utf-8') as f:
216
+ f.write(policy_content)
217
+
218
+ docs.append({
219
+ 'name': 'Medical Policy',
220
+ 'file_path': policy_path,
221
+ 'type': 'policy'
222
+ })
223
+
224
+ # Create claims guide
225
+ claims_content = """
226
+ CLAIMS PROCESSING GUIDE
227
+
228
+ REQUIRED DOCUMENTS:
229
+ 1. Completed claim form
230
+ 2. Medical certificate from doctor
231
+ 3. Original receipts and bills
232
+ 4. Prescription details (if applicable)
233
+ 5. Hospital discharge summary (if hospitalized)
234
+
235
+ PROCESSING TIMES:
236
+ - Standard claims: 10-15 business days
237
+ - Urgent claims: 3-5 business days
238
+ - Complex cases: 20-30 business days
239
+
240
+ CLAIM LIMITS:
241
+ - Maximum annual benefit: $100,000
242
+ - Maximum per claim: $25,000
243
+ - Deductible: $500 per year
244
+
245
+ SUBMISSION METHODS:
246
+ - Online portal
247
+ - Mobile app
248
+ - Mail to claims department
249
+ - In-person at service centers
250
+ """
251
+
252
+ claims_path = os.path.join(test_dir, "claims_guide.txt")
253
+ with open(claims_path, 'w', encoding='utf-8') as f:
254
+ f.write(claims_content)
255
+
256
+ docs.append({
257
+ 'name': 'Claims Guide',
258
+ 'file_path': claims_path,
259
+ 'type': 'guide'
260
+ })
261
+
262
+ return docs
263
+
264
+ def cleanup_test_data():
265
+ """Clean up test data"""
266
+ try:
267
+ import time
268
+ import gc
269
+
270
+ # Force garbage collection
271
+ gc.collect()
272
+ time.sleep(2)
273
+
274
+ # Remove test directories
275
+ test_dirs = ["./test_rag_db", "./test_vector_db", "./temp_test_db"]
276
+ for dir_path in test_dirs:
277
+ if os.path.exists(dir_path):
278
+ try:
279
+ shutil.rmtree(dir_path, ignore_errors=True)
280
+ print(f" ✅ Cleaned {dir_path}")
281
+ except Exception as e:
282
+ print(f" ⚠️ Could not clean {dir_path}: {e}")
283
+
284
+ # Remove temporary files
285
+ temp_files = [f for f in os.listdir('.') if f.startswith('temp_')]
286
+ for file in temp_files:
287
+ try:
288
+ os.remove(file)
289
+ print(f" ✅ Removed {file}")
290
+ except Exception as e:
291
+ print(f" ⚠️ Could not remove {file}: {e}")
292
+
293
+ except Exception as e:
294
+ print(f" ⚠️ Cleanup warning: {e}")
295
+
296
+ def test_individual_components():
297
+ """Test individual components before RAG system"""
298
+ print("\n🧪 TESTING INDIVIDUAL COMPONENTS")
299
+ print("="*40)
300
+
301
+ components = {
302
+ 'Document Processor': 'document_processer',
303
+ 'Vector Database': 'vector_database',
304
+ 'Query Parser': 'query_parser',
305
+ 'LLM Reasoning': 'llm_reasoning'
306
+ }
307
+
308
+ results = {}
309
+
310
+ for name, module in components.items():
311
+ print(f"\n🔄 Testing {name}...")
312
+ try:
313
+ __import__(module)
314
+ print(f" ✅ {name} imported successfully")
315
+ results[name] = True
316
+ except Exception as e:
317
+ print(f" ❌ {name} import failed: {e}")
318
+ results[name] = False
319
+
320
+ # Summary
321
+ print(f"\n📊 COMPONENT TEST RESULTS:")
322
+ passed = sum(results.values())
323
+ total = len(results)
324
+
325
+ for name, result in results.items():
326
+ status = "✅ PASS" if result else "❌ FAIL"
327
+ print(f" {name}: {status}")
328
+
329
+ print(f"\nOverall: {passed}/{total} components ready")
330
+
331
+ return passed == total
332
+
333
+ def main():
334
+ """Main test runner"""
335
+ print("🚀 RAG System Test Suite")
336
+ print("="*50)
337
+
338
+ # Test individual components first
339
+ components_ready = test_individual_components()
340
+
341
+ if not components_ready:
342
+ print("\n❌ Some components are not ready. Please fix the issues above.")
343
+ return False
344
+
345
+ print(f"\n{'='*50}")
346
+ print("🔄 RUNNING RAG SYSTEM INTEGRATION TEST")
347
+ print(f"{'='*50}")
348
+
349
+ # Test RAG system
350
+ success = test_rag_system()
351
+
352
+ if success:
353
+ print(f"\n🎉 RAG System Integration Test PASSED!")
354
+ print("✅ All components working together successfully")
355
+ print("🎯 Your RAG system is ready for production use!")
356
+ else:
357
+ print(f"\n⚠️ RAG System Integration Test FAILED!")
358
+ print("❌ Some issues need to be resolved")
359
+
360
+ print(f"\n💡 Next steps:")
361
+ print(" 1. Add your actual documents")
362
+ print(" 2. Customize the query processing")
363
+ print(" 3. Fine-tune the reasoning engine")
364
+ print(" 4. Deploy to production")
365
+
366
+ if __name__ == "__main__":
367
+ main()
test_upload.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test Upload Endpoint
4
+ This script tests the file upload functionality
5
+ """
6
+
7
+ import requests
8
+ import os
9
+
10
+ # Your ngrok URL
11
+ BASE_URL = "https://0468c638cef7.ngrok-free.app"
12
+
13
+ def test_upload():
14
+ """Test file upload"""
15
+ print("🧪 Testing File Upload...")
16
+
17
+ # Check if we have a test file
18
+ test_files = ["doc2.pdf", "main.py", "app.py"]
19
+ test_file = None
20
+
21
+ for file in test_files:
22
+ if os.path.exists(file):
23
+ test_file = file
24
+ break
25
+
26
+ if not test_file:
27
+ print("❌ No test file found. Please ensure you have a PDF file in the directory.")
28
+ return False
29
+
30
+ print(f"📁 Using test file: {test_file}")
31
+
32
+ url = f"{BASE_URL}/hackrx/upload"
33
+
34
+ try:
35
+ with open(test_file, 'rb') as f:
36
+ files = {'file': (test_file, f, 'application/pdf')}
37
+ print(f"⏳ Uploading {test_file}...")
38
+ response = requests.post(url, files=files, timeout=60)
39
+
40
+ print(f"Status: {response.status_code}")
41
+ if response.status_code == 200:
42
+ print("✅ Upload successful!")
43
+ result = response.json()
44
+ print(f"Response: {result}")
45
+ return True
46
+ else:
47
+ print(f"❌ Upload failed: {response.text}")
48
+ return False
49
+
50
+ except Exception as e:
51
+ print(f"❌ Error: {e}")
52
+ return False
53
+
54
+ def test_health_first():
55
+ """Test health endpoint first"""
56
+ print("🧪 Testing Health Endpoint...")
57
+ try:
58
+ response = requests.get(f"{BASE_URL}/api/health", timeout=10)
59
+ if response.status_code == 200:
60
+ print("✅ Health check passed!")
61
+ return True
62
+ else:
63
+ print(f"❌ Health check failed: {response.text}")
64
+ return False
65
+ except Exception as e:
66
+ print(f"❌ Error: {e}")
67
+ return False
68
+
69
+ def main():
70
+ """Run upload test"""
71
+ print("🚀 Upload Test")
72
+ print("=" * 40)
73
+
74
+ # Test health first
75
+ if not test_health_first():
76
+ print("\n❌ Server not responding. Make sure Flask server is running!")
77
+ print("Run: python app.py")
78
+ return
79
+
80
+ # Test upload
81
+ test_upload()
82
+
83
+ print("\n" + "=" * 40)
84
+ print("📋 POSTMAN UPLOAD GUIDE")
85
+ print("=" * 40)
86
+ print("1. Method: POST")
87
+ print(f"2. URL: {BASE_URL}/hackrx/upload")
88
+ print("3. Body: form-data")
89
+ print("4. Key: file (Type: File)")
90
+ print("5. Value: Select your PDF file")
91
+ print("=" * 40)
92
+
93
+ if __name__ == "__main__":
94
+ main()
vector_database.py ADDED
@@ -0,0 +1,567 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Advanced Vector Database for Document Storage and Retrieval
3
+ Handles document embeddings, similarity search, and metadata management
4
+ """
5
+
6
+ import os
7
+ import json
8
+ import logging
9
+ import hashlib
10
+ import uuid
11
+ from datetime import datetime
12
+ from typing import List, Dict, Any, Optional, Tuple
13
+ from dataclasses import dataclass, asdict
14
+ from pathlib import Path
15
+ import numpy as np
16
+
17
+ # Vector database and embedding libraries
18
+ import chromadb
19
+ from chromadb.config import Settings
20
+ from sentence_transformers import SentenceTransformer
21
+
22
+ # Optional LangChain imports with error handling
23
+ try:
24
+ from langchain_huggingface import HuggingFaceEmbeddings
25
+ from langchain_chroma import Chroma
26
+ from langchain.retrievers import ContextualCompressionRetriever
27
+ from langchain.retrievers.document_compressors import LLMChainExtractor
28
+ LANGCHAIN_AVAILABLE = True
29
+ except ImportError as e:
30
+ print(f"⚠️ LangChain components not available: {e}")
31
+ print(" Basic functionality will work without LangChain features")
32
+ LANGCHAIN_AVAILABLE = False
33
+
34
+ # Disable ChromaDB telemetry
35
+ os.environ["ANONYMIZED_TELEMETRY"] = "False"
36
+
37
+ # Configure logging
38
+ logging.basicConfig(level=logging.INFO)
39
+ logger = logging.getLogger(__name__)
40
+
41
+ @dataclass
42
+ class SearchResult:
43
+ """Represents a search result with metadata"""
44
+ chunk_id: str
45
+ content: str
46
+ source_file: str
47
+ similarity_score: float
48
+ metadata: Dict[str, Any]
49
+ section_type: Optional[str] = None
50
+ table_data: Optional[Dict[str, Any]] = None
51
+
52
+ class VectorDatabase:
53
+ """Advanced vector database with GPU optimization and hybrid search"""
54
+
55
+ def __init__(self,
56
+ embedding_model: str = "all-MiniLM-L6-v2",
57
+ collection_name: str = "documents",
58
+ persist_directory: str = "./vector_db",
59
+ use_gpu: bool = True):
60
+
61
+ self.embedding_model = embedding_model
62
+ self.collection_name = collection_name
63
+ self.persist_directory = persist_directory
64
+ self.use_gpu = use_gpu
65
+
66
+ # Initialize embedding model
67
+ self._initialize_embeddings()
68
+
69
+ # Initialize ChromaDB
70
+ self._initialize_chromadb()
71
+
72
+ # Initialize LangChain components
73
+ self._initialize_langchain()
74
+
75
+ logger.info(f"Vector database initialized with model: {embedding_model}")
76
+
77
+ def _initialize_embeddings(self):
78
+ """Initialize the embedding model"""
79
+ try:
80
+ # Use GPU if available and requested
81
+ device = "cuda" if self.use_gpu and self._check_gpu_availability() else "cpu"
82
+
83
+ self.embedder = SentenceTransformer(self.embedding_model, device=device)
84
+
85
+ # Initialize LangChain embeddings if available
86
+ if LANGCHAIN_AVAILABLE:
87
+ self.langchain_embeddings = HuggingFaceEmbeddings(
88
+ model_name=self.embedding_model,
89
+ model_kwargs={'device': device}
90
+ )
91
+ else:
92
+ self.langchain_embeddings = None
93
+
94
+ logger.info(f"Embedding model loaded on device: {device}")
95
+
96
+ except Exception as e:
97
+ logger.error(f"Error initializing embedding model: {e}")
98
+ raise
99
+
100
+ def _check_gpu_availability(self) -> bool:
101
+ """Check if GPU is available"""
102
+ try:
103
+ import torch
104
+ return torch.cuda.is_available()
105
+ except ImportError:
106
+ return False
107
+
108
+ def _initialize_chromadb(self):
109
+ """Initialize ChromaDB client and collection"""
110
+ try:
111
+ # Create persist directory
112
+ os.makedirs(self.persist_directory, exist_ok=True)
113
+
114
+ # Initialize ChromaDB client
115
+ self.chroma_client = chromadb.PersistentClient(
116
+ path=self.persist_directory,
117
+ settings=Settings(
118
+ anonymized_telemetry=False,
119
+ allow_reset=True
120
+ )
121
+ )
122
+
123
+ # Get or create collection
124
+ self.collection = self.chroma_client.get_or_create_collection(
125
+ name=self.collection_name,
126
+ metadata={"hnsw:space": "cosine"}
127
+ )
128
+
129
+ logger.info(f"ChromaDB collection '{self.collection_name}' initialized")
130
+
131
+ except Exception as e:
132
+ logger.error(f"Error initializing ChromaDB: {e}")
133
+ raise
134
+
135
+ def _initialize_langchain(self):
136
+ """Initialize LangChain components for advanced retrieval"""
137
+ if not LANGCHAIN_AVAILABLE:
138
+ logger.warning("LangChain components not available - advanced features disabled")
139
+ self.langchain_chroma = None
140
+ self.contextual_retriever = None
141
+ return
142
+
143
+ try:
144
+ # Initialize LangChain Chroma
145
+ self.langchain_chroma = Chroma(
146
+ client=self.chroma_client,
147
+ collection_name=self.collection_name,
148
+ embedding_function=self.langchain_embeddings
149
+ )
150
+
151
+ # Initialize contextual compression retriever
152
+ self.contextual_retriever = ContextualCompressionRetriever(
153
+ base_retriever=self.langchain_chroma.as_retriever(
154
+ search_type="similarity",
155
+ search_kwargs={"k": 10}
156
+ ),
157
+ base_compressor=LLMChainExtractor.from_llm(
158
+ llm=None, # Will be set later
159
+ prompt_template="Extract the most relevant information from the following text: {text}"
160
+ )
161
+ )
162
+
163
+ logger.info("LangChain components initialized")
164
+
165
+ except Exception as e:
166
+ logger.error(f"Error initializing LangChain components: {e}")
167
+ # Continue without LangChain components if they fail
168
+ self.langchain_chroma = None
169
+ self.contextual_retriever = None
170
+
171
+ def add_documents(self, chunks: List[Any]) -> bool:
172
+ """Add document chunks to the vector database"""
173
+ try:
174
+ if not chunks:
175
+ logger.warning("No chunks to add")
176
+ return False
177
+
178
+ # Prepare data for ChromaDB
179
+ ids = []
180
+ texts = []
181
+ metadatas = []
182
+ embeddings = []
183
+
184
+ for chunk in chunks:
185
+ # Generate unique ID
186
+ chunk_id = chunk.chunk_id if hasattr(chunk, 'chunk_id') else str(uuid.uuid4())
187
+
188
+ # Get content
189
+ content = chunk.content if hasattr(chunk, 'content') else str(chunk)
190
+
191
+ # Create metadata (filter out None values)
192
+ metadata = {
193
+ 'source_file': getattr(chunk, 'source_file', 'unknown'),
194
+ 'file_type': getattr(chunk, 'file_type', 'unknown'),
195
+ 'section_type': getattr(chunk, 'section_type', 'text'),
196
+ 'chunk_index': getattr(chunk, 'chunk_index', 0),
197
+ 'confidence_score': getattr(chunk, 'confidence_score', 1.0),
198
+ 'timestamp': datetime.now().isoformat()
199
+ }
200
+
201
+ # Add page_number only if it's not None
202
+ page_number = getattr(chunk, 'page_number', None)
203
+ if page_number is not None:
204
+ metadata['page_number'] = page_number
205
+
206
+ # Add table data if present
207
+ if hasattr(chunk, 'table_data') and chunk.table_data:
208
+ metadata['table_data'] = json.dumps(chunk.table_data)
209
+
210
+ # Generate embedding
211
+ embedding = self.embedder.encode(content, convert_to_tensor=False)
212
+
213
+ ids.append(chunk_id)
214
+ texts.append(content)
215
+ metadatas.append(metadata)
216
+ embeddings.append(embedding.tolist())
217
+
218
+ # Add to ChromaDB
219
+ self.collection.add(
220
+ ids=ids,
221
+ documents=texts,
222
+ metadatas=metadatas,
223
+ embeddings=embeddings
224
+ )
225
+
226
+ logger.info(f"Successfully added {len(chunks)} chunks to vector database")
227
+ return True
228
+
229
+ except Exception as e:
230
+ logger.error(f"Error adding documents to vector database: {e}")
231
+ return False
232
+
233
+ def add_document(self, content: str, metadata: Dict[str, Any]) -> bool:
234
+ """Add a single document to the vector database"""
235
+ try:
236
+ # Generate unique ID
237
+ chunk_id = str(uuid.uuid4())
238
+
239
+ # Create metadata with defaults
240
+ doc_metadata = {
241
+ 'source_file': metadata.get('source_file', 'unknown'),
242
+ 'file_type': metadata.get('file_type', 'unknown'),
243
+ 'section_type': metadata.get('section_type', 'text'),
244
+ 'chunk_index': metadata.get('chunk_index', 0),
245
+ 'confidence_score': metadata.get('confidence_score', 1.0),
246
+ 'timestamp': datetime.now().isoformat()
247
+ }
248
+
249
+ # Add additional metadata
250
+ for key, value in metadata.items():
251
+ if key not in doc_metadata and value is not None:
252
+ doc_metadata[key] = value
253
+
254
+ # Generate embedding
255
+ embedding = self.embedder.encode(content, convert_to_tensor=False)
256
+
257
+ # Add to ChromaDB
258
+ self.collection.add(
259
+ ids=[chunk_id],
260
+ documents=[content],
261
+ metadatas=[doc_metadata],
262
+ embeddings=[embedding.tolist()]
263
+ )
264
+
265
+ logger.info(f"Successfully added document to vector database")
266
+ return True
267
+
268
+ except Exception as e:
269
+ logger.error(f"Error adding document to vector database: {e}")
270
+ return False
271
+
272
+ def search_documents(self, query: str, n_results: int = 5, similarity_threshold: float = 0.7) -> List[Dict[str, Any]]:
273
+ """Search for documents and return as dictionary format for compatibility"""
274
+ try:
275
+ search_results = self.search_similar(query, n_results, similarity_threshold)
276
+
277
+ # Convert to dictionary format
278
+ results = []
279
+ for result in search_results:
280
+ results.append({
281
+ 'content': result.content,
282
+ 'source_file': result.source_file,
283
+ 'similarity_score': result.similarity_score,
284
+ 'metadata': result.metadata,
285
+ 'section_type': result.section_type,
286
+ 'table_data': result.table_data
287
+ })
288
+
289
+ return results
290
+
291
+ except Exception as e:
292
+ logger.error(f"Error searching documents: {e}")
293
+ return []
294
+
295
+ def search_similar(self,
296
+ query: str,
297
+ n_results: int = 5,
298
+ similarity_threshold: float = 0.7,
299
+ filter_metadata: Optional[Dict[str, Any]] = None) -> List[SearchResult]:
300
+ """Search for similar documents using semantic similarity"""
301
+ try:
302
+ # Generate query embedding
303
+ query_embedding = self.embedder.encode(query, convert_to_tensor=False)
304
+
305
+ # Prepare where clause for filtering
306
+ where_clause = None
307
+ if filter_metadata:
308
+ where_clause = filter_metadata
309
+
310
+ # Search in ChromaDB
311
+ results = self.collection.query(
312
+ query_embeddings=[query_embedding.tolist()],
313
+ n_results=n_results,
314
+ where=where_clause,
315
+ include=['documents', 'metadatas', 'distances']
316
+ )
317
+
318
+ # Process results
319
+ search_results = []
320
+ for i in range(len(results['ids'][0])):
321
+ chunk_id = results['ids'][0][i]
322
+ content = results['documents'][0][i]
323
+ metadata = results['metadatas'][0][i]
324
+ distance = results['distances'][0][i]
325
+
326
+ # Convert distance to similarity score
327
+ similarity_score = 1 - distance
328
+
329
+ # Filter by similarity threshold
330
+ if similarity_score >= similarity_threshold:
331
+ # Parse table data if present
332
+ table_data = None
333
+ if 'table_data' in metadata and metadata['table_data']:
334
+ try:
335
+ table_data = json.loads(metadata['table_data'])
336
+ except:
337
+ pass
338
+
339
+ result = SearchResult(
340
+ chunk_id=chunk_id,
341
+ content=content,
342
+ source_file=metadata.get('source_file', 'unknown'),
343
+ similarity_score=similarity_score,
344
+ metadata=metadata,
345
+ section_type=metadata.get('section_type', 'text'),
346
+ table_data=table_data
347
+ )
348
+ search_results.append(result)
349
+
350
+ # Sort by similarity score
351
+ search_results.sort(key=lambda x: x.similarity_score, reverse=True)
352
+
353
+ logger.info(f"Found {len(search_results)} similar documents for query")
354
+ return search_results
355
+
356
+ except Exception as e:
357
+ logger.error(f"Error searching vector database: {e}")
358
+ return []
359
+
360
+ def hybrid_search(self,
361
+ query: str,
362
+ n_results: int = 5,
363
+ semantic_weight: float = 0.7,
364
+ keyword_weight: float = 0.3) -> List[SearchResult]:
365
+ """Perform hybrid search combining semantic and keyword matching"""
366
+ try:
367
+ # Semantic search
368
+ semantic_results = self.search_similar(query, n_results=n_results*2)
369
+
370
+ # Keyword search (simple implementation)
371
+ keyword_results = self._keyword_search(query, n_results=n_results*2)
372
+
373
+ # Combine and rank results
374
+ combined_results = self._combine_search_results(
375
+ semantic_results,
376
+ keyword_results,
377
+ semantic_weight,
378
+ keyword_weight
379
+ )
380
+
381
+ # Return top results
382
+ return combined_results[:n_results]
383
+
384
+ except Exception as e:
385
+ logger.error(f"Error in hybrid search: {e}")
386
+ return self.search_similar(query, n_results)
387
+
388
+ def _keyword_search(self, query: str, n_results: int = 5) -> List[SearchResult]:
389
+ """Simple keyword-based search"""
390
+ try:
391
+ # Get all documents
392
+ all_results = self.collection.get()
393
+
394
+ keyword_results = []
395
+ query_terms = query.lower().split()
396
+
397
+ for i, content in enumerate(all_results['documents']):
398
+ content_lower = content.lower()
399
+
400
+ # Calculate keyword match score
401
+ matches = sum(1 for term in query_terms if term in content_lower)
402
+ if matches > 0:
403
+ score = matches / len(query_terms)
404
+
405
+ result = SearchResult(
406
+ chunk_id=all_results['ids'][i],
407
+ content=content,
408
+ source_file=all_results['metadatas'][i].get('source_file', 'unknown'),
409
+ similarity_score=score,
410
+ metadata=all_results['metadatas'][i],
411
+ section_type=all_results['metadatas'][i].get('section_type', 'text')
412
+ )
413
+ keyword_results.append(result)
414
+
415
+ # Sort by score
416
+ keyword_results.sort(key=lambda x: x.similarity_score, reverse=True)
417
+ return keyword_results[:n_results]
418
+
419
+ except Exception as e:
420
+ logger.error(f"Error in keyword search: {e}")
421
+ return []
422
+
423
+ def _combine_search_results(self,
424
+ semantic_results: List[SearchResult],
425
+ keyword_results: List[SearchResult],
426
+ semantic_weight: float,
427
+ keyword_weight: float) -> List[SearchResult]:
428
+ """Combine semantic and keyword search results"""
429
+ try:
430
+ # Create a dictionary to store combined scores
431
+ combined_scores = {}
432
+
433
+ # Add semantic results
434
+ for result in semantic_results:
435
+ combined_scores[result.chunk_id] = {
436
+ 'result': result,
437
+ 'semantic_score': result.similarity_score,
438
+ 'keyword_score': 0.0
439
+ }
440
+
441
+ # Add keyword results
442
+ for result in keyword_results:
443
+ if result.chunk_id in combined_scores:
444
+ combined_scores[result.chunk_id]['keyword_score'] = result.similarity_score
445
+ else:
446
+ combined_scores[result.chunk_id] = {
447
+ 'result': result,
448
+ 'semantic_score': 0.0,
449
+ 'keyword_score': result.similarity_score
450
+ }
451
+
452
+ # Calculate combined scores
453
+ combined_results = []
454
+ for chunk_id, scores in combined_scores.items():
455
+ combined_score = (scores['semantic_score'] * semantic_weight +
456
+ scores['keyword_score'] * keyword_weight)
457
+
458
+ # Update the result with combined score
459
+ result = scores['result']
460
+ result.similarity_score = combined_score
461
+ combined_results.append(result)
462
+
463
+ # Sort by combined score
464
+ combined_results.sort(key=lambda x: x.similarity_score, reverse=True)
465
+ return combined_results
466
+
467
+ except Exception as e:
468
+ logger.error(f"Error combining search results: {e}")
469
+ return semantic_results
470
+
471
+ def get_document_statistics(self) -> Dict[str, Any]:
472
+ """Get statistics about the vector database"""
473
+ try:
474
+ # Get collection info
475
+ count = self.collection.count()
476
+
477
+ # Get unique sources
478
+ all_results = self.collection.get()
479
+ sources = set()
480
+ file_types = set()
481
+ section_types = set()
482
+
483
+ for metadata in all_results['metadatas']:
484
+ sources.add(metadata.get('source_file', 'unknown'))
485
+ file_types.add(metadata.get('file_type', 'unknown'))
486
+ section_types.add(metadata.get('section_type', 'text'))
487
+
488
+ stats = {
489
+ 'total_chunks': count,
490
+ 'unique_sources': len(sources),
491
+ 'file_types': list(file_types),
492
+ 'section_types': list(section_types),
493
+ 'sources': list(sources),
494
+ 'embedding_model': self.embedding_model,
495
+ 'collection_name': self.collection_name
496
+ }
497
+
498
+ return stats
499
+
500
+ except Exception as e:
501
+ logger.error(f"Error getting document statistics: {e}")
502
+ return {}
503
+
504
+ def delete_documents(self, source_file: str) -> bool:
505
+ """Delete all documents from a specific source file"""
506
+ try:
507
+ # Get documents from the source
508
+ results = self.collection.get(
509
+ where={"source_file": source_file}
510
+ )
511
+
512
+ if results['ids']:
513
+ # Delete the documents
514
+ self.collection.delete(ids=results['ids'])
515
+ logger.info(f"Deleted {len(results['ids'])} chunks from {source_file}")
516
+ return True
517
+ else:
518
+ logger.warning(f"No documents found for source: {source_file}")
519
+ return False
520
+
521
+ except Exception as e:
522
+ logger.error(f"Error deleting documents: {e}")
523
+ return False
524
+
525
+ def clear_database(self) -> bool:
526
+ """Clear all documents from the database"""
527
+ try:
528
+ self.collection.delete(where={})
529
+ logger.info("Cleared all documents from vector database")
530
+ return True
531
+ except Exception as e:
532
+ logger.error(f"Error clearing database: {e}")
533
+ return False
534
+
535
+ def export_database(self, export_path: str) -> bool:
536
+ """Export database statistics and metadata"""
537
+ try:
538
+ stats = self.get_document_statistics()
539
+
540
+ with open(export_path, 'w') as f:
541
+ json.dump(stats, f, indent=2)
542
+
543
+ logger.info(f"Database exported to: {export_path}")
544
+ return True
545
+
546
+ except Exception as e:
547
+ logger.error(f"Error exporting database: {e}")
548
+ return False
549
+
550
+ # Example usage
551
+ if __name__ == "__main__":
552
+ # Initialize vector database
553
+ vector_db = VectorDatabase(use_gpu=True)
554
+
555
+ # Test search
556
+ results = vector_db.search_similar("insurance policy coverage", n_results=3)
557
+
558
+ print(f"Found {len(results)} results:")
559
+ for i, result in enumerate(results):
560
+ print(f"\nResult {i+1}:")
561
+ print(f"Score: {result.similarity_score:.3f}")
562
+ print(f"Source: {result.source_file}")
563
+ print(f"Content: {result.content[:100]}...")
564
+
565
+ # Get statistics
566
+ stats = vector_db.get_document_statistics()
567
+ print(f"\nDatabase statistics: {stats}")