Update old.py
Browse files
old.py
CHANGED
|
@@ -1,577 +1,577 @@
|
|
| 1 |
-
import streamlit as st
|
| 2 |
-
import os
|
| 3 |
-
import json
|
| 4 |
-
import re
|
| 5 |
-
from pathlib import Path
|
| 6 |
-
from datetime import datetime
|
| 7 |
-
from typing import Dict, List, Any, Optional
|
| 8 |
-
import fitz # PyMuPDF
|
| 9 |
-
from docx import Document
|
| 10 |
-
from docx.shared import Inches
|
| 11 |
-
import tempfile
|
| 12 |
-
from groq import Groq
|
| 13 |
|
| 14 |
-
# Configure Streamlit page
|
| 15 |
-
st.set_page_config(
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
)
|
| 21 |
|
| 22 |
-
class GLRProcessor:
|
| 23 |
-
|
| 24 |
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
|
| 40 |
-
|
| 41 |
-
|
| 42 |
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
|
| 73 |
-
|
| 74 |
-
|
| 75 |
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
|
| 98 |
-
|
| 99 |
-
|
| 100 |
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
|
| 139 |
-
|
| 140 |
|
| 141 |
-
REQUIRED FIELDS TO EXTRACT:
|
| 142 |
-
{chr(10).join(extraction_instructions)}
|
| 143 |
|
| 144 |
-
EXTRACTION GUIDELINES:
|
| 145 |
-
1. Search the ENTIRE document text carefully for each field
|
| 146 |
-
2. Look for variations like "Date of Loss", "Loss Date", "DOL" for DATE_LOSS
|
| 147 |
-
3. For names, extract complete full names as they appear
|
| 148 |
-
4. For addresses, look for complete street addresses with numbers
|
| 149 |
-
5. For dates, convert to MM/DD/YYYY format if in different format
|
| 150 |
-
6. For states, use 2-letter codes (California = CA, New York = NY, etc.)
|
| 151 |
-
7. Look in headers, footers, tables, and body text
|
| 152 |
-
8. If a field has multiple possible values, choose the most complete/relevant one
|
| 153 |
-
9. Only use "N/A" if the information is absolutely not present anywhere in the document
|
| 154 |
|
| 155 |
-
DOCUMENT TEXT TO ANALYZE:
|
| 156 |
-
{combined_text}
|
| 157 |
|
| 158 |
-
Return ONLY a valid JSON object with the extracted data:
|
| 159 |
-
{{
|
| 160 |
-
|
| 161 |
-
}}
|
| 162 |
|
| 163 |
-
JSON:"""
|
| 164 |
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
|
| 185 |
-
|
| 186 |
|
| 187 |
-
|
| 188 |
-
|
| 189 |
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
|
| 229 |
-
|
| 230 |
-
|
| 231 |
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
|
| 408 |
-
|
| 409 |
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
|
| 414 |
-
|
| 415 |
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
|
| 420 |
-
|
| 421 |
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
|
| 428 |
|
| 429 |
-
|
| 430 |
|
| 431 |
-
|
| 432 |
-
|
| 433 |
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
|
| 453 |
-
|
| 454 |
-
|
| 455 |
|
| 456 |
-
|
| 457 |
-
|
| 458 |
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
|
| 463 |
-
def main():
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
|
| 468 |
-
|
| 469 |
-
|
| 470 |
|
| 471 |
-
|
| 472 |
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
|
| 481 |
-
|
| 482 |
-
|
| 483 |
|
| 484 |
-
|
| 485 |
-
|
| 486 |
|
| 487 |
-
|
| 488 |
-
|
| 489 |
|
| 490 |
-
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
|
| 497 |
-
|
| 498 |
-
|
| 499 |
|
| 500 |
-
|
| 501 |
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
|
| 518 |
-
|
| 519 |
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
|
| 525 |
-
|
| 526 |
-
|
| 527 |
|
| 528 |
-
|
| 529 |
-
|
| 530 |
-
|
| 531 |
-
|
| 532 |
|
| 533 |
-
|
| 534 |
-
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
|
| 538 |
-
|
| 539 |
-
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
|
| 547 |
-
|
| 548 |
-
|
| 549 |
-
|
| 550 |
-
|
| 551 |
|
| 552 |
-
|
| 553 |
-
|
| 554 |
-
|
| 555 |
|
| 556 |
-
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
|
| 560 |
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
|
| 566 |
-
|
| 567 |
|
| 568 |
-
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
| 575 |
|
| 576 |
-
if __name__ == "__main__":
|
| 577 |
-
|
|
|
|
| 1 |
+
# import streamlit as st
|
| 2 |
+
# import os
|
| 3 |
+
# import json
|
| 4 |
+
# import re
|
| 5 |
+
# from pathlib import Path
|
| 6 |
+
# from datetime import datetime
|
| 7 |
+
# from typing import Dict, List, Any, Optional
|
| 8 |
+
# import fitz # PyMuPDF
|
| 9 |
+
# from docx import Document
|
| 10 |
+
# from docx.shared import Inches
|
| 11 |
+
# import tempfile
|
| 12 |
+
# from groq import Groq
|
| 13 |
|
| 14 |
+
# # Configure Streamlit page
|
| 15 |
+
# st.set_page_config(
|
| 16 |
+
# page_title="GLR Pipeline Automation",
|
| 17 |
+
# page_icon="π",
|
| 18 |
+
# layout="wide",
|
| 19 |
+
# initial_sidebar_state="expanded"
|
| 20 |
+
# )
|
| 21 |
|
| 22 |
+
# class GLRProcessor:
|
| 23 |
+
# """Main class for processing GLR documents using Groq API"""
|
| 24 |
|
| 25 |
+
# def __init__(self):
|
| 26 |
+
# self.output_dir = Path("output")
|
| 27 |
+
# self.output_dir.mkdir(exist_ok=True)
|
| 28 |
+
# # Get API key from Hugging Face secrets
|
| 29 |
+
# self.api_key = os.getenv("Key") # Get from HF secrets named "Key"
|
| 30 |
+
# self.groq_client = None
|
| 31 |
+
# self.initialize_groq()
|
| 32 |
|
| 33 |
+
# def initialize_groq(self):
|
| 34 |
+
# """Initialize Groq client with API key from HF secrets"""
|
| 35 |
+
# try:
|
| 36 |
+
# if not self.api_key:
|
| 37 |
+
# st.error("β API key not found in Hugging Face secrets. Please set the 'Key' secret in your Space settings.")
|
| 38 |
+
# return
|
| 39 |
|
| 40 |
+
# self.groq_client = Groq(api_key=self.api_key)
|
| 41 |
+
# st.success("β
Groq API initialized successfully!")
|
| 42 |
|
| 43 |
+
# except Exception as e:
|
| 44 |
+
# st.error(f"Failed to initialize Groq: {str(e)}")
|
| 45 |
+
# self.groq_client = None
|
| 46 |
|
| 47 |
+
# def extract_text_from_pdf(self, pdf_file) -> str:
|
| 48 |
+
# """Extract text from uploaded PDF file"""
|
| 49 |
+
# try:
|
| 50 |
+
# with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp_file:
|
| 51 |
+
# tmp_file.write(pdf_file.read())
|
| 52 |
+
# tmp_file_path = tmp_file.name
|
| 53 |
|
| 54 |
+
# doc = fitz.open(tmp_file_path)
|
| 55 |
+
# text = ""
|
| 56 |
+
# for page in doc:
|
| 57 |
+
# text += page.get_text()
|
| 58 |
+
# doc.close()
|
| 59 |
|
| 60 |
+
# os.unlink(tmp_file_path)
|
| 61 |
+
# return text.strip()
|
| 62 |
+
# except Exception as e:
|
| 63 |
+
# st.error(f"Error extracting text from PDF: {str(e)}")
|
| 64 |
+
# return ""
|
| 65 |
|
| 66 |
+
# def extract_placeholders_from_docx(self, docx_file) -> List[str]:
|
| 67 |
+
# """Extract placeholders from DOCX template"""
|
| 68 |
+
# try:
|
| 69 |
+
# with tempfile.NamedTemporaryFile(delete=False, suffix='.docx') as tmp_file:
|
| 70 |
+
# tmp_file.write(docx_file.read())
|
| 71 |
+
# tmp_file_path = tmp_file.name
|
| 72 |
|
| 73 |
+
# doc = Document(tmp_file_path)
|
| 74 |
+
# placeholders = set()
|
| 75 |
|
| 76 |
+
# for paragraph in doc.paragraphs:
|
| 77 |
+
# matches = re.findall(r'\[([A-Z_]+)\]', paragraph.text)
|
| 78 |
+
# placeholders.update(matches)
|
| 79 |
|
| 80 |
+
# for table in doc.tables:
|
| 81 |
+
# for row in table.rows:
|
| 82 |
+
# for cell in row.cells:
|
| 83 |
+
# matches = re.findall(r'\[([A-Z_]+)\]', cell.text)
|
| 84 |
+
# placeholders.update(matches)
|
| 85 |
|
| 86 |
+
# os.unlink(tmp_file_path)
|
| 87 |
+
# return list(placeholders)
|
| 88 |
+
# except Exception as e:
|
| 89 |
+
# st.error(f"Error extracting placeholders from DOCX: {str(e)}")
|
| 90 |
+
# return []
|
| 91 |
|
| 92 |
+
# def extract_data_with_groq(self, pdf_texts: List[str], placeholders: List[str]) -> Dict[str, str]:
|
| 93 |
+
# """Use Groq API to extract data from PDF texts with enhanced prompting"""
|
| 94 |
+
# if self.groq_client is None:
|
| 95 |
+
# st.warning("β οΈ Groq API not available. Using enhanced fallback extraction...")
|
| 96 |
+
# return self._enhanced_fallback_extraction("\n\n".join(pdf_texts), placeholders)
|
| 97 |
|
| 98 |
+
# try:
|
| 99 |
+
# combined_text = "\n\n".join(pdf_texts)
|
| 100 |
|
| 101 |
+
# # Truncate if too long but keep more context
|
| 102 |
+
# if len(combined_text) > 12000:
|
| 103 |
+
# combined_text = combined_text[:12000] + "..."
|
| 104 |
|
| 105 |
+
# # Create more specific field descriptions
|
| 106 |
+
# field_descriptions = {
|
| 107 |
+
# "DATE_LOSS": "Date when the loss/damage occurred (format: MM/DD/YYYY)",
|
| 108 |
+
# "DATE_RECEIVED": "Date when the claim was received (format: MM/DD/YYYY)",
|
| 109 |
+
# "DATE_INSPECTED": "Date when the property was inspected (format: MM/DD/YYYY)",
|
| 110 |
+
# "INSURED_NAME": "Full name of the insured person/entity",
|
| 111 |
+
# "STREET": "Street address of the property (number + street name + suffix like St, Ave, Rd)",
|
| 112 |
+
# "H_STREET": "Home street address (number + street name + suffix)",
|
| 113 |
+
# "INSURED_H_STREET": "Insured's home street address (number + street name + suffix like St, Ave, Rd)",
|
| 114 |
+
# "CITY": "City name",
|
| 115 |
+
# "H_CITY": "Home city name",
|
| 116 |
+
# "INSURED_H_CITY": "Insured's home city name",
|
| 117 |
+
# "STATE": "State abbreviation (2 letters like CA, NY, TX)",
|
| 118 |
+
# "H_STATE": "Home state abbreviation",
|
| 119 |
+
# "INSURED_H_STATE": "Insured's home state abbreviation",
|
| 120 |
+
# "ZIP": "ZIP code (5 or 9 digits)",
|
| 121 |
+
# "H_ZIP": "Home ZIP code",
|
| 122 |
+
# "INSURED_H_ZIP": "Insured's home ZIP code",
|
| 123 |
+
# "MORTGAGEE": "Mortgage company or lender name",
|
| 124 |
+
# "MORTGAGE": "Mortgage information",
|
| 125 |
+
# "MORTGAGE_CO": "Mortgage company name",
|
| 126 |
+
# "TOL_CODE": "Type of Loss code (alphanumeric code like F1, W2, H3, or similar insurance peril codes)",
|
| 127 |
+
# "CLAIM_NUMBER": "Insurance claim number",
|
| 128 |
+
# "POLICY_NUMBER": "Insurance policy number",
|
| 129 |
+
# "PHONE": "Phone number",
|
| 130 |
+
# "EMAIL": "Email address"
|
| 131 |
+
# }
|
| 132 |
|
| 133 |
+
# # Build detailed extraction instructions
|
| 134 |
+
# extraction_instructions = []
|
| 135 |
+
# for field in placeholders:
|
| 136 |
+
# description = field_descriptions.get(field, f"Extract the {field.lower().replace('_', ' ')}")
|
| 137 |
+
# extraction_instructions.append(f"- {field}: {description}")
|
| 138 |
|
| 139 |
+
# prompt = f"""You are an expert insurance document analyst. Extract the following specific information from this insurance report text. Be very thorough and look for variations in how the data might be presented.
|
| 140 |
|
| 141 |
+
# REQUIRED FIELDS TO EXTRACT:
|
| 142 |
+
# {chr(10).join(extraction_instructions)}
|
| 143 |
|
| 144 |
+
# EXTRACTION GUIDELINES:
|
| 145 |
+
# 1. Search the ENTIRE document text carefully for each field
|
| 146 |
+
# 2. Look for variations like "Date of Loss", "Loss Date", "DOL" for DATE_LOSS
|
| 147 |
+
# 3. For names, extract complete full names as they appear
|
| 148 |
+
# 4. For addresses, look for complete street addresses with numbers
|
| 149 |
+
# 5. For dates, convert to MM/DD/YYYY format if in different format
|
| 150 |
+
# 6. For states, use 2-letter codes (California = CA, New York = NY, etc.)
|
| 151 |
+
# 7. Look in headers, footers, tables, and body text
|
| 152 |
+
# 8. If a field has multiple possible values, choose the most complete/relevant one
|
| 153 |
+
# 9. Only use "N/A" if the information is absolutely not present anywhere in the document
|
| 154 |
|
| 155 |
+
# DOCUMENT TEXT TO ANALYZE:
|
| 156 |
+
# {combined_text}
|
| 157 |
|
| 158 |
+
# Return ONLY a valid JSON object with the extracted data:
|
| 159 |
+
# {{
|
| 160 |
+
# "FIELD_NAME": "extracted_value"
|
| 161 |
+
# }}
|
| 162 |
|
| 163 |
+
# JSON:"""
|
| 164 |
|
| 165 |
+
# # Make API call to Groq with better parameters
|
| 166 |
+
# completion = self.groq_client.chat.completions.create(
|
| 167 |
+
# model="deepseek-r1-distill-llama-70b",
|
| 168 |
+
# messages=[
|
| 169 |
+
# {
|
| 170 |
+
# "role": "system",
|
| 171 |
+
# "content": "You are an expert at extracting structured data from insurance documents. Always return valid JSON and be thorough in your extraction."
|
| 172 |
+
# },
|
| 173 |
+
# {
|
| 174 |
+
# "role": "user",
|
| 175 |
+
# "content": prompt
|
| 176 |
+
# }
|
| 177 |
+
# ],
|
| 178 |
+
# temperature=0.0, # More deterministic
|
| 179 |
+
# max_tokens=3000,
|
| 180 |
+
# top_p=0.9,
|
| 181 |
+
# stream=False,
|
| 182 |
+
# stop=None,
|
| 183 |
+
# )
|
| 184 |
|
| 185 |
+
# response = completion.choices[0].message.content
|
| 186 |
|
| 187 |
+
# # Better JSON parsing
|
| 188 |
+
# json_text = response.strip()
|
| 189 |
|
| 190 |
+
# # Remove markdown formatting
|
| 191 |
+
# if json_text.startswith("```json"):
|
| 192 |
+
# json_text = json_text[7:]
|
| 193 |
+
# if json_text.startswith("```"):
|
| 194 |
+
# json_text = json_text[3:]
|
| 195 |
+
# if json_text.endswith("```"):
|
| 196 |
+
# json_text = json_text[:-3]
|
| 197 |
+
# json_text = json_text.strip()
|
| 198 |
|
| 199 |
+
# # Extract JSON object more carefully
|
| 200 |
+
# json_match = re.search(r'\{.*\}', json_text, re.DOTALL)
|
| 201 |
+
# if json_match:
|
| 202 |
+
# json_text = json_match.group()
|
| 203 |
|
| 204 |
+
# try:
|
| 205 |
+
# extracted_data = json.loads(json_text)
|
| 206 |
+
# except json.JSONDecodeError:
|
| 207 |
+
# # Try to fix common JSON issues
|
| 208 |
+
# json_text = json_text.replace("'", '"') # Replace single quotes
|
| 209 |
+
# json_text = re.sub(r',\s*}', '}', json_text) # Remove trailing commas
|
| 210 |
+
# extracted_data = json.loads(json_text)
|
| 211 |
|
| 212 |
+
# # Ensure all placeholders are present and post-process
|
| 213 |
+
# final_data = {}
|
| 214 |
+
# for placeholder in placeholders:
|
| 215 |
+
# if placeholder in extracted_data:
|
| 216 |
+
# value = str(extracted_data[placeholder]).strip()
|
| 217 |
+
# # Clean up the value
|
| 218 |
+
# if value and value.lower() not in ['n/a', 'null', 'none', '', 'not found']:
|
| 219 |
+
# final_data[placeholder] = value
|
| 220 |
+
# else:
|
| 221 |
+
# # Try fallback extraction for this specific field
|
| 222 |
+
# fallback_value = self._extract_single_field(combined_text, placeholder)
|
| 223 |
+
# final_data[placeholder] = fallback_value
|
| 224 |
+
# else:
|
| 225 |
+
# # Try fallback extraction for missing field
|
| 226 |
+
# fallback_value = self._extract_single_field(combined_text, placeholder)
|
| 227 |
+
# final_data[placeholder] = fallback_value
|
| 228 |
|
| 229 |
+
# st.success("β
Groq AI extraction completed successfully!")
|
| 230 |
+
# return final_data
|
| 231 |
|
| 232 |
+
# except Exception as e:
|
| 233 |
+
# st.warning(f"Groq API extraction failed: {str(e)}")
|
| 234 |
+
# return self._enhanced_fallback_extraction(combined_text, placeholders)
|
| 235 |
|
| 236 |
+
# def _extract_single_field(self, text: str, field: str) -> str:
|
| 237 |
+
# """Extract a single field using enhanced pattern matching"""
|
| 238 |
+
# text_lower = text.lower()
|
| 239 |
+
# text_upper = text.upper()
|
| 240 |
|
| 241 |
+
# # Date patterns
|
| 242 |
+
# if "DATE" in field:
|
| 243 |
+
# date_patterns = [
|
| 244 |
+
# r'(?:' + field.lower().replace('_', r'[\s_]*') + r')[\s:]*(\d{1,2}[/\-]\d{1,2}[/\-]\d{2,4})',
|
| 245 |
+
# r'(?:loss|received|inspect[a-z]*|claim)[\s:]+((?:\d{1,2}[/\-]\d{1,2}[/\-]\d{2,4})|(?:[A-Za-z]+ \d{1,2}, \d{4}))',
|
| 246 |
+
# r'\b(\d{1,2}[/\-]\d{1,2}[/\-]\d{2,4})\b',
|
| 247 |
+
# r'\b([A-Za-z]+ \d{1,2}, \d{4})\b'
|
| 248 |
+
# ]
|
| 249 |
+
# for pattern in date_patterns:
|
| 250 |
+
# match = re.search(pattern, text, re.IGNORECASE)
|
| 251 |
+
# if match:
|
| 252 |
+
# return match.group(1)
|
| 253 |
|
| 254 |
+
# # Name patterns
|
| 255 |
+
# elif "NAME" in field:
|
| 256 |
+
# name_patterns = [
|
| 257 |
+
# r'(?:insured|name|policyholder)[\s:]+([A-Z][a-z]+ [A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)',
|
| 258 |
+
# r'\b([A-Z][a-z]+ [A-Z][a-z]+)\b',
|
| 259 |
+
# r'name[\s:]*([A-Z][A-Za-z\s]+?)(?:\n|address|phone|email)',
|
| 260 |
+
# ]
|
| 261 |
+
# for pattern in name_patterns:
|
| 262 |
+
# matches = re.findall(pattern, text, re.IGNORECASE)
|
| 263 |
+
# for match in matches:
|
| 264 |
+
# if len(match.split()) >= 2 and len(match) > 4:
|
| 265 |
+
# return match.strip()
|
| 266 |
|
| 267 |
+
# # Enhanced Address patterns for STREET
|
| 268 |
+
# elif "STREET" in field:
|
| 269 |
+
# street_patterns = [
|
| 270 |
+
# # Standard address patterns with numbers
|
| 271 |
+
# r'\b(\d+\s+[A-Z][A-Za-z\s]+(?:ST|STREET|AVE|AVENUE|RD|ROAD|DR|DRIVE|LN|LANE|CT|COURT|BLVD|BOULEVARD|PL|PLACE|WAY|CIR|CIRCLE|PKWY|PARKWAY)\.?)\b',
|
| 272 |
+
# # Address after keywords
|
| 273 |
+
# r'(?:address|street|location|property)[\s:]+(\d+\s+[A-Za-z][A-Za-z\s]+)',
|
| 274 |
+
# # Full address patterns
|
| 275 |
+
# r'\b(\d{1,5}\s+[A-Za-z][A-Za-z\s]+ (?:Street|Avenue|Road|Drive|Lane|Court|Boulevard|Place|Way|Circle|Parkway))\b',
|
| 276 |
+
# # Address in format: number + name + abbreviated suffix
|
| 277 |
+
# r'\b(\d{1,5}\s+[A-Za-z][A-Za-z\s]+\s+(?:St|Ave|Rd|Dr|Ln|Ct|Blvd|Pl|Way|Cir|Pkwy)\.?)\b',
|
| 278 |
+
# # Property address patterns
|
| 279 |
+
# r'(?:property|insured|loss)[\s\w]*address[\s:]*(\d+\s+[A-Za-z][A-Za-z\s]+)',
|
| 280 |
+
# # Address before city/state
|
| 281 |
+
# r'(\d+\s+[A-Za-z][A-Za-z\s]+?)(?:,\s*[A-Z][a-z]+\s*,?\s*[A-Z]{2})',
|
| 282 |
+
# # General number + street name pattern
|
| 283 |
+
# r'\b(\d{1,5}\s+[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*(?:\s+(?:St|Ave|Rd|Dr|Ln|Ct|Blvd|Pl|Way|Street|Avenue|Road|Drive|Lane|Court|Boulevard|Place))?)\b'
|
| 284 |
+
# ]
|
| 285 |
+
# for pattern in street_patterns:
|
| 286 |
+
# match = re.search(pattern, text, re.IGNORECASE)
|
| 287 |
+
# if match:
|
| 288 |
+
# street = match.group(1).strip()
|
| 289 |
+
# # Validate it looks like a street address
|
| 290 |
+
# if re.match(r'\d+\s+[A-Za-z]', street) and len(street) > 5:
|
| 291 |
+
# return street
|
| 292 |
|
| 293 |
+
# # City patterns
|
| 294 |
+
# elif "CITY" in field:
|
| 295 |
+
# city_patterns = [
|
| 296 |
+
# r'(?:city)[\s:]+([A-Z][a-z\s]+?)(?:,|\s+[A-Z]{2}\s|\n)',
|
| 297 |
+
# r',\s*([A-Z][a-z\s]+?)\s+[A-Z]{2}\s+\d{5}',
|
| 298 |
+
# r'\b([A-Z][a-z]+(?: [A-Z][a-z]+)*),\s*[A-Z]{2}\b'
|
| 299 |
+
# ]
|
| 300 |
+
# for pattern in city_patterns:
|
| 301 |
+
# match = re.search(pattern, text)
|
| 302 |
+
# if match:
|
| 303 |
+
# city = match.group(1).strip()
|
| 304 |
+
# if len(city) > 2:
|
| 305 |
+
# return city
|
| 306 |
|
| 307 |
+
# # State patterns
|
| 308 |
+
# elif "STATE" in field:
|
| 309 |
+
# state_patterns = [
|
| 310 |
+
# r'\b([A-Z]{2})\s+\d{5}',
|
| 311 |
+
# r'(?:state)[\s:]+([A-Z]{2})\b',
|
| 312 |
+
# r',\s*[A-Za-z\s]+,?\s*([A-Z]{2})\s+\d{5}'
|
| 313 |
+
# ]
|
| 314 |
+
# for pattern in state_patterns:
|
| 315 |
+
# match = re.search(pattern, text)
|
| 316 |
+
# if match:
|
| 317 |
+
# return match.group(1)
|
| 318 |
|
| 319 |
+
# # ZIP patterns
|
| 320 |
+
# elif "ZIP" in field:
|
| 321 |
+
# zip_patterns = [
|
| 322 |
+
# r'\b(\d{5}-\d{4})\b',
|
| 323 |
+
# r'\b(\d{5})\b(?!\d)'
|
| 324 |
+
# ]
|
| 325 |
+
# for pattern in zip_patterns:
|
| 326 |
+
# match = re.search(pattern, text)
|
| 327 |
+
# if match:
|
| 328 |
+
# return match.group(1)
|
| 329 |
|
| 330 |
+
# # Phone patterns
|
| 331 |
+
# elif "PHONE" in field:
|
| 332 |
+
# phone_patterns = [
|
| 333 |
+
# r'\b(\d{3}[-.\s]?\d{3}[-.\s]?\d{4})\b',
|
| 334 |
+
# r'\((\d{3})\)\s*(\d{3})[-.\s]?(\d{4})'
|
| 335 |
+
# ]
|
| 336 |
+
# for pattern in phone_patterns:
|
| 337 |
+
# match = re.search(pattern, text)
|
| 338 |
+
# if match:
|
| 339 |
+
# if len(match.groups()) == 1:
|
| 340 |
+
# return match.group(1)
|
| 341 |
+
# else:
|
| 342 |
+
# return f"({match.group(1)}) {match.group(2)}-{match.group(3)}"
|
| 343 |
|
| 344 |
+
# # Email patterns
|
| 345 |
+
# elif "EMAIL" in field:
|
| 346 |
+
# email_pattern = r'\b([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})\b'
|
| 347 |
+
# match = re.search(email_pattern, text)
|
| 348 |
+
# if match:
|
| 349 |
+
# return match.group(1)
|
| 350 |
|
| 351 |
+
# # Policy/Claim number patterns
|
| 352 |
+
# elif "POLICY" in field or "CLAIM" in field:
|
| 353 |
+
# number_patterns = [
|
| 354 |
+
# r'(?:policy|claim)[\s#:]*([A-Z0-9\-]+)',
|
| 355 |
+
# r'\b([A-Z]{2,4}\d{6,})\b',
|
| 356 |
+
# r'\b(\d{8,})\b'
|
| 357 |
+
# ]
|
| 358 |
+
# for pattern in number_patterns:
|
| 359 |
+
# match = re.search(pattern, text, re.IGNORECASE)
|
| 360 |
+
# if match:
|
| 361 |
+
# return match.group(1)
|
| 362 |
|
| 363 |
+
# # Mortgage patterns
|
| 364 |
+
# elif "MORTGAGE" in field:
|
| 365 |
+
# mortgage_patterns = [
|
| 366 |
+
# r'(?:mortgage[e]?|lender)[\s:]+([A-Z][A-Za-z\s&]+?)(?:\n|$|address)',
|
| 367 |
+
# r'\b([A-Z][a-z]+ (?:Bank|Mortgage|Financial|Credit Union|Lending))\b'
|
| 368 |
+
# ]
|
| 369 |
+
# for pattern in mortgage_patterns:
|
| 370 |
+
# match = re.search(pattern, text, re.IGNORECASE)
|
| 371 |
+
# if match:
|
| 372 |
+
# return match.group(1).strip()
|
| 373 |
|
| 374 |
+
# # Enhanced TOL Code patterns
|
| 375 |
+
# elif "TOL" in field or "CODE" in field:
|
| 376 |
+
# code_patterns = [
|
| 377 |
+
# # Direct TOL/code patterns
|
| 378 |
+
# r'(?:tol|type\s*of\s*loss)[\s:]*code[\s:]*([A-Z0-9\-]+)',
|
| 379 |
+
# r'(?:tol|code)[\s:]*([A-Z0-9\-]+)',
|
| 380 |
+
# r'(?:type\s*of\s*loss)[\s:]*([A-Z0-9\-]+)',
|
| 381 |
+
# # Loss type codes
|
| 382 |
+
# r'(?:loss\s*type|cause\s*of\s*loss)[\s:]*([A-Z0-9\-]+)',
|
| 383 |
+
# # Peril codes
|
| 384 |
+
# r'(?:peril|coverage)[\s:]*code[\s:]*([A-Z0-9\-]+)',
|
| 385 |
+
# # General code patterns in insurance context
|
| 386 |
+
# r'(?:claim|loss|damage)[\s\w]*code[\s:]*([A-Z0-9\-]+)',
|
| 387 |
+
# # Alphanumeric codes (common format)
|
| 388 |
+
# r'\b([A-Z]{1,3}\d{1,4})\b',
|
| 389 |
+
# r'\b([A-Z]{2,4}-?\d{2,4})\b',
|
| 390 |
+
# # Fire, water, wind codes
|
| 391 |
+
# r'(?:fire|water|wind|storm|hail)[\s:]*([A-Z0-9\-]+)',
|
| 392 |
+
# # Coverage codes
|
| 393 |
+
# r'(?:coverage|section)[\s:]*([A-Z]\d*)',
|
| 394 |
+
# # Standalone codes that might be TOL
|
| 395 |
+
# r'\b([A-Z]\d{2,3})\b',
|
| 396 |
+
# r'\b([A-Z]{2}\d{1,2})\b'
|
| 397 |
+
# ]
|
| 398 |
+
# for pattern in code_patterns:
|
| 399 |
+
# matches = re.findall(pattern, text, re.IGNORECASE)
|
| 400 |
+
# for match in matches:
|
| 401 |
+
# # Filter out common false positives
|
| 402 |
+
# if (len(match) >= 2 and
|
| 403 |
+
# not match.lower() in ['tx', 'ca', 'ny', 'fl'] and # state codes
|
| 404 |
+
# not match.isdigit() and # pure numbers
|
| 405 |
+
# not re.match(r'^\d{5}$', match)): # zip codes
|
| 406 |
+
# return match.upper()
|
| 407 |
|
| 408 |
+
# return "N/A"
|
| 409 |
|
| 410 |
+
# def _enhanced_fallback_extraction(self, text: str, placeholders: List[str]) -> Dict[str, str]:
|
| 411 |
+
# """Enhanced fallback extraction with better pattern matching"""
|
| 412 |
+
# st.info("π Using enhanced pattern matching extraction...")
|
| 413 |
|
| 414 |
+
# fallback_data = {}
|
| 415 |
|
| 416 |
+
# for placeholder in placeholders:
|
| 417 |
+
# value = self._extract_single_field(text, placeholder)
|
| 418 |
+
# fallback_data[placeholder] = value
|
| 419 |
|
| 420 |
+
# return fallback_data
|
| 421 |
|
| 422 |
+
# def populate_docx_template(self, docx_file, extracted_data: Dict[str, str]) -> str:
|
| 423 |
+
# """Populate DOCX template with extracted data"""
|
| 424 |
+
# try:
|
| 425 |
+
# with tempfile.NamedTemporaryFile(delete=False, suffix='.docx') as tmp_file:
|
| 426 |
+
# tmp_file.write(docx_file.read())
|
| 427 |
+
# tmp_file_path = tmp_file.name
|
| 428 |
|
| 429 |
+
# doc = Document(tmp_file_path)
|
| 430 |
|
| 431 |
+
# # Track replacements made
|
| 432 |
+
# replacements_made = 0
|
| 433 |
|
| 434 |
+
# for paragraph in doc.paragraphs:
|
| 435 |
+
# for placeholder, value in extracted_data.items():
|
| 436 |
+
# if f"[{placeholder}]" in paragraph.text:
|
| 437 |
+
# paragraph.text = paragraph.text.replace(f"[{placeholder}]", str(value))
|
| 438 |
+
# replacements_made += 1
|
| 439 |
|
| 440 |
+
# for table in doc.tables:
|
| 441 |
+
# for row in table.rows:
|
| 442 |
+
# for cell in row.cells:
|
| 443 |
+
# for paragraph in cell.paragraphs:
|
| 444 |
+
# for placeholder, value in extracted_data.items():
|
| 445 |
+
# if f"[{placeholder}]" in paragraph.text:
|
| 446 |
+
# paragraph.text = paragraph.text.replace(f"[{placeholder}]", str(value))
|
| 447 |
+
# replacements_made += 1
|
| 448 |
|
| 449 |
+
# timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 450 |
+
# output_filename = f"GLR_Report_{timestamp}.docx"
|
| 451 |
+
# output_path = self.output_dir / output_filename
|
| 452 |
|
| 453 |
+
# doc.save(str(output_path))
|
| 454 |
+
# os.unlink(tmp_file_path)
|
| 455 |
|
| 456 |
+
# st.info(f"Made {replacements_made} field replacements in the template")
|
| 457 |
+
# return str(output_path)
|
| 458 |
|
| 459 |
+
# except Exception as e:
|
| 460 |
+
# st.error(f"Error populating template: {str(e)}")
|
| 461 |
+
# return None
|
| 462 |
|
| 463 |
+
# def main():
|
| 464 |
+
# """Main Streamlit application"""
|
| 465 |
+
# st.title("π GLR Pipeline Automation (Enhanced)")
|
| 466 |
+
# st.markdown("**Automate General Loss Report generation with improved data extraction**")
|
| 467 |
|
| 468 |
+
# # Initialize processor
|
| 469 |
+
# processor = GLRProcessor()
|
| 470 |
|
| 471 |
+
# col1, col2 = st.columns([1, 1])
|
| 472 |
|
| 473 |
+
# with col1:
|
| 474 |
+
# st.header("π Upload Template")
|
| 475 |
+
# template_file = st.file_uploader("Upload GLR Template (.docx)", type=['docx'])
|
| 476 |
|
| 477 |
+
# with col2:
|
| 478 |
+
# st.header("π Upload Reports")
|
| 479 |
+
# photo_reports = st.file_uploader("Upload PDF Reports", type=['pdf'], accept_multiple_files=True)
|
| 480 |
|
| 481 |
+
# if template_file and photo_reports:
|
| 482 |
+
# st.header("π Processing")
|
| 483 |
|
| 484 |
+
# with st.spinner("Processing..."):
|
| 485 |
+
# placeholders = processor.extract_placeholders_from_docx(template_file)
|
| 486 |
|
| 487 |
+
# if placeholders:
|
| 488 |
+
# st.success(f"Found {len(placeholders)} placeholders: {', '.join(placeholders)}")
|
| 489 |
|
| 490 |
+
# pdf_texts = []
|
| 491 |
+
# for pdf_file in photo_reports:
|
| 492 |
+
# text = processor.extract_text_from_pdf(pdf_file)
|
| 493 |
+
# if text:
|
| 494 |
+
# pdf_texts.append(text)
|
| 495 |
+
# st.success(f"β
Processed {pdf_file.name} ({len(text)} characters)")
|
| 496 |
|
| 497 |
+
# if pdf_texts:
|
| 498 |
+
# extracted_data = processor.extract_data_with_groq(pdf_texts, placeholders)
|
| 499 |
|
| 500 |
+
# st.subheader("π Extracted Data")
|
| 501 |
|
| 502 |
+
# # Show extraction statistics
|
| 503 |
+
# non_na_count = sum(1 for v in extracted_data.values() if v != "N/A")
|
| 504 |
+
# extraction_rate = (non_na_count / len(extracted_data)) * 100
|
| 505 |
+
# st.metric("Extraction Success Rate", f"{extraction_rate:.1f}%", f"{non_na_count}/{len(extracted_data)} fields")
|
| 506 |
|
| 507 |
+
# # Create form for editing data
|
| 508 |
+
# with st.form("data_form"):
|
| 509 |
+
# edited_data = {}
|
| 510 |
+
# for placeholder, value in extracted_data.items():
|
| 511 |
+
# # Color code based on whether value was found
|
| 512 |
+
# if value != "N/A":
|
| 513 |
+
# st.markdown(f"**{placeholder}** β
")
|
| 514 |
+
# else:
|
| 515 |
+
# st.markdown(f"**{placeholder}** β")
|
| 516 |
+
# edited_data[placeholder] = st.text_input(f"", value=value, key=placeholder)
|
| 517 |
|
| 518 |
+
# generate_report = st.form_submit_button("Generate Report", type="primary")
|
| 519 |
|
| 520 |
+
# # Handle report generation outside the form
|
| 521 |
+
# if generate_report:
|
| 522 |
+
# template_file.seek(0)
|
| 523 |
+
# output_path = processor.populate_docx_template(template_file, edited_data)
|
| 524 |
|
| 525 |
+
# if output_path:
|
| 526 |
+
# st.success("β
Report generated successfully!")
|
| 527 |
|
| 528 |
+
# # Store the file data in session state for download
|
| 529 |
+
# with open(output_path, 'rb') as file:
|
| 530 |
+
# st.session_state['report_data'] = file.read()
|
| 531 |
+
# st.session_state['report_filename'] = os.path.basename(output_path)
|
| 532 |
|
| 533 |
+
# # Download button outside the form
|
| 534 |
+
# if 'report_data' in st.session_state:
|
| 535 |
+
# st.download_button(
|
| 536 |
+
# label="π₯ Download Generated Report",
|
| 537 |
+
# data=st.session_state['report_data'],
|
| 538 |
+
# file_name=st.session_state['report_filename'],
|
| 539 |
+
# mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
| 540 |
+
# type="primary"
|
| 541 |
+
# )
|
| 542 |
+
# else:
|
| 543 |
+
# st.error("No text extracted from PDFs")
|
| 544 |
+
# else:
|
| 545 |
+
# st.error("No placeholders found in template")
|
| 546 |
|
| 547 |
+
# # Instructions and setup for Hugging Face Spaces
|
| 548 |
+
# st.header("π Hugging Face Spaces Setup")
|
| 549 |
+
# st.markdown("""
|
| 550 |
+
# **This app is configured for Hugging Face Spaces deployment:**
|
| 551 |
|
| 552 |
+
# **Step 1: Set up your Space**
|
| 553 |
+
# - Create a new Space on Hugging Face with Streamlit SDK
|
| 554 |
+
# - Upload the app files (app.py, requirements.txt, packages.txt)
|
| 555 |
|
| 556 |
+
# **Step 2: Configure API Key**
|
| 557 |
+
# - Go to your Space Settings β Repository secrets
|
| 558 |
+
# - Add a new secret named `Key` with your Groq API key value
|
| 559 |
+
# - Get your free API key from [console.groq.com](https://console.groq.com/)
|
| 560 |
|
| 561 |
+
# **Step 3: Use the App**
|
| 562 |
+
# 1. Upload a DOCX template with placeholders like `[DATE_LOSS]`, `[INSURED_NAME]`
|
| 563 |
+
# 2. Upload PDF reports containing the data to extract
|
| 564 |
+
# 3. Review and edit the AI-extracted data
|
| 565 |
+
# 4. Generate and download the completed report
|
| 566 |
+
# """)
|
| 567 |
|
| 568 |
+
# # Show API key status
|
| 569 |
+
# if not processor.api_key:
|
| 570 |
+
# st.error("β οΈ **API Key Not Found** - Please set the 'Key' secret in your Hugging Face Space settings.")
|
| 571 |
+
# elif processor.groq_client:
|
| 572 |
+
# st.success("β
**API Key Configured** - Ready to process documents!")
|
| 573 |
+
# else:
|
| 574 |
+
# st.error("β **API Key Error** - Please check your API key configuration in Space secrets")
|
| 575 |
|
| 576 |
+
# if __name__ == "__main__":
|
| 577 |
+
# main()
|