Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -6,7 +6,6 @@ from word2number import w2n
|
|
| 6 |
import re
|
| 7 |
from typing import Tuple, List, Dict
|
| 8 |
from simple_salesforce import Salesforce
|
| 9 |
-
import os
|
| 10 |
import base64
|
| 11 |
from io import BytesIO
|
| 12 |
import uuid
|
|
@@ -16,20 +15,13 @@ from reportlab.lib.pagesizes import letter
|
|
| 16 |
from reportlab.pdfgen import canvas
|
| 17 |
from reportlab.lib.units import inch
|
| 18 |
import time
|
|
|
|
|
|
|
| 19 |
|
| 20 |
# Set up logging
|
| 21 |
logging.basicConfig(level=logging.INFO)
|
| 22 |
logger = logging.getLogger(__name__)
|
| 23 |
|
| 24 |
-
# Configuration for public URL base
|
| 25 |
-
CONFIG = {
|
| 26 |
-
"PUBLIC_URL_BASE": "https://huggingface.co/spaces/YourUsername/YourSpace/resolve/main/static/output/",
|
| 27 |
-
"OUTPUT_DIR": "static/output"
|
| 28 |
-
}
|
| 29 |
-
|
| 30 |
-
# Ensure output directory exists
|
| 31 |
-
os.makedirs(CONFIG["OUTPUT_DIR"], exist_ok=True)
|
| 32 |
-
|
| 33 |
# Custom CSS for styling with dark mode compatibility
|
| 34 |
css = """
|
| 35 |
:root {
|
|
@@ -223,8 +215,16 @@ body {
|
|
| 223 |
transition: all 0.2s ease;
|
| 224 |
}
|
| 225 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
.count-item:hover {
|
| 227 |
-
background-color: rgba(0,
|
| 228 |
transform: translateX(5px);
|
| 229 |
}
|
| 230 |
|
|
@@ -256,20 +256,20 @@ button {
|
|
| 256 |
button:hover {
|
| 257 |
background: linear-gradient(135deg, var(--secondary-color), var(--primary-color)) !important;
|
| 258 |
transform: translateY(-2px) !important;
|
| 259 |
-
box-shadow: 0 6px 12px rgba(0,0,0,0.15)
|
| 260 |
}
|
| 261 |
|
| 262 |
.upload-area {
|
| 263 |
border: 2px dashed var(--primary-color) !important;
|
| 264 |
-
background-color: rgba(240, 248, 255, 0.3)
|
| 265 |
border-radius: 10px !important;
|
| 266 |
padding: 30px !important;
|
| 267 |
transition: all 0.3s ease !important;
|
| 268 |
}
|
| 269 |
|
| 270 |
.upload-area:hover {
|
| 271 |
-
background-color: rgba(224,
|
| 272 |
-
border-color: var(--secondary-color)
|
| 273 |
}
|
| 274 |
|
| 275 |
.risk-meter {
|
|
@@ -392,11 +392,9 @@ def get_hugging_face_sentiment(text: str) -> float:
|
|
| 392 |
logger.error(f"Hugging Face sentiment analysis failed: {str(e)}. Using fallback score.")
|
| 393 |
return 0.5
|
| 394 |
|
| 395 |
-
def generate_sentiment_pdf(sentiment_score: float
|
| 396 |
-
"""Generate a PDF with sentiment analysis results and return
|
| 397 |
try:
|
| 398 |
-
pdf_filename = f"sentiment_analysis_{int(time.time())}.pdf"
|
| 399 |
-
pdf_path = os.path.join(output_dir, pdf_filename)
|
| 400 |
pdf_file = BytesIO()
|
| 401 |
c = canvas.Canvas(pdf_file, pagesize=letter)
|
| 402 |
|
|
@@ -428,16 +426,11 @@ def generate_sentiment_pdf(sentiment_score: float, output_dir: str) -> tuple[str
|
|
| 428 |
|
| 429 |
c.save()
|
| 430 |
pdf_file.seek(0)
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
f.write(pdf_file.getvalue())
|
| 434 |
-
|
| 435 |
-
public_url = f"{CONFIG['PUBLIC_URL_BASE']}{pdf_filename}"
|
| 436 |
-
logger.info(f"PDF generated: {public_url}")
|
| 437 |
-
return pdf_path, public_url, pdf_file
|
| 438 |
except Exception as e:
|
| 439 |
logger.error(f"Error generating PDF: {str(e)}")
|
| 440 |
-
return
|
| 441 |
|
| 442 |
def save_to_salesforce(sf: Salesforce, data: Dict) -> str:
|
| 443 |
"""Save analysis results to Salesforce, return record ID"""
|
|
@@ -721,10 +714,16 @@ def analyze_pdf(file_obj) -> List:
|
|
| 721 |
salesforce_id = "N/A"
|
| 722 |
|
| 723 |
try:
|
| 724 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 725 |
except Exception as e:
|
| 726 |
logger.error(f"PDF generation failed: {str(e)}")
|
| 727 |
-
|
| 728 |
|
| 729 |
box_class = "success-box" if risk_level == "Low" else "warning-box" if risk_level == "Medium" else "danger-box"
|
| 730 |
risk_icon = "✅" if risk_level == "Low" else "⚠" if risk_level == "Medium" else "🚨"
|
|
@@ -734,7 +733,7 @@ def analyze_pdf(file_obj) -> List:
|
|
| 734 |
"High": "This contract is high risk! Immediate legal review required."
|
| 735 |
}
|
| 736 |
|
| 737 |
-
#
|
| 738 |
sentiment_analysis_output = f"""
|
| 739 |
<div class='result-box'>
|
| 740 |
<div class='section-title'>📊 Sentiment Analysis</div>
|
|
@@ -744,8 +743,7 @@ def analyze_pdf(file_obj) -> List:
|
|
| 744 |
</div>
|
| 745 |
{sentiment_meter}
|
| 746 |
<div style='margin-top: 15px;'>
|
| 747 |
-
<strong>
|
| 748 |
-
{'<a href="' + public_url + '" target="_blank" style="color: var(--primary-color); text-decoration: underline;">Download PDF</a>' if public_url else 'Failed to generate PDF download link'}
|
| 749 |
</div>
|
| 750 |
</div>
|
| 751 |
"""
|
|
@@ -770,7 +768,8 @@ def analyze_pdf(file_obj) -> List:
|
|
| 770 |
obligation_details,
|
| 771 |
delay_details,
|
| 772 |
f"<div class='result-box'><div class='section-title'>📜 Extracted Data</div>{extracted_data}</div>",
|
| 773 |
-
sentiment_analysis_output
|
|
|
|
| 774 |
]
|
| 775 |
except Exception as e:
|
| 776 |
logger.error(f"Analysis failed: {str(e)}")
|
|
@@ -786,7 +785,14 @@ def analyze_pdf(file_obj) -> List:
|
|
| 786 |
</div>
|
| 787 |
</div>
|
| 788 |
"""
|
| 789 |
-
return [error_message] *
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 790 |
|
| 791 |
# Create Gradio interface with dark mode compatibility
|
| 792 |
with gr.Blocks(css=css, title="PDF Contract Risk Analyzer", theme=gr.themes.Default(primary_hue="blue")) as demo:
|
|
@@ -833,6 +839,7 @@ with gr.Blocks(css=css, title="PDF Contract Risk Analyzer", theme=gr.themes.Defa
|
|
| 833 |
|
| 834 |
with gr.Row():
|
| 835 |
sentiment_analysis = gr.HTML(label="Sentiment Analysis")
|
|
|
|
| 836 |
|
| 837 |
submit_btn.click(
|
| 838 |
fn=analyze_pdf,
|
|
@@ -841,7 +848,7 @@ with gr.Blocks(css=css, title="PDF Contract Risk Analyzer", theme=gr.themes.Defa
|
|
| 841 |
risk_summary, risk_visualization,
|
| 842 |
penalty_count, penalty_amounts,
|
| 843 |
obligation_count, delay_count,
|
| 844 |
-
extracted_data, sentiment_analysis
|
| 845 |
]
|
| 846 |
)
|
| 847 |
|
|
|
|
| 6 |
import re
|
| 7 |
from typing import Tuple, List, Dict
|
| 8 |
from simple_salesforce import Salesforce
|
|
|
|
| 9 |
import base64
|
| 10 |
from io import BytesIO
|
| 11 |
import uuid
|
|
|
|
| 15 |
from reportlab.pdfgen import canvas
|
| 16 |
from reportlab.lib.units import inch
|
| 17 |
import time
|
| 18 |
+
import tempfile
|
| 19 |
+
import os
|
| 20 |
|
| 21 |
# Set up logging
|
| 22 |
logging.basicConfig(level=logging.INFO)
|
| 23 |
logger = logging.getLogger(__name__)
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
# Custom CSS for styling with dark mode compatibility
|
| 26 |
css = """
|
| 27 |
:root {
|
|
|
|
| 215 |
transition: all 0.2s ease;
|
| 216 |
}
|
| 217 |
|
| 218 |
+
.count-item {
|
| 219 |
+
display: flex;
|
| 220 |
+
justify-content: flex-start;
|
| 221 |
+
align-items: center;
|
| 222 |
+
padding: 10px 0;
|
| 223 |
+
gap: 10px;
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
.count-item:hover {
|
| 227 |
+
background-color: rgba(0,255,255,0.05);
|
| 228 |
transform: translateX(5px);
|
| 229 |
}
|
| 230 |
|
|
|
|
| 256 |
button:hover {
|
| 257 |
background: linear-gradient(135deg, var(--secondary-color), var(--primary-color)) !important;
|
| 258 |
transform: translateY(-2px) !important;
|
| 259 |
+
box-shadow: 0 6px 12px rgba(0,0,0,0.15)!important;
|
| 260 |
}
|
| 261 |
|
| 262 |
.upload-area {
|
| 263 |
border: 2px dashed var(--primary-color) !important;
|
| 264 |
+
background-color: rgba(240, 248, 255, 0.3)!important;
|
| 265 |
border-radius: 10px !important;
|
| 266 |
padding: 30px !important;
|
| 267 |
transition: all 0.3s ease !important;
|
| 268 |
}
|
| 269 |
|
| 270 |
.upload-area:hover {
|
| 271 |
+
background-color: rgba(224,255,255,0.3)!important;
|
| 272 |
+
border-color: var(--secondary-color)!important;
|
| 273 |
}
|
| 274 |
|
| 275 |
.risk-meter {
|
|
|
|
| 392 |
logger.error(f"Hugging Face sentiment analysis failed: {str(e)}. Using fallback score.")
|
| 393 |
return 0.5
|
| 394 |
|
| 395 |
+
def generate_sentiment_pdf(sentiment_score: float) -> BytesIO:
|
| 396 |
+
"""Generate a PDF with sentiment analysis results in memory and return BytesIO buffer"""
|
| 397 |
try:
|
|
|
|
|
|
|
| 398 |
pdf_file = BytesIO()
|
| 399 |
c = canvas.Canvas(pdf_file, pagesize=letter)
|
| 400 |
|
|
|
|
| 426 |
|
| 427 |
c.save()
|
| 428 |
pdf_file.seek(0)
|
| 429 |
+
logger.info("PDF generated in memory")
|
| 430 |
+
return pdf_file
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
except Exception as e:
|
| 432 |
logger.error(f"Error generating PDF: {str(e)}")
|
| 433 |
+
return None
|
| 434 |
|
| 435 |
def save_to_salesforce(sf: Salesforce, data: Dict) -> str:
|
| 436 |
"""Save analysis results to Salesforce, return record ID"""
|
|
|
|
| 714 |
salesforce_id = "N/A"
|
| 715 |
|
| 716 |
try:
|
| 717 |
+
pdf_buffer = generate_sentiment_pdf(sentiment_score)
|
| 718 |
+
if pdf_buffer is None:
|
| 719 |
+
raise Exception("Failed to generate PDF")
|
| 720 |
+
# Save to a temporary file for Gradio to serve
|
| 721 |
+
with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as temp_file:
|
| 722 |
+
temp_file.write(pdf_buffer.getvalue())
|
| 723 |
+
temp_file_path = temp_file.name
|
| 724 |
except Exception as e:
|
| 725 |
logger.error(f"PDF generation failed: {str(e)}")
|
| 726 |
+
temp_file_path = None
|
| 727 |
|
| 728 |
box_class = "success-box" if risk_level == "Low" else "warning-box" if risk_level == "Medium" else "danger-box"
|
| 729 |
risk_icon = "✅" if risk_level == "Low" else "⚠" if risk_level == "Medium" else "🚨"
|
|
|
|
| 733 |
"High": "This contract is high risk! Immediate legal review required."
|
| 734 |
}
|
| 735 |
|
| 736 |
+
# Sentiment analysis output with PDF download prompt
|
| 737 |
sentiment_analysis_output = f"""
|
| 738 |
<div class='result-box'>
|
| 739 |
<div class='section-title'>📊 Sentiment Analysis</div>
|
|
|
|
| 743 |
</div>
|
| 744 |
{sentiment_meter}
|
| 745 |
<div style='margin-top: 15px;'>
|
| 746 |
+
<strong>Sentiment Report:</strong> {'' if temp_file_path else 'Failed to generate PDF'}
|
|
|
|
| 747 |
</div>
|
| 748 |
</div>
|
| 749 |
"""
|
|
|
|
| 768 |
obligation_details,
|
| 769 |
delay_details,
|
| 770 |
f"<div class='result-box'><div class='section-title'>📜 Extracted Data</div>{extracted_data}</div>",
|
| 771 |
+
sentiment_analysis_output,
|
| 772 |
+
temp_file_path # Return temporary file path for PDF download
|
| 773 |
]
|
| 774 |
except Exception as e:
|
| 775 |
logger.error(f"Analysis failed: {str(e)}")
|
|
|
|
| 785 |
</div>
|
| 786 |
</div>
|
| 787 |
"""
|
| 788 |
+
return [error_message] * 9
|
| 789 |
+
finally:
|
| 790 |
+
if temp_file_path and os.path.exists(temp_file_path):
|
| 791 |
+
try:
|
| 792 |
+
os.remove(temp_file_path)
|
| 793 |
+
logger.info(f"Cleaned up temporary PDF file: {temp_file_path}")
|
| 794 |
+
except Exception as e:
|
| 795 |
+
logger.error(f"Failed to clean up temporary PDF file {temp_file_path}: {e}")
|
| 796 |
|
| 797 |
# Create Gradio interface with dark mode compatibility
|
| 798 |
with gr.Blocks(css=css, title="PDF Contract Risk Analyzer", theme=gr.themes.Default(primary_hue="blue")) as demo:
|
|
|
|
| 839 |
|
| 840 |
with gr.Row():
|
| 841 |
sentiment_analysis = gr.HTML(label="Sentiment Analysis")
|
| 842 |
+
pdf_output = gr.File(label="Download Sentiment Report PDF", file_types=[".pdf"])
|
| 843 |
|
| 844 |
submit_btn.click(
|
| 845 |
fn=analyze_pdf,
|
|
|
|
| 848 |
risk_summary, risk_visualization,
|
| 849 |
penalty_count, penalty_amounts,
|
| 850 |
obligation_count, delay_count,
|
| 851 |
+
extracted_data, sentiment_analysis, pdf_output
|
| 852 |
]
|
| 853 |
)
|
| 854 |
|