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:
|