Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -415,20 +415,31 @@ def recommend_preamble_solution(industry,
|
|
| 415 |
|
| 416 |
|
| 417 |
def calculate_build_vs_buy_comparison(initial_dev_cost=1000000, num_ai_personnel=1, avg_annual_salary=200000, annual_maintenance=500000, security_compliance=250000):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 418 |
|
| 419 |
first_year_cost = initial_dev_cost + (num_ai_personnel * avg_annual_salary) + security_compliance
|
| 420 |
ongoing_annual_cost = (num_ai_personnel * avg_annual_salary) + annual_maintenance + security_compliance
|
| 421 |
|
| 422 |
-
|
| 423 |
three_year_build_cost = first_year_cost + (ongoing_annual_cost * 2)
|
| 424 |
|
| 425 |
-
|
| 426 |
preamble_annual_cost = 27000 * 12
|
| 427 |
three_year_preamble_cost = preamble_annual_cost * 3
|
| 428 |
|
| 429 |
-
|
| 430 |
three_year_savings = three_year_build_cost - three_year_preamble_cost
|
| 431 |
-
savings_percentage = (three_year_savings / three_year_build_cost) * 100
|
| 432 |
|
| 433 |
return {
|
| 434 |
"first_year_build": first_year_cost,
|
|
@@ -752,34 +763,137 @@ CUSTOM_CSS = f"""
|
|
| 752 |
color: {BRAND_COLORS['result_text']} !important;
|
| 753 |
border-left: 5px solid {BRAND_COLORS['accent']} !important;
|
| 754 |
}}
|
| 755 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 756 |
.result-card h3 {{
|
| 757 |
font-size: 1.8rem !important;
|
| 758 |
-
margin
|
| 759 |
color: {BRAND_COLORS['result_text']} !important;
|
| 760 |
font-weight: 600 !important;
|
| 761 |
-
border-bottom:
|
| 762 |
-
padding-bottom: 0.
|
|
|
|
| 763 |
}}
|
|
|
|
| 764 |
.result-card h4 {{
|
| 765 |
font-size: 1.5rem !important;
|
| 766 |
margin: 1.2rem 0 0.8rem !important;
|
| 767 |
color: {BRAND_COLORS['result_text']} !important;
|
| 768 |
font-weight: 600 !important;
|
| 769 |
}}
|
| 770 |
-
|
| 771 |
-
.result-card
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 772 |
color: {BRAND_COLORS['result_text']} !important;
|
| 773 |
font-size: 16px !important;
|
| 774 |
line-height: 1.7 !important;
|
| 775 |
margin-bottom: 1rem !important;
|
| 776 |
}}
|
| 777 |
-
|
| 778 |
-
|
| 779 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 780 |
}}
|
|
|
|
| 781 |
.result-card li {{
|
| 782 |
-
margin-bottom: 0.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 783 |
}}
|
| 784 |
/* Buttons styling */
|
| 785 |
.calculate-button {{
|
|
@@ -1031,10 +1145,11 @@ def create_app():
|
|
| 1031 |
with gr.Row(elem_classes="input-row"):
|
| 1032 |
org_size = gr.Number(label="Number of Employees",
|
| 1033 |
value=90,
|
|
|
|
| 1034 |
elem_classes="number-input",
|
| 1035 |
precision=0)
|
| 1036 |
gr.Markdown(
|
| 1037 |
-
"""<div class="helper-text">This helps us scale recommendations appropriately for your organization size.</div>"""
|
| 1038 |
)
|
| 1039 |
|
| 1040 |
with gr.Row(elem_classes="input-row"):
|
|
@@ -1285,9 +1400,48 @@ def create_app():
|
|
| 1285 |
if total_employees > 0:
|
| 1286 |
current_total = updated_departments['Number of Employees'].sum()
|
| 1287 |
if total_employees != current_total:
|
| 1288 |
-
#
|
| 1289 |
-
|
| 1290 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1291 |
|
| 1292 |
# Update all subscription users at once
|
| 1293 |
updated_subscriptions['Number of Users'] = total_employees
|
|
@@ -1341,14 +1495,45 @@ def create_app():
|
|
| 1341 |
annual_maintenance, security_compliance, step):
|
| 1342 |
try:
|
| 1343 |
# Convert inputs to appropriate types with error handling
|
| 1344 |
-
|
| 1345 |
-
|
| 1346 |
-
|
| 1347 |
-
|
| 1348 |
-
|
| 1349 |
-
|
| 1350 |
-
|
| 1351 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1352 |
|
| 1353 |
# Validate dataframes and convert to default if invalid
|
| 1354 |
if department_df is None or department_df.empty:
|
|
|
|
| 415 |
|
| 416 |
|
| 417 |
def calculate_build_vs_buy_comparison(initial_dev_cost=1000000, num_ai_personnel=1, avg_annual_salary=200000, annual_maintenance=500000, security_compliance=250000):
|
| 418 |
+
# Convert inputs to numbers if they aren't already
|
| 419 |
+
try:
|
| 420 |
+
initial_dev_cost = float(initial_dev_cost)
|
| 421 |
+
num_ai_personnel = int(num_ai_personnel)
|
| 422 |
+
avg_annual_salary = float(avg_annual_salary)
|
| 423 |
+
annual_maintenance = float(annual_maintenance)
|
| 424 |
+
security_compliance = float(security_compliance)
|
| 425 |
+
except (ValueError, TypeError):
|
| 426 |
+
# Default values if conversion fails
|
| 427 |
+
initial_dev_cost = 1000000.0
|
| 428 |
+
num_ai_personnel = 1
|
| 429 |
+
avg_annual_salary = 200000.0
|
| 430 |
+
annual_maintenance = 500000.0
|
| 431 |
+
security_compliance = 250000.0
|
| 432 |
|
| 433 |
first_year_cost = initial_dev_cost + (num_ai_personnel * avg_annual_salary) + security_compliance
|
| 434 |
ongoing_annual_cost = (num_ai_personnel * avg_annual_salary) + annual_maintenance + security_compliance
|
| 435 |
|
|
|
|
| 436 |
three_year_build_cost = first_year_cost + (ongoing_annual_cost * 2)
|
| 437 |
|
|
|
|
| 438 |
preamble_annual_cost = 27000 * 12
|
| 439 |
three_year_preamble_cost = preamble_annual_cost * 3
|
| 440 |
|
|
|
|
| 441 |
three_year_savings = three_year_build_cost - three_year_preamble_cost
|
| 442 |
+
savings_percentage = (three_year_savings / three_year_build_cost) * 100 if three_year_build_cost > 0 else 0
|
| 443 |
|
| 444 |
return {
|
| 445 |
"first_year_build": first_year_cost,
|
|
|
|
| 763 |
color: {BRAND_COLORS['result_text']} !important;
|
| 764 |
border-left: 5px solid {BRAND_COLORS['accent']} !important;
|
| 765 |
}}
|
| 766 |
+
|
| 767 |
+
/* Enhanced Markdown Rendering - All heading levels */
|
| 768 |
+
.result-card h1 {{
|
| 769 |
+
font-size: 2.2rem !important;
|
| 770 |
+
margin: 1.5rem 0 1.2rem !important;
|
| 771 |
+
color: {BRAND_COLORS['accent']} !important;
|
| 772 |
+
font-weight: 700 !important;
|
| 773 |
+
border-bottom: 2px solid {BRAND_COLORS['accent']} !important;
|
| 774 |
+
padding-bottom: 0.6rem !important;
|
| 775 |
+
}}
|
| 776 |
+
|
| 777 |
+
.result-card h2 {{
|
| 778 |
+
font-size: 2rem !important;
|
| 779 |
+
margin: 1.4rem 0 1rem !important;
|
| 780 |
+
color: {BRAND_COLORS['result_text']} !important;
|
| 781 |
+
font-weight: 700 !important;
|
| 782 |
+
border-bottom: 1px solid {BRAND_COLORS['accent']} !important;
|
| 783 |
+
padding-bottom: 0.5rem !important;
|
| 784 |
+
}}
|
| 785 |
+
|
| 786 |
.result-card h3 {{
|
| 787 |
font-size: 1.8rem !important;
|
| 788 |
+
margin: 1.3rem 0 1rem !important;
|
| 789 |
color: {BRAND_COLORS['result_text']} !important;
|
| 790 |
font-weight: 600 !important;
|
| 791 |
+
border-bottom: 1px solid rgba(255, 199, 0, 0.5) !important;
|
| 792 |
+
padding-bottom: 0.4rem !important;
|
| 793 |
+
display: inline-block !important;
|
| 794 |
}}
|
| 795 |
+
|
| 796 |
.result-card h4 {{
|
| 797 |
font-size: 1.5rem !important;
|
| 798 |
margin: 1.2rem 0 0.8rem !important;
|
| 799 |
color: {BRAND_COLORS['result_text']} !important;
|
| 800 |
font-weight: 600 !important;
|
| 801 |
}}
|
| 802 |
+
|
| 803 |
+
.result-card h5 {{
|
| 804 |
+
font-size: 1.25rem !important;
|
| 805 |
+
margin: 1rem 0 0.6rem !important;
|
| 806 |
+
color: {BRAND_COLORS['result_text']} !important;
|
| 807 |
+
font-weight: 600 !important;
|
| 808 |
+
}}
|
| 809 |
+
|
| 810 |
+
/* Result card text elements */
|
| 811 |
+
.result-card p {{
|
| 812 |
color: {BRAND_COLORS['result_text']} !important;
|
| 813 |
font-size: 16px !important;
|
| 814 |
line-height: 1.7 !important;
|
| 815 |
margin-bottom: 1rem !important;
|
| 816 |
}}
|
| 817 |
+
|
| 818 |
+
.result-card strong, .result-card b {{
|
| 819 |
+
color: {BRAND_COLORS['accent']} !important;
|
| 820 |
+
font-weight: 600 !important;
|
| 821 |
+
}}
|
| 822 |
+
|
| 823 |
+
.result-card em, .result-card i {{
|
| 824 |
+
font-style: italic !important;
|
| 825 |
+
}}
|
| 826 |
+
|
| 827 |
+
.result-card a {{
|
| 828 |
+
color: {BRAND_COLORS['accent']} !important;
|
| 829 |
+
text-decoration: underline !important;
|
| 830 |
+
transition: all 0.2s ease !important;
|
| 831 |
+
}}
|
| 832 |
+
|
| 833 |
+
.result-card a:hover {{
|
| 834 |
+
opacity: 0.8 !important;
|
| 835 |
+
}}
|
| 836 |
+
|
| 837 |
+
/* Lists styling */
|
| 838 |
+
.result-card ul, .result-card ol {{
|
| 839 |
+
padding-left: 1.8rem !important;
|
| 840 |
+
margin: 0.8rem 0 1.2rem 0.5rem !important;
|
| 841 |
}}
|
| 842 |
+
|
| 843 |
.result-card li {{
|
| 844 |
+
margin-bottom: 0.7rem !important;
|
| 845 |
+
color: {BRAND_COLORS['result_text']} !important;
|
| 846 |
+
}}
|
| 847 |
+
|
| 848 |
+
/* Code blocks */
|
| 849 |
+
.result-card code {{
|
| 850 |
+
background-color: rgba(0, 0, 0, 0.3) !important;
|
| 851 |
+
padding: 0.2rem 0.4rem !important;
|
| 852 |
+
border-radius: 4px !important;
|
| 853 |
+
font-family: monospace !important;
|
| 854 |
+
font-size: 0.9rem !important;
|
| 855 |
+
}}
|
| 856 |
+
|
| 857 |
+
.result-card pre {{
|
| 858 |
+
background-color: rgba(0, 0, 0, 0.3) !important;
|
| 859 |
+
padding: 1rem !important;
|
| 860 |
+
border-radius: 6px !important;
|
| 861 |
+
overflow-x: auto !important;
|
| 862 |
+
margin: 1rem 0 !important;
|
| 863 |
+
}}
|
| 864 |
+
|
| 865 |
+
/* Blockquotes */
|
| 866 |
+
.result-card blockquote {{
|
| 867 |
+
border-left: 4px solid {BRAND_COLORS['accent']} !important;
|
| 868 |
+
padding-left: 1rem !important;
|
| 869 |
+
margin-left: 0 !important;
|
| 870 |
+
margin-right: 0 !important;
|
| 871 |
+
font-style: italic !important;
|
| 872 |
+
color: rgba(255, 255, 255, 0.9) !important;
|
| 873 |
+
}}
|
| 874 |
+
|
| 875 |
+
/* Tables */
|
| 876 |
+
.result-card table {{
|
| 877 |
+
width: 100% !important;
|
| 878 |
+
border-collapse: collapse !important;
|
| 879 |
+
margin: 1.5rem 0 !important;
|
| 880 |
+
}}
|
| 881 |
+
|
| 882 |
+
.result-card th {{
|
| 883 |
+
background-color: {BRAND_COLORS['primary']} !important;
|
| 884 |
+
color: {BRAND_COLORS['light_text']} !important;
|
| 885 |
+
padding: 0.8rem !important;
|
| 886 |
+
text-align: left !important;
|
| 887 |
+
font-weight: 600 !important;
|
| 888 |
+
}}
|
| 889 |
+
|
| 890 |
+
.result-card td {{
|
| 891 |
+
border: 1px solid rgba(255, 255, 255, 0.2) !important;
|
| 892 |
+
padding: 0.8rem !important;
|
| 893 |
+
}}
|
| 894 |
+
|
| 895 |
+
.result-card tr:nth-child(even) {{
|
| 896 |
+
background-color: rgba(0, 0, 0, 0.2) !important;
|
| 897 |
}}
|
| 898 |
/* Buttons styling */
|
| 899 |
.calculate-button {{
|
|
|
|
| 1145 |
with gr.Row(elem_classes="input-row"):
|
| 1146 |
org_size = gr.Number(label="Number of Employees",
|
| 1147 |
value=90,
|
| 1148 |
+
minimum=10,
|
| 1149 |
elem_classes="number-input",
|
| 1150 |
precision=0)
|
| 1151 |
gr.Markdown(
|
| 1152 |
+
"""<div class="helper-text">This helps us scale recommendations appropriately for your organization size (minimum 10 employees).</div>"""
|
| 1153 |
)
|
| 1154 |
|
| 1155 |
with gr.Row(elem_classes="input-row"):
|
|
|
|
| 1400 |
if total_employees > 0:
|
| 1401 |
current_total = updated_departments['Number of Employees'].sum()
|
| 1402 |
if total_employees != current_total:
|
| 1403 |
+
# Distribute employees proportionally across departments
|
| 1404 |
+
if current_total > 0:
|
| 1405 |
+
ratio = total_employees / current_total
|
| 1406 |
+
for idx in updated_departments.index:
|
| 1407 |
+
new_count = round(updated_departments.at[idx, 'Number of Employees'] * ratio)
|
| 1408 |
+
updated_departments.at[idx, 'Number of Employees'] = max(1, new_count) # Ensure at least 1 employee per department
|
| 1409 |
+
else:
|
| 1410 |
+
# If current total is 0, distribute evenly
|
| 1411 |
+
dept_count = len(updated_departments)
|
| 1412 |
+
base_count = total_employees // dept_count
|
| 1413 |
+
remainder = total_employees % dept_count
|
| 1414 |
+
|
| 1415 |
+
for idx in updated_departments.index:
|
| 1416 |
+
if idx < remainder:
|
| 1417 |
+
updated_departments.at[idx, 'Number of Employees'] = base_count + 1
|
| 1418 |
+
else:
|
| 1419 |
+
updated_departments.at[idx, 'Number of Employees'] = base_count
|
| 1420 |
+
|
| 1421 |
+
# Adjust to ensure exact total (due to rounding)
|
| 1422 |
+
current_sum = updated_departments['Number of Employees'].sum()
|
| 1423 |
+
if current_sum != total_employees:
|
| 1424 |
+
diff = total_employees - current_sum
|
| 1425 |
+
# Add or subtract the difference from the largest department to minimize impact
|
| 1426 |
+
largest_dept_idx = updated_departments['Number of Employees'].idxmax()
|
| 1427 |
+
updated_departments.at[largest_dept_idx, 'Number of Employees'] += diff
|
| 1428 |
+
# Ensure no negative values after adjustment
|
| 1429 |
+
if updated_departments.at[largest_dept_idx, 'Number of Employees'] < 1:
|
| 1430 |
+
# If the largest department would go negative, redistribute
|
| 1431 |
+
updated_departments.at[largest_dept_idx, 'Number of Employees'] = 1
|
| 1432 |
+
remaining_diff = diff + 1 - updated_departments.at[largest_dept_idx, 'Number of Employees']
|
| 1433 |
+
|
| 1434 |
+
# Distribute remaining difference across other departments
|
| 1435 |
+
for idx in updated_departments.index:
|
| 1436 |
+
if idx != largest_dept_idx and remaining_diff != 0:
|
| 1437 |
+
if remaining_diff < 0 and updated_departments.at[idx, 'Number of Employees'] > 1:
|
| 1438 |
+
updated_departments.at[idx, 'Number of Employees'] -= 1
|
| 1439 |
+
remaining_diff += 1
|
| 1440 |
+
elif remaining_diff > 0:
|
| 1441 |
+
updated_departments.at[idx, 'Number of Employees'] += 1
|
| 1442 |
+
remaining_diff -= 1
|
| 1443 |
+
if remaining_diff == 0:
|
| 1444 |
+
break
|
| 1445 |
|
| 1446 |
# Update all subscription users at once
|
| 1447 |
updated_subscriptions['Number of Users'] = total_employees
|
|
|
|
| 1495 |
annual_maintenance, security_compliance, step):
|
| 1496 |
try:
|
| 1497 |
# Convert inputs to appropriate types with error handling
|
| 1498 |
+
try:
|
| 1499 |
+
org_size_val = int(org_size) if org_size is not None else 90
|
| 1500 |
+
except (ValueError, TypeError):
|
| 1501 |
+
org_size_val = 90
|
| 1502 |
+
|
| 1503 |
+
try:
|
| 1504 |
+
monthly_budget_val = float(monthly_budget) if monthly_budget is not None else 30000
|
| 1505 |
+
except (ValueError, TypeError):
|
| 1506 |
+
monthly_budget_val = 30000.0
|
| 1507 |
+
|
| 1508 |
+
try:
|
| 1509 |
+
api_calls_val = int(api_calls) if api_calls is not None else 10000
|
| 1510 |
+
except (ValueError, TypeError):
|
| 1511 |
+
api_calls_val = 10000
|
| 1512 |
+
|
| 1513 |
+
try:
|
| 1514 |
+
initial_dev_cost_val = float(initial_dev_cost) if initial_dev_cost is not None else 1000000
|
| 1515 |
+
except (ValueError, TypeError):
|
| 1516 |
+
initial_dev_cost_val = 1000000.0
|
| 1517 |
+
|
| 1518 |
+
try:
|
| 1519 |
+
num_ai_personnel_val = int(num_ai_personnel) if num_ai_personnel is not None else 1
|
| 1520 |
+
except (ValueError, TypeError):
|
| 1521 |
+
num_ai_personnel_val = 1
|
| 1522 |
+
|
| 1523 |
+
try:
|
| 1524 |
+
avg_annual_salary_val = float(avg_annual_salary) if avg_annual_salary is not None else 200000
|
| 1525 |
+
except (ValueError, TypeError):
|
| 1526 |
+
avg_annual_salary_val = 200000.0
|
| 1527 |
+
|
| 1528 |
+
try:
|
| 1529 |
+
annual_maintenance_val = float(annual_maintenance) if annual_maintenance is not None else 500000
|
| 1530 |
+
except (ValueError, TypeError):
|
| 1531 |
+
annual_maintenance_val = 500000.0
|
| 1532 |
+
|
| 1533 |
+
try:
|
| 1534 |
+
security_compliance_val = float(security_compliance) if security_compliance is not None else 250000
|
| 1535 |
+
except (ValueError, TypeError):
|
| 1536 |
+
security_compliance_val = 250000.0
|
| 1537 |
|
| 1538 |
# Validate dataframes and convert to default if invalid
|
| 1539 |
if department_df is None or department_df.empty:
|