Spaces:
Paused
Paused
Update app.py via AI Editor
Browse files
app.py
CHANGED
|
@@ -160,6 +160,16 @@ def save_virtual_board_as_docx(board_text, base_filename):
|
|
| 160 |
memf.seek(0)
|
| 161 |
return memf.read()
|
| 162 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
def process_document(action, selected_filename=None, chat_input=None, rfp_decoded_bytes=None, selected_proposal_filename=None):
|
| 164 |
global generated_response
|
| 165 |
|
|
@@ -336,7 +346,39 @@ def process_document(action, selected_filename=None, chat_input=None, rfp_decode
|
|
| 336 |
return result, None, None, None, None
|
| 337 |
|
| 338 |
elif action == 'loe':
|
| 339 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
return "Action not implemented yet.", None, None, None, None
|
| 341 |
|
| 342 |
def get_documents_list(docdict, shreddedict):
|
|
@@ -379,7 +421,10 @@ def get_proposals_list(proposaldict):
|
|
| 379 |
for filename in proposaldict:
|
| 380 |
file_content = proposaldict[filename]
|
| 381 |
try:
|
| 382 |
-
|
|
|
|
|
|
|
|
|
|
| 383 |
b64 = base64.b64encode(docx_bytes).decode('utf-8')
|
| 384 |
mime = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
| 385 |
except Exception:
|
|
@@ -743,9 +788,8 @@ def master_callback(
|
|
| 743 |
output_data_upload = dcc.Markdown(result, style={"whiteSpace": "pre-wrap", "wordWrap": "break-word"})
|
| 744 |
elif triggered_id == "loe-action-btn":
|
| 745 |
action_name = "loe"
|
| 746 |
-
selected_bytes = uploaded_documents_bytes.get(doc_value, None)
|
| 747 |
result, _, _, generated_filename, generated_docx_bytes = process_document(
|
| 748 |
-
action_name,
|
| 749 |
)
|
| 750 |
output_data_upload = dcc.Markdown(result, style={"whiteSpace": "pre-wrap", "wordWrap": "break-word"})
|
| 751 |
finally:
|
|
|
|
| 160 |
memf.seek(0)
|
| 161 |
return memf.read()
|
| 162 |
|
| 163 |
+
def save_loe_as_docx(loe_text, proposal_filename):
|
| 164 |
+
doc = Document()
|
| 165 |
+
doc.add_heading(f"Level of Effort for {proposal_filename}", 0)
|
| 166 |
+
for line in loe_text.split('\n'):
|
| 167 |
+
doc.add_paragraph(line)
|
| 168 |
+
memf = io.BytesIO()
|
| 169 |
+
doc.save(memf)
|
| 170 |
+
memf.seek(0)
|
| 171 |
+
return memf.read()
|
| 172 |
+
|
| 173 |
def process_document(action, selected_filename=None, chat_input=None, rfp_decoded_bytes=None, selected_proposal_filename=None):
|
| 174 |
global generated_response
|
| 175 |
|
|
|
|
| 346 |
return result, None, None, None, None
|
| 347 |
|
| 348 |
elif action == 'loe':
|
| 349 |
+
# LOE estimation implementation
|
| 350 |
+
if not selected_proposal_filename or selected_proposal_filename not in proposals:
|
| 351 |
+
logging.warning("No proposal document selected for LOE estimation.")
|
| 352 |
+
return "No proposal document selected for LOE estimation.", None, None, None, None
|
| 353 |
+
proposal_text = proposals[selected_proposal_filename]
|
| 354 |
+
proposal_base_name = os.path.splitext(selected_proposal_filename)[0]
|
| 355 |
+
prompt = (
|
| 356 |
+
"You are a federal proposal cost and level of effort estimator. "
|
| 357 |
+
"Analyze the following proposal response which is structured to map to a government RFP, PWS, or SOW. "
|
| 358 |
+
"For each PWS task or requirement referenced in the proposal, estimate the labor categories and a conservative, overestimated number of hours for each labor category required to accomplish that task. "
|
| 359 |
+
"Include a management reserve to ensure the estimate is not underestimated. "
|
| 360 |
+
"Return ONLY a markdown table (NO comments, NO intro, NO outro, NO summary) with the following columns: "
|
| 361 |
+
"| PWS Task Number | Brief Task Description | Labor Category | Estimated Hours (including management reserve) | "
|
| 362 |
+
"For each task, if more than one labor category is needed, list each category on its own row under the same task. "
|
| 363 |
+
"Be sure to break down by detailed task, not just high level sections. "
|
| 364 |
+
"Be detailed and thorough in your estimation and make sure to overestimate hours to ensure sufficient coverage. "
|
| 365 |
+
"If the proposal references any assumed task number or section, use it in the table. "
|
| 366 |
+
"Return ONLY the markdown table, no other text, no intro, no outro.\n\n"
|
| 367 |
+
)
|
| 368 |
+
if chat_input:
|
| 369 |
+
prompt += f"User additional instructions: {chat_input}\n"
|
| 370 |
+
prompt += f"\n---\nProposal Document ({selected_proposal_filename}):\n{proposal_text}\n"
|
| 371 |
+
result = gemini_generate_content(prompt, file_id=None, chat_input=chat_input)
|
| 372 |
+
if result and not result.startswith("Error"):
|
| 373 |
+
loe_docx_name = f"{proposal_base_name}_loe.docx"
|
| 374 |
+
proposals[loe_docx_name] = result
|
| 375 |
+
proposals_fileid[loe_docx_name] = None
|
| 376 |
+
docx_bytes = save_loe_as_docx(result, proposal_base_name)
|
| 377 |
+
logging.info(f"LOE generated and saved as {loe_docx_name}")
|
| 378 |
+
return result, None, None, loe_docx_name, docx_bytes
|
| 379 |
+
else:
|
| 380 |
+
return result, None, None, None, None
|
| 381 |
+
|
| 382 |
return "Action not implemented yet.", None, None, None, None
|
| 383 |
|
| 384 |
def get_documents_list(docdict, shreddedict):
|
|
|
|
| 421 |
for filename in proposaldict:
|
| 422 |
file_content = proposaldict[filename]
|
| 423 |
try:
|
| 424 |
+
if filename.lower().endswith('_loe.docx'):
|
| 425 |
+
docx_bytes = save_loe_as_docx(file_content, filename)
|
| 426 |
+
else:
|
| 427 |
+
docx_bytes = save_proposal_as_docx(file_content, filename)
|
| 428 |
b64 = base64.b64encode(docx_bytes).decode('utf-8')
|
| 429 |
mime = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
| 430 |
except Exception:
|
|
|
|
| 788 |
output_data_upload = dcc.Markdown(result, style={"whiteSpace": "pre-wrap", "wordWrap": "break-word"})
|
| 789 |
elif triggered_id == "loe-action-btn":
|
| 790 |
action_name = "loe"
|
|
|
|
| 791 |
result, _, _, generated_filename, generated_docx_bytes = process_document(
|
| 792 |
+
action_name, None, chat_input, None, proposal_value
|
| 793 |
)
|
| 794 |
output_data_upload = dcc.Markdown(result, style={"whiteSpace": "pre-wrap", "wordWrap": "break-word"})
|
| 795 |
finally:
|