import joblib
import numpy as np
import gradio as gr
# Lataa malli
model = joblib.load("workload_model.joblib")
CLASS_NAMES = {0: "Low", 1: "Medium", 2: "High"}
EMOJI = {"Low": "🟢", "Medium": "🟠", "High": "🔴"}
# Esimerkkipäivät dropdownille
EXAMPLE_PRESETS = {
"Balanced day": [3, 2.5, 4, 2, 45, 9, 16],
"Overloaded day": [9, 7.5, 15, 0, 10, 9, 18],
"Focused day": [2, 1.5, 2, 3, 60, 8, 15],
}
def predict_workload(
meetings_count,
total_meeting_hours,
context_switches,
deep_work_blocks,
break_minutes,
day_start_hour,
day_end_hour,
):
X = np.array([[
meetings_count,
total_meeting_hours,
context_switches,
deep_work_blocks,
break_minutes,
day_start_hour,
day_end_hour,
]])
probs = model.predict_proba(X)[0]
pred_class = int(np.argmax(probs))
pred_label = CLASS_NAMES[pred_class]
confidence = float(probs[pred_class])
probs_dict = {name: float(probs[i]) for i, name in CLASS_NAMES.items()}
headline = (
f"
"
f"{EMOJI[pred_label]} {pred_label} Workload"
f"
"
f""
f"Confidence: {confidence * 100:.1f}%"
f"
"
)
# --- practical tips ---
tips = []
day_length = day_end_hour - day_start_hour
if meetings_count >= 8 or total_meeting_hours >= 6:
tips.append(
"Consider blocking meeting-free focus time (e.g. 2–3h) and moving "
"non-essential meetings to another day."
)
if context_switches >= 12:
tips.append(
"Try to batch similar tasks or meetings together to reduce context switching."
)
if deep_work_blocks == 0:
tips.append(
"Add at least one deep work block (60–90 minutes) for focused work without notifications."
)
if break_minutes < 30 and day_length >= 8:
tips.append(
"Increase break time slightly – even short 5–10 minute breaks every few hours reduce fatigue."
)
if day_length > 9:
tips.append(
"Your workday is long – consider moving low-priority tasks to another day or finishing earlier."
)
if not tips:
tips.append(
"Your setup looks balanced! To stay sustainable, consider scheduling regular deep work and micro-breaks."
)
tips_html = ""
for tip in tips:
tips_html += f"- {tip}
"
tips_html += "
"
explanation = (
"💡 Personalized Suggestions
"
+ tips_html +
"🧠 How the Model Works
"
"This AI estimates your perceived workload using:
"
""
"- Number and duration of meetings
"
"- Frequency of task/meeting switches (context switches)
"
"- Presence of uninterrupted deep work blocks
"
"- Total break time during the day
"
"- Overall workday length
"
"
"
)
return headline, probs_dict, explanation
def load_example(example_name):
"""Täyttää sliderit valitun esimerkin arvoilla."""
if example_name in EXAMPLE_PRESETS:
return EXAMPLE_PRESETS[example_name]
return [4, 3, 6, 1, 30, 9, 17]
# Teema
theme = gr.themes.Soft(
primary_hue=gr.themes.colors.orange,
secondary_hue=gr.themes.colors.rose,
neutral_hue=gr.themes.colors.slate,
radius_size=gr.themes.sizes.radius_md,
).set(
button_primary_background_fill="*primary_500",
button_primary_background_fill_hover="*primary_600",
block_title_text_weight="600",
)
# CSS
css = """
#app-container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1.5rem;
}
#app-header {
text-align: center;
margin-bottom: 2rem;
}
#app-header h1 {
font-weight: 700;
font-size: 2.2rem;
color: #222;
margin-bottom: 0.4rem;
}
#app-header p {
font-size: 1.1rem;
color: #666;
max-width: 650px;
margin: 0 auto;
line-height: 1.5;
}
.gr-button-primary {
font-weight: 600;
padding: 0.6rem 1.4rem;
font-size: 1.05rem;
}
@media (max-width: 768px) {
#app-header h1 {
font-size: 1.8rem;
}
#app-header p {
font-size: 1rem;
}
.gr-button-primary {
width: 100%;
padding: 0.7rem;
}
}
"""
with gr.Blocks(theme=theme, css=css, title="🗓️ Workload Estimator") as demo:
with gr.Column(elem_id="app-container"):
gr.Markdown(
"""
""",
elem_id="header"
)
with gr.Tab("📊 Estimate Your Day"):
with gr.Row(equal_height=False):
# VASEN SARake: dropdown + sliderit + nappi
with gr.Column(scale=2):
example_dropdown = gr.Dropdown(
label="💡 Load example schedule",
choices=list(EXAMPLE_PRESETS.keys()),
value=None,
interactive=True,
)
gr.Markdown("### 🗓️ Workday Structure")
meetings_count = gr.Slider(0, 12, value=4, step=1, label="Meetings (count)")
total_meeting_hours = gr.Slider(0, 9, value=3, step=0.5, label="Total meeting hours")
context_switches = gr.Slider(0, 20, value=6, step=1, label="Context switches")
gr.Markdown("### 🧘 Focus & Recovery")
deep_work_blocks = gr.Slider(0, 4, value=1, step=1, label="Deep work blocks (≥60 min)")
break_minutes = gr.Slider(0, 120, value=30, step=5, label="Total break minutes")
gr.Markdown("### ⏰ Workday Timing")
day_start_hour = gr.Slider(6, 11, value=9, step=1, label="Start hour (24h)")
day_end_hour = gr.Slider(14, 21, value=17, step=1, label="End hour (24h)")
btn = gr.Button("🔍 Analyze Workload", variant="primary")
# OIKEA sarake: tulos
with gr.Column(scale=1):
gr.Markdown("### 📈 Result")
headline_out = gr.HTML()
probs_out = gr.Label(label="Workload Probabilities")
explanation_out = gr.HTML()
# Kun valitaan esimerkki → täytetään sliderit
example_dropdown.change(
load_example,
inputs=example_dropdown,
outputs=[
meetings_count,
total_meeting_hours,
context_switches,
deep_work_blocks,
break_minutes,
day_start_hour,
day_end_hour,
],
)
# Varsinainen ennustekutsu
btn.click(
predict_workload,
inputs=[
meetings_count,
total_meeting_hours,
context_switches,
deep_work_blocks,
break_minutes,
day_start_hour,
day_end_hour,
],
outputs=[headline_out, probs_out, explanation_out],
)
with gr.Tab("ℹ️ About"):
gr.Markdown("""
### About This Tool
This demo shows how **calendar metadata** can be used to estimate cognitive workload — helping you reflect on sustainability, focus, and recovery.
- **Synthetic data only**: No real user data was used.
- **Model**: Trained `RandomForestClassifier` (scikit-learn).
- **Output**: 3-class workload (`Low`, `Medium`, `High`).
- **Goal**: Spark reflection, not replace judgment.
Use this to **simulate "what-if" scenarios**:
_"What if I cancel two meetings?"_ or _"What if I add a 90-min focus block?"_
Built with **Python**, **scikit-learn** and **Gradio**, deployed on **Hugging Face Spaces**.
""")
if __name__ == "__main__":
demo.launch()