bluenevus commited on
Commit
34f1f65
·
1 Parent(s): b8c66ce

Update app.py via AI Editor

Browse files
Files changed (1) hide show
  1. app.py +75 -15
app.py CHANGED
@@ -146,7 +146,7 @@ def save_proposal_as_docx(proposal_text, base_filename):
146
  memf.seek(0)
147
  return memf.read()
148
 
149
- def process_document(action, selected_filename=None, chat_input=None, rfp_decoded_bytes=None, cancel_event=None, stream=True):
150
  global shredded_document, generated_response, stream_buffer
151
 
152
  logging.info(f"Process document called with action: {action}")
@@ -277,7 +277,65 @@ def process_document(action, selected_filename=None, chat_input=None, rfp_decode
277
  return result_holder["text"], None, result_holder["docx_name"], result_holder["text"]
278
 
279
  elif action == 'compliance':
280
- return "Compliance checking not implemented yet.", None, None, None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  elif action == 'recover':
282
  return "Recovery not implemented yet.", None, None, None
283
  elif action == 'board':
@@ -549,6 +607,7 @@ app.layout = dbc.Container([
549
  [
550
  Input('shred-action-btn', 'n_clicks'),
551
  Input('proposal-action-btn', 'n_clicks'),
 
552
  Input('upload-document', 'contents'),
553
  State('upload-document', 'filename'),
554
  Input({'type': 'delete-doc-btn', 'index': ALL, 'group': 'rfp'}, 'n_clicks'),
@@ -573,7 +632,7 @@ app.layout = dbc.Container([
573
  prevent_initial_call=True
574
  )
575
  def master_callback(
576
- shred_clicks, proposal_clicks,
577
  rfp_content, rfp_filename, rfp_delete_clicks, selected_doc,
578
  proposal_content, proposal_filename, proposal_delete_clicks, selected_proposal,
579
  generated_delete_clicks, selected_generated,
@@ -598,10 +657,10 @@ def master_callback(
598
  proposal_store = {'text': None, 'docx_name': None}
599
  streaming = False
600
 
601
- rfp_delete_clicks = safe_get_n_clicks(ctx, 4)
602
- proposal_delete_clicks = safe_get_n_clicks(ctx, 8)
603
- generated_delete_clicks = safe_get_n_clicks(ctx, 10)
604
- shredded_delete_clicks = safe_get_n_clicks(ctx, 12)
605
 
606
  uploaded_rfp_decoded_bytes = None
607
 
@@ -692,7 +751,7 @@ def master_callback(
692
  if triggered_id and isinstance(rfp_delete_clicks, list):
693
  for i, n_click in enumerate(rfp_delete_clicks):
694
  if n_click:
695
- btn_id = ctx.inputs_list[4][i]['id']
696
  del_filename = btn_id['index']
697
  if del_filename in uploaded_documents:
698
  del uploaded_documents[del_filename]
@@ -711,7 +770,7 @@ def master_callback(
711
  if triggered_id and isinstance(proposal_delete_clicks, list):
712
  for i, n_click in enumerate(proposal_delete_clicks):
713
  if n_click:
714
- btn_id = ctx.inputs_list[8][i]['id']
715
  del_filename = btn_id['index']
716
  if del_filename in uploaded_proposals:
717
  del uploaded_proposals[del_filename]
@@ -730,7 +789,7 @@ def master_callback(
730
  if triggered_id and isinstance(generated_delete_clicks, list):
731
  for i, n_click in enumerate(generated_delete_clicks):
732
  if n_click:
733
- btn_id = ctx.inputs_list[10][i]['id']
734
  del_filename = btn_id['index']
735
  if del_filename in generated_documents:
736
  del generated_documents[del_filename]
@@ -743,7 +802,7 @@ def master_callback(
743
  if triggered_id and isinstance(shredded_delete_clicks, list):
744
  for i, n_click in enumerate(shredded_delete_clicks):
745
  if n_click:
746
- btn_id = ctx.inputs_list[12][i]['id']
747
  del_filename = btn_id['index']
748
  if del_filename in shredded_documents:
749
  del shredded_documents[del_filename]
@@ -762,7 +821,7 @@ def master_callback(
762
 
763
  output_data_upload = html.Div("No action taken yet.", style={"wordWrap": "break-word"})
764
 
765
- if triggered_id in ['shred-action-btn', 'proposal-action-btn']:
766
  got_lock = gemini_lock.acquire(blocking=False)
767
  if not got_lock:
768
  output_data_upload = html.Div("Another Gemini operation is in progress. Please wait or cancel.", style={"wordWrap": "break-word"})
@@ -778,12 +837,13 @@ def master_callback(
778
  stream_event.clear()
779
  stream_buffer["preview"] = ""
780
  streaming = True
781
- def stream_gemini_thread(action, doc_value, chat_input, rfp_decoded_bytes):
782
  try:
783
- process_document(action, doc_value, chat_input, rfp_decoded_bytes, cancel_event=stream_event, stream=True)
784
  finally:
785
  gemini_lock.release()
786
- t = Thread(target=stream_gemini_thread, args=(('shred' if triggered_id=='shred-action-btn' else 'proposal'), doc_value, chat_input, uploaded_rfp_decoded_bytes))
 
787
  t.daemon = True
788
  t.start()
789
  output_data_upload = dcc.Markdown("Starting Gemini operation...", style={"whiteSpace": "pre-wrap", "wordWrap": "break-word"})
 
146
  memf.seek(0)
147
  return memf.read()
148
 
149
+ def process_document(action, selected_filename=None, chat_input=None, rfp_decoded_bytes=None, cancel_event=None, stream=True, selected_generated_filename=None):
150
  global shredded_document, generated_response, stream_buffer
151
 
152
  logging.info(f"Process document called with action: {action}")
 
277
  return result_holder["text"], None, result_holder["docx_name"], result_holder["text"]
278
 
279
  elif action == 'compliance':
280
+ # --- COMPLIANCE FUNCTIONALITY ADDED HERE ---
281
+ # Get selected generated doc (proposal) and selected RFP doc
282
+ if not selected_generated_filename or selected_generated_filename not in generated_documents:
283
+ return "No generated document selected for compliance.", None, None, None
284
+ if not selected_filename or selected_filename not in uploaded_documents:
285
+ return "No RFP/SOW/PWS/RFI document selected for compliance.", None, None, None
286
+
287
+ proposal_docx_bytes = generated_documents[selected_generated_filename]
288
+ rfp_text = uploaded_documents[selected_filename]
289
+ logging.info(f"Compliance check: comparing proposal [{selected_generated_filename}] to RFP [{selected_filename}]")
290
+
291
+ # Decode the proposal docx to text (best-effort, using python-docx)
292
+ proposal_text = ""
293
+ try:
294
+ file_stream = io.BytesIO(proposal_docx_bytes)
295
+ doc = Document(file_stream)
296
+ proposal_text = "\n".join([para.text for para in doc.paragraphs])
297
+ if not proposal_text.strip():
298
+ proposal_text = "[Unable to extract text from proposal docx]"
299
+ except Exception as e:
300
+ proposal_text = "[Error decoding proposal docx: {}]".format(str(e))
301
+ logging.error("Error extracting text from proposal docx: %s", e)
302
+
303
+ prompt = (
304
+ "You are a proposal compliance expert. Use the following RFP/SOW/PWS/RFI and the generated proposal response. "
305
+ "Compare the proposal to the RFP requirements and win themes. "
306
+ "For each PWS section/requirement, determine if the proposal fully, partially, or does not address the requirement and win theme. "
307
+ "For each, give a finding and a recommendation to recover for compliance or to strengthen the response. "
308
+ "Return ONLY a markdown table (NO comments, NO intro, NO outro, NO summary) with the following columns: "
309
+ "| PWS Section Number | Section Name | Requirement Description | Finding | Recommendation to Recover for Compliance |. "
310
+ "Be concise and focus on actionable compliance gaps. Only the table, nothing else.\n\n"
311
+ f"---\nRFP/SOW/PWS/RFI ({selected_filename}):\n{rfp_text}\n"
312
+ "---\nGenerated Proposal Document:\n"
313
+ f"{proposal_text}\n"
314
+ )
315
+ result_holder = {"text": None, "docx_name": None}
316
+
317
+ def thread_compliance():
318
+ global stream_buffer
319
+ stream_buffer["preview"] = ""
320
+ try:
321
+ logging.info("Starting Gemini generate_content for compliance check.")
322
+ for partial in gemini_generate_content_stream(prompt, file_id=None, chat_input=None, cancel_event=cancel_event):
323
+ stream_buffer["preview"] = partial
324
+ if cancel_event is not None and cancel_event.is_set():
325
+ break
326
+ result = stream_buffer["preview"]
327
+ result_holder["text"] = result
328
+ logging.info("Compliance check completed.")
329
+ except Exception as e:
330
+ logging.error("Error during compliance check: %s", e)
331
+ result_holder["text"] = f"Error during compliance check: {e}"
332
+
333
+ stream_buffer["preview"] = ""
334
+ t = Thread(target=thread_compliance)
335
+ t.start()
336
+ t.join()
337
+ return result_holder["text"], None, None, result_holder["text"]
338
+
339
  elif action == 'recover':
340
  return "Recovery not implemented yet.", None, None, None
341
  elif action == 'board':
 
607
  [
608
  Input('shred-action-btn', 'n_clicks'),
609
  Input('proposal-action-btn', 'n_clicks'),
610
+ Input('compliance-action-btn', 'n_clicks'),
611
  Input('upload-document', 'contents'),
612
  State('upload-document', 'filename'),
613
  Input({'type': 'delete-doc-btn', 'index': ALL, 'group': 'rfp'}, 'n_clicks'),
 
632
  prevent_initial_call=True
633
  )
634
  def master_callback(
635
+ shred_clicks, proposal_clicks, compliance_clicks,
636
  rfp_content, rfp_filename, rfp_delete_clicks, selected_doc,
637
  proposal_content, proposal_filename, proposal_delete_clicks, selected_proposal,
638
  generated_delete_clicks, selected_generated,
 
657
  proposal_store = {'text': None, 'docx_name': None}
658
  streaming = False
659
 
660
+ rfp_delete_clicks = safe_get_n_clicks(ctx, 5)
661
+ proposal_delete_clicks = safe_get_n_clicks(ctx, 9)
662
+ generated_delete_clicks = safe_get_n_clicks(ctx, 11)
663
+ shredded_delete_clicks = safe_get_n_clicks(ctx, 13)
664
 
665
  uploaded_rfp_decoded_bytes = None
666
 
 
751
  if triggered_id and isinstance(rfp_delete_clicks, list):
752
  for i, n_click in enumerate(rfp_delete_clicks):
753
  if n_click:
754
+ btn_id = ctx.inputs_list[5][i]['id']
755
  del_filename = btn_id['index']
756
  if del_filename in uploaded_documents:
757
  del uploaded_documents[del_filename]
 
770
  if triggered_id and isinstance(proposal_delete_clicks, list):
771
  for i, n_click in enumerate(proposal_delete_clicks):
772
  if n_click:
773
+ btn_id = ctx.inputs_list[9][i]['id']
774
  del_filename = btn_id['index']
775
  if del_filename in uploaded_proposals:
776
  del uploaded_proposals[del_filename]
 
789
  if triggered_id and isinstance(generated_delete_clicks, list):
790
  for i, n_click in enumerate(generated_delete_clicks):
791
  if n_click:
792
+ btn_id = ctx.inputs_list[11][i]['id']
793
  del_filename = btn_id['index']
794
  if del_filename in generated_documents:
795
  del generated_documents[del_filename]
 
802
  if triggered_id and isinstance(shredded_delete_clicks, list):
803
  for i, n_click in enumerate(shredded_delete_clicks):
804
  if n_click:
805
+ btn_id = ctx.inputs_list[13][i]['id']
806
  del_filename = btn_id['index']
807
  if del_filename in shredded_documents:
808
  del shredded_documents[del_filename]
 
821
 
822
  output_data_upload = html.Div("No action taken yet.", style={"wordWrap": "break-word"})
823
 
824
+ if triggered_id in ['shred-action-btn', 'proposal-action-btn', 'compliance-action-btn']:
825
  got_lock = gemini_lock.acquire(blocking=False)
826
  if not got_lock:
827
  output_data_upload = html.Div("Another Gemini operation is in progress. Please wait or cancel.", style={"wordWrap": "break-word"})
 
837
  stream_event.clear()
838
  stream_buffer["preview"] = ""
839
  streaming = True
840
+ def stream_gemini_thread(action, doc_value, chat_input, rfp_decoded_bytes, selected_generated_filename):
841
  try:
842
+ process_document(action, doc_value, chat_input, rfp_decoded_bytes, cancel_event=stream_event, stream=True, selected_generated_filename=selected_generated_filename)
843
  finally:
844
  gemini_lock.release()
845
+ action_name = "shred" if triggered_id=="shred-action-btn" else ("proposal" if triggered_id=="proposal-action-btn" else "compliance")
846
+ t = Thread(target=stream_gemini_thread, args=(action_name, doc_value, chat_input, uploaded_rfp_decoded_bytes, generated_doc_value))
847
  t.daemon = True
848
  t.start()
849
  output_data_upload = dcc.Markdown("Starting Gemini operation...", style={"whiteSpace": "pre-wrap", "wordWrap": "break-word"})