Kushalmanda commited on
Commit
cd8aaa1
·
verified ·
1 Parent(s): 2338bb6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +186 -68
app.py CHANGED
@@ -17,6 +17,7 @@ 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)
@@ -36,7 +37,6 @@ css = """
36
  --danger-color: #f44336;
37
  --info-color: #2196F3;
38
  }
39
-
40
  .dark {
41
  --primary-color: #4a89dc;
42
  --secondary-color: #3b7dd8;
@@ -49,7 +49,6 @@ css = """
49
  --danger-color: #F44336;
50
  --info-color: #2196F3;
51
  }
52
-
53
  body {
54
  background-image: url('https://images.unsplash.com/photo-1604147706283-d7119b5b822c?ixlib=rb-1.2.1&auto=format&fit=crop&w=1920&q=80');
55
  background-size: cover;
@@ -62,7 +61,6 @@ body {
62
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
63
  color: var(--text-color);
64
  }
65
-
66
  .gradio-container {
67
  background-color: rgba(var(--bg-color), 0.97) !important;
68
  border-radius: 15px;
@@ -73,11 +71,9 @@ body {
73
  min-height: 90vh;
74
  border: 1px solid var(--primary-color) !important;
75
  }
76
-
77
  .risk-low { color: var(--success-color); font-weight: bold; }
78
  .risk-medium { color: var(--warning-color); font-weight: bold; }
79
  .risk-high { color: var(--danger-color); font-weight: bold; }
80
-
81
  .result-box {
82
  padding: 20px;
83
  border-radius: 10px;
@@ -87,7 +83,6 @@ body {
87
  border-left: 5px solid var(--primary-color) !important;
88
  color: var(--text-color);
89
  }
90
-
91
  .penalty-box {
92
  padding: 20px;
93
  border-radius: 10px;
@@ -96,7 +91,6 @@ body {
96
  background-color: var(--box-bg);
97
  box-shadow: 0 2px 8px rgba(0,0,0,0.08);
98
  }
99
-
100
  .obligation-box {
101
  padding: 20px;
102
  border-radius: 10px;
@@ -105,7 +99,6 @@ body {
105
  background-color: var(--box-bg);
106
  box-shadow: 0 2px 8px rgba(0,0,0,0.08);
107
  }
108
-
109
  .delay-box {
110
  padding: 20px;
111
  border-radius: 10px;
@@ -114,7 +107,6 @@ body {
114
  background-color: var(--box-bg);
115
  box-shadow: 0 2px 8px rgba(0,0,0,0.08);
116
  }
117
-
118
  .combined-risk-container {
119
  display: flex;
120
  flex-direction: column;
@@ -125,7 +117,6 @@ body {
125
  border-radius: 10px;
126
  box-shadow: 0 2px 8px rgba(0,0,0,0.08);
127
  }
128
-
129
  .risk-row {
130
  display: flex;
131
  align-items: center;
@@ -136,19 +127,16 @@ body {
136
  border: 1px solid var(--border-color) !important;
137
  transition: all 0.3s ease;
138
  }
139
-
140
  .risk-row:hover {
141
  transform: translateY(-2px);
142
  box-shadow: 0 4px 12px rgba(0,0,0,0.1);
143
  }
144
-
145
  .risk-label {
146
  width: 150px;
147
  font-weight: 600;
148
  font-size: 16px;
149
  color: var(--text-color) !important;
150
  }
151
-
152
  .risk-score {
153
  width: 120px;
154
  font-size: 20px;
@@ -156,7 +144,6 @@ body {
156
  padding: 8px 12px;
157
  border-radius: 6px;
158
  }
159
-
160
  .warning-box {
161
  padding: 18px;
162
  border-radius: 8px;
@@ -166,7 +153,6 @@ body {
166
  font-weight: 600;
167
  box-shadow: 0 2px 8px rgba(0,0,0,0.05);
168
  }
169
-
170
  .danger-box {
171
  padding: 18px;
172
  border-radius: 8px;
@@ -176,7 +162,6 @@ body {
176
  font-weight: 600;
177
  box-shadow: 0 2px 8px rgba(0,0,0,0.05);
178
  }
179
-
180
  .success-box {
181
  padding: 18px;
182
  border-radius: 8px;
@@ -186,7 +171,6 @@ body {
186
  font-weight: 600;
187
  box-shadow: 0 2px 8px rgba(0,0,0,0.05);
188
  }
189
-
190
  .info-box {
191
  padding: 18px;
192
  border-radius: 8px;
@@ -196,7 +180,6 @@ body {
196
  font-weight: 600;
197
  box-shadow: 0 2px 8px rgba(0,0,0,0.05);
198
  }
199
-
200
  .section-title {
201
  font-size: 22px;
202
  font-weight: 700;
@@ -206,7 +189,6 @@ body {
206
  align-items: center;
207
  gap: 10px;
208
  }
209
-
210
  .count-item {
211
  display: flex;
212
  justify-content: space-between;
@@ -214,12 +196,10 @@ body {
214
  border-bottom: 1px solid var(--border-color) !important;
215
  transition: all 0.2s ease;
216
  }
217
-
218
  .count-item:hover {
219
  background-color: rgba(0,0,0,0.05);
220
  transform: translateX(5px);
221
  }
222
-
223
  .count-label {
224
  font-weight: 600;
225
  color: var(--text-color) !important;
@@ -227,13 +207,11 @@ body {
227
  align-items: center;
228
  gap: 8px;
229
  }
230
-
231
  .count-value {
232
  color: var(--secondary-color) !important;
233
  font-weight: 600;
234
  font-size: 16px;
235
  }
236
-
237
  button {
238
  background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)) !important;
239
  border: none !important;
@@ -244,13 +222,11 @@ button {
244
  transition: all 0.3s ease !important;
245
  box-shadow: 0 4px 6px rgba(0,0,0,0.1) !important;
246
  }
247
-
248
  button:hover {
249
  background: linear-gradient(135deg, var(--secondary-color), var(--primary-color)) !important;
250
  transform: translateY(-2px) !important;
251
  box-shadow: 0 6px 12px rgba(0,0,0,0.15) !important;
252
  }
253
-
254
  .upload-area {
255
  border: 2px dashed var(--primary-color) !important;
256
  background-color: rgba(240, 248, 255, 0.3) !important;
@@ -258,12 +234,10 @@ button:hover {
258
  padding: 30px !important;
259
  transition: all 0.3s ease !important;
260
  }
261
-
262
  .upload-area:hover {
263
  background-color: rgba(224, 255, 255, 0.3) !important;
264
  border-color: var(--secondary-color) !important;
265
  }
266
-
267
  .risk-meter {
268
  width: 100%;
269
  height: 20px;
@@ -272,7 +246,6 @@ button:hover {
272
  margin: 15px 0;
273
  position: relative;
274
  }
275
-
276
  .risk-meter-indicator {
277
  position: absolute;
278
  top: -5px;
@@ -281,7 +254,6 @@ button:hover {
281
  background-color: var(--text-color);
282
  transform: translateX(-50%);
283
  }
284
-
285
  .risk-meter-labels {
286
  display: flex;
287
  justify-content: space-between;
@@ -289,7 +261,6 @@ button:hover {
289
  font-size: 12px;
290
  color: var(--text-color);
291
  }
292
-
293
  .clause-example {
294
  background-color: rgba(0,0,0,0.05);
295
  padding: 15px;
@@ -300,13 +271,11 @@ button:hover {
300
  line-height: 1.5;
301
  color: var(--text-color);
302
  }
303
-
304
  .clause-number {
305
  font-weight: bold;
306
  color: var(--primary-color);
307
  margin-right: 8px;
308
  }
309
-
310
  .sentiment-meter {
311
  width: 100%;
312
  height: 20px;
@@ -314,13 +283,11 @@ button:hover {
314
  border-radius: 10px;
315
  margin: 15px 0;
316
  }
317
-
318
  .sentiment-score {
319
  height: 100%;
320
  border-radius: 10px;
321
  background-color: rgba(255,255,255,0.3);
322
  }
323
-
324
  /* Hide elements */
325
  footer, .gradio-footer, .hide, [data-testid="Use via API"], [data-testid="mmsettings"],
326
  #sentiment-analysis, #risk-visualization {
@@ -331,23 +298,19 @@ footer, .gradio-footer, .hide, [data-testid="Use via API"], [data-testid="mmsett
331
  padding: 0 !important;
332
  margin: 0 !important;
333
  }
334
-
335
  .file-info {
336
  margin-top: -15px;
337
  margin-bottom: 15px;
338
  color: var(--text-color);
339
  font-size: 13px;
340
  }
341
-
342
  /* Dark mode specific adjustments */
343
  .dark .clause-example {
344
  background-color: rgba(255,255,255,0.05);
345
  }
346
-
347
  .dark .risk-row {
348
  background-color: rgba(255,255,255,0.05);
349
  }
350
-
351
  .dark .count-item:hover {
352
  background-color: rgba(255,255,255,0.05);
353
  }
@@ -384,45 +347,180 @@ def get_hugging_face_sentiment(text: str) -> float:
384
  logger.error(f"Hugging Face sentiment analysis failed: {str(e)}. Using fallback score.")
385
  return 0.5
386
 
387
- def generate_sentiment_pdf(sentiment_score: float) -> BytesIO:
388
- """Generate a PDF with sentiment analysis results in memory and return BytesIO buffer"""
389
  try:
390
  pdf_file = BytesIO()
391
  c = canvas.Canvas(pdf_file, pagesize=letter)
392
-
393
- c.setFont("Helvetica-Bold", 16)
394
- c.drawString(1 * inch, 10 * inch, "Sentiment Analysis Report")
395
-
396
- c.setFont("Helvetica", 12)
397
- c.drawString(1 * inch, 9.5 * inch, f"Date: {time.strftime('%Y-%m-%d')}")
398
- c.drawString(1 * inch, 9.2 * inch, f"Time: {time.strftime('%H:%M:%S')}")
399
-
 
 
 
 
 
400
  c.setFont("Helvetica-Bold", 14)
401
- c.drawString(1 * inch, 8.7 * inch, f"Sentiment Score: {sentiment_score:.2f}")
402
-
403
- y_position = 8.2 * inch
404
- c.setFont("Helvetica-Bold", 12)
405
- c.drawString(1 * inch, y_position, "Summary:")
406
  y_position -= 0.3 * inch
407
-
408
  c.setFont("Helvetica", 10)
409
- summary_data = {
410
- "Sentiment Score": f"{sentiment_score:.2f} (0.0 Negative to 1.0 Positive)",
411
- "Interpretation": "Positive" if sentiment_score > 0.6 else "Negative" if sentiment_score < 0.4 else "Neutral",
412
- "Analysis Timestamp": time.strftime("%Y-%m-%d %H:%M:%S")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
413
  }
414
-
415
- for key, value in summary_data.items():
416
- c.drawString(1 * inch, y_position, f"{key}: {value}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417
  y_position -= 0.25 * inch
418
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
419
  c.save()
420
  pdf_file.seek(0)
421
- logger.info("PDF generated in memory")
422
  return pdf_file
423
  except Exception as e:
424
- logger.error(f"Error generating PDF: {str(e)}")
425
- return None
426
 
427
  def save_to_salesforce(sf: Salesforce, data: Dict) -> str:
428
  """Save analysis results to Salesforce, return record ID"""
@@ -705,8 +803,21 @@ def analyze_pdf(file_obj) -> List:
705
  logger.error(f"Salesforce record creation failed: {str(e)}")
706
  salesforce_id = "N/A"
707
 
 
 
 
 
 
 
 
 
 
 
 
 
 
708
  try:
709
- pdf_buffer = generate_sentiment_pdf(sentiment_score)
710
  if pdf_buffer is None:
711
  raise Exception("Failed to generate PDF")
712
  # Save to a temporary file for Gradio to serve
@@ -735,7 +846,14 @@ def analyze_pdf(file_obj) -> List:
735
  </div>
736
  {sentiment_meter}
737
  <div style='margin-top: 15px;'>
738
- <strong>Sentiment Report:</strong> {'' if temp_file_path else 'Failed to generate PDF'}
 
 
 
 
 
 
 
739
  </div>
740
  </div>
741
  """
@@ -824,7 +942,7 @@ with gr.Blocks(css=css, title="PDF Contract Risk Analyzer", theme=gr.themes.Defa
824
 
825
  with gr.Row():
826
  sentiment_analysis = gr.HTML(label="Sentiment Analysis")
827
- pdf_output = gr.File(label="Download Sentiment Report PDF", file_types=[".pdf"])
828
 
829
  submit_btn.click(
830
  fn=analyze_pdf,
 
17
  import time
18
  import tempfile
19
  import os
20
+ from datetime import datetime
21
 
22
  # Set up logging
23
  logging.basicConfig(level=logging.INFO)
 
37
  --danger-color: #f44336;
38
  --info-color: #2196F3;
39
  }
 
40
  .dark {
41
  --primary-color: #4a89dc;
42
  --secondary-color: #3b7dd8;
 
49
  --danger-color: #F44336;
50
  --info-color: #2196F3;
51
  }
 
52
  body {
53
  background-image: url('https://images.unsplash.com/photo-1604147706283-d7119b5b822c?ixlib=rb-1.2.1&auto=format&fit=crop&w=1920&q=80');
54
  background-size: cover;
 
61
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
62
  color: var(--text-color);
63
  }
 
64
  .gradio-container {
65
  background-color: rgba(var(--bg-color), 0.97) !important;
66
  border-radius: 15px;
 
71
  min-height: 90vh;
72
  border: 1px solid var(--primary-color) !important;
73
  }
 
74
  .risk-low { color: var(--success-color); font-weight: bold; }
75
  .risk-medium { color: var(--warning-color); font-weight: bold; }
76
  .risk-high { color: var(--danger-color); font-weight: bold; }
 
77
  .result-box {
78
  padding: 20px;
79
  border-radius: 10px;
 
83
  border-left: 5px solid var(--primary-color) !important;
84
  color: var(--text-color);
85
  }
 
86
  .penalty-box {
87
  padding: 20px;
88
  border-radius: 10px;
 
91
  background-color: var(--box-bg);
92
  box-shadow: 0 2px 8px rgba(0,0,0,0.08);
93
  }
 
94
  .obligation-box {
95
  padding: 20px;
96
  border-radius: 10px;
 
99
  background-color: var(--box-bg);
100
  box-shadow: 0 2px 8px rgba(0,0,0,0.08);
101
  }
 
102
  .delay-box {
103
  padding: 20px;
104
  border-radius: 10px;
 
107
  background-color: var(--box-bg);
108
  box-shadow: 0 2px 8px rgba(0,0,0,0.08);
109
  }
 
110
  .combined-risk-container {
111
  display: flex;
112
  flex-direction: column;
 
117
  border-radius: 10px;
118
  box-shadow: 0 2px 8px rgba(0,0,0,0.08);
119
  }
 
120
  .risk-row {
121
  display: flex;
122
  align-items: center;
 
127
  border: 1px solid var(--border-color) !important;
128
  transition: all 0.3s ease;
129
  }
 
130
  .risk-row:hover {
131
  transform: translateY(-2px);
132
  box-shadow: 0 4px 12px rgba(0,0,0,0.1);
133
  }
 
134
  .risk-label {
135
  width: 150px;
136
  font-weight: 600;
137
  font-size: 16px;
138
  color: var(--text-color) !important;
139
  }
 
140
  .risk-score {
141
  width: 120px;
142
  font-size: 20px;
 
144
  padding: 8px 12px;
145
  border-radius: 6px;
146
  }
 
147
  .warning-box {
148
  padding: 18px;
149
  border-radius: 8px;
 
153
  font-weight: 600;
154
  box-shadow: 0 2px 8px rgba(0,0,0,0.05);
155
  }
 
156
  .danger-box {
157
  padding: 18px;
158
  border-radius: 8px;
 
162
  font-weight: 600;
163
  box-shadow: 0 2px 8px rgba(0,0,0,0.05);
164
  }
 
165
  .success-box {
166
  padding: 18px;
167
  border-radius: 8px;
 
171
  font-weight: 600;
172
  box-shadow: 0 2px 8px rgba(0,0,0,0.05);
173
  }
 
174
  .info-box {
175
  padding: 18px;
176
  border-radius: 8px;
 
180
  font-weight: 600;
181
  box-shadow: 0 2px 8px rgba(0,0,0,0.05);
182
  }
 
183
  .section-title {
184
  font-size: 22px;
185
  font-weight: 700;
 
189
  align-items: center;
190
  gap: 10px;
191
  }
 
192
  .count-item {
193
  display: flex;
194
  justify-content: space-between;
 
196
  border-bottom: 1px solid var(--border-color) !important;
197
  transition: all 0.2s ease;
198
  }
 
199
  .count-item:hover {
200
  background-color: rgba(0,0,0,0.05);
201
  transform: translateX(5px);
202
  }
 
203
  .count-label {
204
  font-weight: 600;
205
  color: var(--text-color) !important;
 
207
  align-items: center;
208
  gap: 8px;
209
  }
 
210
  .count-value {
211
  color: var(--secondary-color) !important;
212
  font-weight: 600;
213
  font-size: 16px;
214
  }
 
215
  button {
216
  background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)) !important;
217
  border: none !important;
 
222
  transition: all 0.3s ease !important;
223
  box-shadow: 0 4px 6px rgba(0,0,0,0.1) !important;
224
  }
 
225
  button:hover {
226
  background: linear-gradient(135deg, var(--secondary-color), var(--primary-color)) !important;
227
  transform: translateY(-2px) !important;
228
  box-shadow: 0 6px 12px rgba(0,0,0,0.15) !important;
229
  }
 
230
  .upload-area {
231
  border: 2px dashed var(--primary-color) !important;
232
  background-color: rgba(240, 248, 255, 0.3) !important;
 
234
  padding: 30px !important;
235
  transition: all 0.3s ease !important;
236
  }
 
237
  .upload-area:hover {
238
  background-color: rgba(224, 255, 255, 0.3) !important;
239
  border-color: var(--secondary-color) !important;
240
  }
 
241
  .risk-meter {
242
  width: 100%;
243
  height: 20px;
 
246
  margin: 15px 0;
247
  position: relative;
248
  }
 
249
  .risk-meter-indicator {
250
  position: absolute;
251
  top: -5px;
 
254
  background-color: var(--text-color);
255
  transform: translateX(-50%);
256
  }
 
257
  .risk-meter-labels {
258
  display: flex;
259
  justify-content: space-between;
 
261
  font-size: 12px;
262
  color: var(--text-color);
263
  }
 
264
  .clause-example {
265
  background-color: rgba(0,0,0,0.05);
266
  padding: 15px;
 
271
  line-height: 1.5;
272
  color: var(--text-color);
273
  }
 
274
  .clause-number {
275
  font-weight: bold;
276
  color: var(--primary-color);
277
  margin-right: 8px;
278
  }
 
279
  .sentiment-meter {
280
  width: 100%;
281
  height: 20px;
 
283
  border-radius: 10px;
284
  margin: 15px 0;
285
  }
 
286
  .sentiment-score {
287
  height: 100%;
288
  border-radius: 10px;
289
  background-color: rgba(255,255,255,0.3);
290
  }
 
291
  /* Hide elements */
292
  footer, .gradio-footer, .hide, [data-testid="Use via API"], [data-testid="mmsettings"],
293
  #sentiment-analysis, #risk-visualization {
 
298
  padding: 0 !important;
299
  margin: 0 !important;
300
  }
 
301
  .file-info {
302
  margin-top: -15px;
303
  margin-bottom: 15px;
304
  color: var(--text-color);
305
  font-size: 13px;
306
  }
 
307
  /* Dark mode specific adjustments */
308
  .dark .clause-example {
309
  background-color: rgba(255,255,255,0.05);
310
  }
 
311
  .dark .risk-row {
312
  background-color: rgba(255,255,255,0.05);
313
  }
 
314
  .dark .count-item:hover {
315
  background-color: rgba(255,255,255,0.05);
316
  }
 
347
  logger.error(f"Hugging Face sentiment analysis failed: {str(e)}. Using fallback score.")
348
  return 0.5
349
 
350
+ def generate_analysis_pdf(analysis_data: Dict) -> BytesIO:
351
+ """Generate a comprehensive PDF report with analysis results"""
352
  try:
353
  pdf_file = BytesIO()
354
  c = canvas.Canvas(pdf_file, pagesize=letter)
355
+
356
+ # Header
357
+ c.setFont("Helvetica-Bold", 18)
358
+ c.drawString(1 * inch, 10.5 * inch, "Contract Risk Analysis Report")
359
+ c.setFont("Helvetica", 10)
360
+ c.drawString(1 * inch, 10.2 * inch, f"Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
361
+ c.drawString(1 * inch, 10 * inch, f"Document: {analysis_data.get('document_name', 'Unknown')}")
362
+
363
+ # Add a line separator
364
+ c.line(1 * inch, 9.8 * inch, 7.5 * inch, 9.8 * inch)
365
+
366
+ # Risk Summary Section
367
+ y_position = 9.5 * inch
368
  c.setFont("Helvetica-Bold", 14)
369
+ c.drawString(1 * inch, y_position, "1. Risk Summary")
 
 
 
 
370
  y_position -= 0.3 * inch
371
+
372
  c.setFont("Helvetica", 10)
373
+ risk_level = analysis_data['risk_level']
374
+ risk_color = {
375
+ "Low": "#4CAF50",
376
+ "Medium": "#FF9800",
377
+ "High": "#F44336"
378
+ }.get(risk_level, "#000000")
379
+
380
+ c.setFillColor(risk_color)
381
+ c.setFont("Helvetica-Bold", 12)
382
+ c.drawString(1 * inch, y_position, f"Overall Risk Level: {risk_level}")
383
+ c.setFillColor("black")
384
+
385
+ y_position -= 0.25 * inch
386
+ c.setFont("Helvetica", 10)
387
+ c.drawString(1 * inch, y_position, f"Risk Score: {analysis_data['risk_score']:.1f}/100")
388
+ y_position -= 0.25 * inch
389
+
390
+ # Risk explanation
391
+ risk_explanations = {
392
+ "Low": "The contract appears to be low risk with favorable terms. Standard review recommended.",
393
+ "Medium": "The contract has moderate risk factors. Careful review of flagged clauses advised.",
394
+ "High": "The contract contains high-risk elements! Immediate legal review required."
395
  }
396
+ c.drawString(1 * inch, y_position, "Assessment:")
397
+ y_position -= 0.2 * inch
398
+ c.setFont("Helvetica", 10)
399
+ for line in textwrap.wrap(risk_explanations.get(risk_level, ""), width=80):
400
+ c.drawString(1.2 * inch, y_position, line)
401
+ y_position -= 0.2 * inch
402
+
403
+ # Detailed Metrics Section
404
+ y_position -= 0.3 * inch
405
+ c.setFont("Helvetica-Bold", 14)
406
+ c.drawString(1 * inch, y_position, "2. Detailed Metrics")
407
+ y_position -= 0.3 * inch
408
+
409
+ # Sentiment Analysis
410
+ c.setFont("Helvetica-Bold", 12)
411
+ c.drawString(1 * inch, y_position, "Sentiment Analysis:")
412
+ y_position -= 0.2 * inch
413
+ c.setFont("Helvetica", 10)
414
+ sentiment_score = analysis_data['sentiment_score']
415
+ sentiment_text = (
416
+ "Positive (favorable language)" if sentiment_score > 0.6 else
417
+ "Negative (adversarial language)" if sentiment_score < 0.4 else
418
+ "Neutral (balanced language)"
419
+ )
420
+ c.drawString(1.2 * inch, y_position, f"Score: {sentiment_score:.2f} - {sentiment_text}")
421
+ y_position -= 0.2 * inch
422
+ c.drawString(1.2 * inch, y_position, "Interpretation: Measures the overall tone of the contract language.")
423
+ y_position -= 0.25 * inch
424
+
425
+ # Penalty Analysis
426
+ c.setFont("Helvetica-Bold", 12)
427
+ c.drawString(1 * inch, y_position, "Penalty Analysis:")
428
+ y_position -= 0.2 * inch
429
+ c.setFont("Helvetica", 10)
430
+ c.drawString(1.2 * inch, y_position, f"Total penalty clauses found: {analysis_data['penalty_count']}")
431
+ y_position -= 0.2 * inch
432
+ if analysis_data['penalty_values']:
433
+ c.drawString(1.2 * inch, y_position, f"Highest penalty amount: ${max(analysis_data['penalty_values']):,.2f}")
434
+ y_position -= 0.2 * inch
435
+ c.drawString(1.2 * inch, y_position, f"Average penalty amount: ${sum(analysis_data['penalty_values'])/len(analysis_data['penalty_values']):,.2f}")
436
+ y_position -= 0.2 * inch
437
+ c.drawString(1.2 * inch, y_position, "Interpretation: Penalties are financial consequences for non-compliance.")
438
+ y_position -= 0.25 * inch
439
+
440
+ # Obligation Analysis
441
+ c.setFont("Helvetica-Bold", 12)
442
+ c.drawString(1 * inch, y_position, "Obligation Analysis:")
443
+ y_position -= 0.2 * inch
444
+ c.setFont("Helvetica", 10)
445
+ c.drawString(1.2 * inch, y_position, f"Total obligation clauses found: {analysis_data['obligation_count']}")
446
+ y_position -= 0.2 * inch
447
+ c.drawString(1.2 * inch, y_position, "Interpretation: Obligations are requirements that must be fulfilled.")
448
+ y_position -= 0.25 * inch
449
+
450
+ # Delay Analysis
451
+ c.setFont("Helvetica-Bold", 12)
452
+ c.drawString(1 * inch, y_position, "Delay Analysis:")
453
+ y_position -= 0.2 * inch
454
+ c.setFont("Helvetica", 10)
455
+ c.drawString(1.2 * inch, y_position, f"Total delay clauses found: {analysis_data['delay_count']}")
456
+ y_position -= 0.2 * inch
457
+ c.drawString(1.2 * inch, y_position, "Interpretation: Delay clauses specify timelines and consequences for delays.")
458
+ y_position -= 0.3 * inch
459
+
460
+ # Key Findings Section
461
+ if y_position < 2 * inch:
462
+ c.showPage()
463
+ y_position = 10.5 * inch
464
+
465
+ c.setFont("Helvetica-Bold", 14)
466
+ c.drawString(1 * inch, y_position, "3. Key Findings")
467
+ y_position -= 0.3 * inch
468
+ c.setFont("Helvetica", 10)
469
+
470
+ # Add key findings
471
+ findings = []
472
+ if analysis_data['risk_level'] == "High":
473
+ findings.append("⚠️ High-risk contract requiring immediate legal review")
474
+ if analysis_data['penalty_count'] > 5:
475
+ findings.append(f"⚠️ High number of penalty clauses ({analysis_data['penalty_count']})")
476
+ if analysis_data['obligation_count'] > 10:
477
+ findings.append(f"📝 Numerous obligations ({analysis_data['obligation_count']}) that may require tracking")
478
+ if analysis_data['sentiment_score'] < 0.4:
479
+ findings.append("🔍 Contract language appears adversarial (low sentiment score)")
480
+
481
+ if not findings:
482
+ findings.append("✅ No major red flags detected in initial analysis")
483
+
484
+ for finding in findings:
485
+ c.drawString(1 * inch, y_position, finding)
486
  y_position -= 0.25 * inch
487
+
488
+ # Recommendations Section
489
+ y_position -= 0.3 * inch
490
+ c.setFont("Helvetica-Bold", 14)
491
+ c.drawString(1 * inch, y_position, "4. Recommendations")
492
+ y_position -= 0.3 * inch
493
+ c.setFont("Helvetica", 10)
494
+
495
+ recommendations = []
496
+ if analysis_data['risk_level'] == "High":
497
+ recommendations.append("• Engage legal counsel for comprehensive review")
498
+ recommendations.append("• Negotiate penalty clauses and liability terms")
499
+ if analysis_data['penalty_count'] > 0:
500
+ recommendations.append("• Review all penalty clauses for fairness and applicability")
501
+ if analysis_data['obligation_count'] > 10:
502
+ recommendations.append("• Create an obligation tracking system")
503
+ if analysis_data['sentiment_score'] < 0.4:
504
+ recommendations.append("• Consider negotiating more balanced language")
505
+
506
+ if not recommendations:
507
+ recommendations.append("• Standard contract review process sufficient")
508
+
509
+ for rec in recommendations:
510
+ c.drawString(1 * inch, y_position, rec)
511
+ y_position -= 0.25 * inch
512
+
513
+ # Footer
514
+ c.setFont("Helvetica-Oblique", 8)
515
+ c.drawString(1 * inch, 0.5 * inch, "Generated by Contract Risk Analyzer - Confidential")
516
+
517
  c.save()
518
  pdf_file.seek(0)
519
+ logger.info("PDF report generated successfully")
520
  return pdf_file
521
  except Exception as e:
522
+ logger.error(f"Error generating PDF report: {str(e)}")
523
+ raise Exception(f"PDF generation failed: {str(e)}")
524
 
525
  def save_to_salesforce(sf: Salesforce, data: Dict) -> str:
526
  """Save analysis results to Salesforce, return record ID"""
 
803
  logger.error(f"Salesforce record creation failed: {str(e)}")
804
  salesforce_id = "N/A"
805
 
806
+ # Prepare data for PDF report
807
+ analysis_data = {
808
+ 'document_name': os.path.basename(file_obj.name),
809
+ 'sentiment_score': sentiment_score,
810
+ 'risk_score': risk_score,
811
+ 'risk_level': risk_level,
812
+ 'penalty_count': total_penalties,
813
+ 'penalty_values': penalty_values,
814
+ 'obligation_count': total_obligations,
815
+ 'delay_count': total_delays,
816
+ 'record_id': record_id
817
+ }
818
+
819
  try:
820
+ pdf_buffer = generate_analysis_pdf(analysis_data)
821
  if pdf_buffer is None:
822
  raise Exception("Failed to generate PDF")
823
  # Save to a temporary file for Gradio to serve
 
846
  </div>
847
  {sentiment_meter}
848
  <div style='margin-top: 15px;'>
849
+ <strong>Interpretation:</strong> {
850
+ "Positive (favorable language)" if sentiment_score > 0.6 else
851
+ "Negative (adversarial language)" if sentiment_score < 0.4 else
852
+ "Neutral (balanced language)"
853
+ }
854
+ </div>
855
+ <div style='margin-top: 10px;'>
856
+ <strong>Full Report:</strong> Available for download below
857
  </div>
858
  </div>
859
  """
 
942
 
943
  with gr.Row():
944
  sentiment_analysis = gr.HTML(label="Sentiment Analysis")
945
+ pdf_output = gr.File(label="Download Full Analysis Report (PDF)", file_types=[".pdf"])
946
 
947
  submit_btn.click(
948
  fn=analyze_pdf,