ROICalculator / app.py
SChodavarpu's picture
Update app.py
48d4734 verified
import gradio as gr
def calculate_radiology_ai_roi(scans_per_day, radiologist_reading_time_minutes, er_time_to_treatment):
scans_per_month = scans_per_day * 30
errors_reduced_per_month = int(scans_per_month * 0.05 * 0.20)
hrs_saved_per_visit = round(er_time_to_treatment * 0.50 / 60, 2)
time_saved_per_scan_minutes = radiologist_reading_time_minutes * 0.30
total_time_saved_hours = round((scans_per_month * time_saved_per_scan_minutes) / 60, 2)
discrepant_cases_flagged = int(scans_per_month * 0.05)
radiologist_cost_savings = f"${int(scans_per_month * 4):,}"
return (
errors_reduced_per_month,
discrepant_cases_flagged,
hrs_saved_per_visit,
total_time_saved_hours,
radiologist_cost_savings
)
def render_result_cards(e, f, h, t, c, active=False):
# This function now generates the HTML for the result cards
# The styling is handled by the embedded CSS
return f"""
<div class="result-grid">
<div class="result-card {"active" if active else "faded"}">
<div class="result-value">{e}</div>
<div class="result-label">Errors reduced/month</div>
</div>
<div class="result-card {"active" if active else "faded"}">
<div class="result-value">{f}</div>
<div class="result-label">Discrepant cases flagged/month</div>
</div>
<div class="result-card {"active" if active else "faded"}">
<div class="result-value">{h}</div>
<div class="result-label">Hours saved per ER visit</div>
</div>
<div class="result-card {"active" if active else "faded"}">
<div class="result-value">{t}</div>
<div class="result-label">Radiologist hours saved/month</div>
</div>
<div class="result-card {"active" if active else "faded"}">
<div class="result-value">{c}</div>
<div class="result-label">Radiologist cost savings/month</div>
</div>
</div>
"""
def launch_ui():
# CSS is now embedded directly within the gr.Blocks call
custom_css = """
/* General Body and Font Styles */
body {
font-family: 'Inter', sans-serif; /* Using Inter font */
background-color: #000000; /* Completely black background */
color: #E0E0E0; /* Light gray for general text */
margin: 0;
padding: 20px;
display: flex;
justify-content: center;
align-items: flex-start;
min-height: 100vh;
box-sizing: border-box;
}
/* Main Gradio Container Styling */
.gradio-container {
max-width: 1200px;
width: 100%;
background-color: #121212; /* Softer dark gray for the main container */
border-radius: 15px;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.5); /* Subtle dark shadow */
padding: 30px;
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 25px;
}
/* Headings */
h1 {
color: #FFFFFF; /* White for main heading */
text-align: center;
text-shadow: none; /* No text glow for main heading */
margin-bottom: 5px;
}
h3 {
color: #FFFFFF; /* White for subheadings */
text-align: center;
text-shadow: none; /* No text glow for subheadings */
margin-bottom: 10px;
}
/* Markdown text (subheading below h1) */
.gradio-container > p { /* Target the direct child p for the subheading */
color: #FFFFFF; /* White for paragraph text */
text-align: center;
margin-top: 5px;
margin-bottom: 25px;
}
/* Main content row (inputs and results) */
.main-container {
display: flex;
flex-wrap: wrap; /* Allow wrapping on smaller screens */
gap: 30px;
justify-content: center;
align-items: flex-start;
}
/* Input Card Styling */
.input-card {
flex: 1;
min-width: 300px; /* Minimum width for input column */
background-color: #1E1E1E; /* Darker gray for input card */
padding: 25px;
border-radius: 12px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.4); /* Softer shadow */
display: flex;
flex-direction: column;
gap: 15px;
}
/* Gradio Input Fields - Targeting for dark background and no borders/shadows */
.input-field label {
color: #90EE90 !important; /* Lighter, more subtle green for input labels */
font-weight: bold;
}
/* Target the core input elements and their immediate containers */
.input-field .gr-block.gr-input,
.input-field .gr-number input[type="number"],
.input-field .gr-text-input,
.input-field .gr-box,
.input-field .gr-input-wrap { /* Added .gr-input-wrap for broader coverage */
background-color: #282828 !important; /* Dark background for inputs */
border: none !important; /* No border for inputs */
box-shadow: none !important; /* No shadow for inputs */
color: #E0E0E0 !important; /* Light text in input */
border-radius: 8px !important;
padding: 10px 15px !important;
font-size: 1em !important;
transition: all 0.3s ease;
}
/* Ensure focus state is also clean */
.input-field .gr-number input[type="number"]:focus,
.input-field .gr-text-input:focus {
outline: none !important;
border: 1px solid #00A000 !important; /* Very subtle muted green border on focus */
box-shadow: 0 0 5px rgba(0, 160, 0, 0.3) !important; /* Very soft glow on focus */
}
/* Calculate Button Styling */
.calculate-button {
background-color: #00A000 !important; /* Muted green button */
color: #FFFFFF !important; /* White text on button */
font-weight: bold !important;
padding: 12px 25px !important;
border-radius: 10px !important;
border: none !important;
cursor: pointer !important;
font-size: 1.1em !important;
transition: all 0.3s ease !important;
box-shadow: 0 0 10px rgba(0, 160, 0, 0.4) !important; /* Subtle green glow */
}
.calculate-button:hover {
background-color: #00C000 !important; /* Slightly lighter green on hover */
box-shadow: 0 0 15px rgba(0, 192, 0, 0.6) !important; /* Stronger subtle glow on hover */
transform: translateY(-2px);
}
/* Results Section Styling */
.results-section {
flex: 2; /* Take more space */
min-width: 400px; /* Minimum width for results column */
background-color: #1E1E1E; /* Darker gray for results card */
padding: 25px;
border-radius: 12px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.4); /* Softer shadow */
display: flex;
flex-direction: column;
gap: 20px;
}
/* Result Grid for Cards */
.result-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); /* Responsive grid for cards */
gap: 15px;
margin-top: 15px;
}
/* Individual Result Card Styling - No borders, no shadows for a flat look */
.result-card {
background-color: #282828; /* Darker background for cards */
border-radius: 10px;
padding: 20px;
text-align: center;
box-shadow: none !important; /* No shadow */
border: none !important; /* No border */
transition: all 0.3s ease;
}
.result-card.active {
background-color: #1a1a1a; /* Slightly darker when active */
/* No border or shadow here either for a flat look */
}
.result-card.faded {
opacity: 0.7; /* Slightly faded when not active */
}
.result-value {
font-size: 2.2em;
font-weight: bold;
color: #90EE90; /* Lighter, more subtle green for values */
margin-bottom: 5px;
text-shadow: none; /* No text shadow for values */
}
.result-label {
font-size: 0.9em;
color: #B0B0B0; /* Light gray for labels */
}
/* Call to Action (CTA) Section */
.cta-section {
background-color: #1E1E1E; /* Dark gray for CTA section */
padding: 25px;
border-radius: 12px;
text-align: center;
margin-top: 30px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.4); /* Softer shadow */
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
}
.cta-section p {
font-size: 1.1em;
color: #E0E0E0;
margin-bottom: 0; /* Remove default margin */
}
.cta-button {
background-color: #00A000; /* Muted green button */
color: #FFFFFF; /* White text */
font-weight: bold;
padding: 12px 30px;
border-radius: 10px;
text-decoration: none; /* No underline */
font-size: 1.1em;
transition: all 0.3s ease;
box-shadow: 0 0 10px rgba(0, 160, 0, 0.4); /* Subtle green glow */
display: inline-block; /* Allows padding and margin */
}
.cta-button:hover {
background-color: #00C000; /* Slightly lighter muted green on hover */
box-shadow: 0 0 15px rgba(0, 192, 0, 0.6); /* Stronger subtle glow */
transform: translateY(-2px);
}
/* Responsive adjustments */
@media (max-width: 768px) {
.main-container {
flex-direction: column; /* Stack columns on small screens */
align-items: center;
}
.input-card, .results-section {
width: 100%; /* Full width on small screens */
min-width: unset; /* Remove min-width constraint */
}
.result-grid {
grid-template-columns: 1fr; /* Single column for result cards on very small screens */
}
.gradio-container {
padding: 15px;
}
}
"""
with gr.Blocks(css=custom_css, title="Radiology ROI Calculator") as demo:
gr.Markdown("# Estimate the Value of AI in Your Practice")
gr.Markdown("Calculate the ROI of MSK AI in emergency radiology settings.")
with gr.Row(elem_classes="main-container"): # Use a new class for the main layout
with gr.Column(elem_classes="input-card"): # Class for the input section
gr.Markdown("### Your Inputs")
scans = gr.Number(label="Scans per day", value=100, elem_classes="input-field")
reading_time = gr.Number(label="Radiologist time per scan (minutes)", value=3, elem_classes="input-field")
er_time = gr.Number(label="ER time to treatment (minutes)", value=60, elem_classes="input-field")
submit = gr.Button("Calculate ROI", elem_classes="calculate-button")
with gr.Column(elem_classes="results-section"): # Class for the results section
gr.Markdown("### Results")
result_container = gr.HTML(render_result_cards('--', '--', '--', '--', '--'))
# The CTA section, now with custom styling
gr.HTML("""
<div class="cta-section">
<p>Want to see how this works in action?</p>
<a href="https://carpl.ai/contact-us" target="_blank" class="cta-button">Book a Demo</a>
</div>
""")
submit.click(
fn=lambda s, r, e: render_result_cards(*calculate_radiology_ai_roi(s, r, e), active=True),
inputs=[scans, reading_time, er_time],
outputs=[result_container]
)
demo.launch()
if __name__ == "__main__":
launch_ui()