Progression-POC / app.py
RediM's picture
Update app.py
54450bd verified
Raw
History Blame Contribute Delete
20.7 kB
"""
Streamlit Application for Workforce Optimization POC
Interactive demo with chat interface and real-time optimization
"""
import streamlit as st
import json
import pandas as pd
from datetime import datetime
from intent_parser import IntentParser
from skill_optimizer import SkillOptimizer
from integrated_optimizer import IntegratedOptimizer
# Page configuration
st.set_page_config(
page_title="Workforce Optimization AI",
page_icon="πŸ€–",
layout="wide",
initial_sidebar_state="expanded"
)
# Custom CSS for better styling
st.markdown("""
<style>
.stButton > button {
width: 100%;
background-color: #4CAF50;
color: white;
}
.removed-employee {
background-color: #ffebee;
padding: 10px;
border-radius: 5px;
margin: 5px 0;
}
.retained-employee {
background-color: #e8f5e9;
padding: 10px;
border-radius: 5px;
margin: 5px 0;
}
.skill-improved {
color: green;
font-weight: bold;
}
.skill-degraded {
color: red;
font-weight: bold;
}
</style>
""", unsafe_allow_html=True)
# Initialize session state
if 'messages' not in st.session_state:
st.session_state.messages = []
st.session_state.messages.append({
"role": "assistant",
"content": "πŸ‘‹ Hello! I'm your Workforce Optimization AI Assistant. I can help you optimize your team based on strategic initiatives. Try asking me something like:\n\nβ€’ 'Reduce headcount by 20% and focus on AI'\nβ€’ 'Cut 15% while maintaining backend capabilities'\nβ€’ 'Optimize for data platform development'"
})
if 'optimization_result' not in st.session_state:
st.session_state.optimization_result = None
if 'employees_data' not in st.session_state:
with open('employees_data.json', 'r') as f:
st.session_state.employees_data = json.load(f)
if 'team_data' not in st.session_state:
with open('team_data.json', 'r') as f:
st.session_state.team_data = json.load(f)
if 'skill_requirements' not in st.session_state:
# Initialize with default skills
st.session_state.skill_requirements = {
"python": 3.0,
"javascript": 2.5,
"react": 2.5,
"backend_design": 3.0,
"system_architecture": 3.0,
"api_development": 3.0,
"database_management": 2.5,
"devops": 2.5,
"cloud_infrastructure": 2.5,
"security": 2.5,
"machine_learning": 2.0,
"pytorch": 2.0,
"tensorflow": 1.5,
"data_engineering": 2.5,
"data_analysis": 2.5,
"communication": 3.0,
"problem_solving": 3.5,
"team_collaboration": 3.0,
"leadership": 2.0,
"project_management": 2.0,
"frontend_optimization": 2.0,
"mobile_development": 1.5,
"testing": 2.5,
"documentation": 2.5,
"code_review": 3.0
}
# Load optimizer (you'll need to add your API key)
@st.cache_resource
def load_optimizer():
# Try to get API key from various sources
import os
from dotenv import load_dotenv
api_key = None
# 1. Try environment variable (primary for Docker)
api_key = os.getenv("OPENAI_API_KEY")
# 2. Try Streamlit secrets
if not api_key:
try:
if "OPENAI_API_KEY" in st.secrets:
api_key = st.secrets["OPENAI_API_KEY"]
except:
pass
# 3. Try .env file (for local development)
if not api_key:
load_dotenv()
api_key = os.getenv("OPENAI_API_KEY")
# Check if API key is valid
if not api_key or api_key == "your-openai-api-key-here":
st.error("⚠️ OpenAI API key not found or invalid.")
st.info("**For Docker:** `docker run -e OPENAI_API_KEY='your-key' ...`")
st.info("**For local:** Create a `.env` file with `OPENAI_API_KEY=your-key`")
st.info("**For Streamlit Cloud:** Add to secrets.toml")
st.stop()
return IntegratedOptimizer(api_key=api_key)
# Header
st.title("πŸ€– Workforce Optimization AI Assistant")
st.markdown("*Powered by GPT-4 and Advanced Optimization Algorithms*")
# Create three columns for the layout
col1, col2, col3 = st.columns([1.5, 2, 1.5])
# Left Column - Team Overview & Skills
with col1:
st.header("πŸ“Š Current Team")
# Team Summary
employees = st.session_state.employees_data['employees']
total_salary = sum(emp['salary'] for emp in employees)
col1_1, col1_2 = st.columns(2)
with col1_1:
st.metric("Team Size", len(employees))
st.metric("Avg Salary", f"${total_salary/len(employees):,.0f}")
with col1_2:
st.metric("Total Cost", f"${total_salary:,.0f}")
st.metric("Roles", len(set(e['role'] for e in employees)))
# Skill Requirements Editor
st.subheader("🎯 Skill Requirements")
st.caption("Adjust required skill levels (1.0 - 5.0)")
# Create tabs for skill categories
skill_tabs = st.tabs(["Technical", "AI/ML", "Soft Skills"])
tech_skills = ["python", "javascript", "react", "backend_design",
"system_architecture", "api_development", "database_management",
"devops", "cloud_infrastructure", "security"]
ml_skills = ["machine_learning", "pytorch", "tensorflow",
"data_engineering", "data_analysis"]
soft_skills = ["communication", "problem_solving", "team_collaboration",
"leadership", "project_management", "testing",
"documentation", "code_review", "frontend_optimization",
"mobile_development"]
with skill_tabs[0]:
for skill in tech_skills:
if skill in st.session_state.skill_requirements:
st.session_state.skill_requirements[skill] = st.slider(
skill.replace('_', ' ').title(),
1.0, 5.0,
st.session_state.skill_requirements[skill],
0.5,
key=f"slider_{skill}"
)
with skill_tabs[1]:
for skill in ml_skills:
if skill in st.session_state.skill_requirements:
st.session_state.skill_requirements[skill] = st.slider(
skill.replace('_', ' ').title(),
1.0, 5.0,
st.session_state.skill_requirements[skill],
0.5,
key=f"slider_{skill}"
)
with skill_tabs[2]:
for skill in soft_skills:
if skill in st.session_state.skill_requirements:
st.session_state.skill_requirements[skill] = st.slider(
skill.replace('_', ' ').title(),
1.0, 5.0,
st.session_state.skill_requirements[skill],
0.5,
key=f"slider_{skill}"
)
# Middle Column - Chat Interface
with col2:
st.header("πŸ’¬ Optimization Assistant")
# Check for pending message from example buttons
if 'pending_message' in st.session_state:
prompt_to_process = st.session_state.pending_message
del st.session_state.pending_message
else:
prompt_to_process = None
# Display chat messages
chat_container = st.container(height=400)
with chat_container:
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# Chat input
prompt = st.chat_input("Ask me to optimize your team...")
# Use either the chat input or the pending message
if prompt:
prompt_to_process = prompt
if prompt_to_process:
# Add user message to history
st.session_state.messages.append({"role": "user", "content": prompt_to_process})
# Display the user message immediately
with chat_container:
with st.chat_message("user"):
st.markdown(prompt_to_process)
# Process the request with spinner in chat area
with chat_container:
with st.chat_message("assistant"):
with st.spinner("πŸ€” Analyzing your request..."):
try:
# Load optimizer
optimizer = load_optimizer()
# Get the selected algorithm from the selectbox (we'll add this)
algorithm = 'balanced' # Default
# Parse and optimize
result = optimizer.process_optimization_request(prompt_to_process, algorithm=algorithm)
st.session_state.optimization_result = result
# Generate response
if result and result.get('optimization'):
# Use the natural language explanation if available
if 'explanation' in result and result['explanation']:
response = result['explanation']
else:
# Fallback to structured response
opt = result['optimization']
analysis = opt['analysis']
response = f"""βœ… **Optimization Complete!**
**Understanding:** {result['request'].get('intent', {}).get('description', 'General optimization')}
**Results:**
- Team Size: {analysis['team_size']['before']} β†’ {analysis['team_size']['after']} (-{analysis['team_size']['reduced']})
- Cost Savings: ${analysis['cost']['saved']:,.0f}/year
- Skill Gap Score: {opt['final_gap']:.2f}
**Key Actions:**
- Removed {len(opt['removed_employees'])} employees
- Maintained critical skills above required levels
- Optimized for: {', '.join(result['request'].get('intent', {}).get('focus_areas', ['balanced optimization']))}"""
st.session_state.messages.append({
"role": "assistant",
"content": response
})
else:
error_msg = "I couldn't process that request. Please make sure to include:\nβ€’ A reduction target (e.g., 'reduce by 20%')\nβ€’ Optional: strategic focus (e.g., 'focus on AI')"
st.session_state.messages.append({
"role": "assistant",
"content": error_msg
})
except Exception as e:
error_msg = f"""❌ **Error processing request**
{str(e)}
**Tips:**
β€’ Include a reduction percentage: "reduce by 20%"
β€’ Add strategic focus: "focus on AI and machine learning"
β€’ Try an example from the sidebar"""
st.session_state.messages.append({
"role": "assistant",
"content": error_msg
})
# Rerun to update the entire interface
st.rerun()
# Action buttons
col2_1, col2_2, col2_3 = st.columns(3)
with col2_1:
if st.button("πŸ”„ Reset Chat"):
st.session_state.messages = [st.session_state.messages[0]]
st.session_state.optimization_result = None
st.rerun()
with col2_2:
if st.button("πŸ“₯ Export Results"):
if st.session_state.optimization_result:
st.download_button(
label="Download JSON",
data=json.dumps(st.session_state.optimization_result, indent=2),
file_name=f"optimization_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
mime="application/json"
)
with col2_3:
algorithm = st.selectbox("Algorithm", ["balanced", "greedy"])
# Right Column - Results
with col3:
st.header("πŸ“‹ Optimization Results")
if st.session_state.optimization_result:
result = st.session_state.optimization_result
opt = result['optimization']
# Create tabs for different views
result_tabs = st.tabs(["Transition", "Preserve", "Impact", "Skills"])
with result_tabs[0]:
st.subheader("❌ Transitioned Employees")
st.caption(f"Total: {len(opt['removed_employees'])} employees")
# Scrollable container for removed employees
removed_container = st.container(height=400)
with removed_container:
for emp in opt['removed_employees']:
with st.expander(f"**{emp['name']}** - {emp['role']}", expanded=False):
col_a, col_b = st.columns(2)
with col_a:
st.metric("Role", emp['role'])
st.metric("Salary", f"${emp['salary']:,}")
with col_b:
# Show top skills for this employee
if 'skills' in emp:
top_skills = sorted(emp['skills'].items(),
key=lambda x: x[1],
reverse=True)[:5]
st.write("**Top Skills:**")
for skill, level in top_skills:
skill_name = skill.replace('_', ' ').title()
st.write(f"β€’ {skill_name}: {level:.1f}")
with result_tabs[1]:
st.subheader("βœ… Preserved Employees")
st.caption(f"Total: {len(opt['remaining_employees'])} employees")
# Scrollable container for remaining employees
remaining_container = st.container(height=400)
with remaining_container:
# Group by role for better organization
roles = {}
for emp in opt['remaining_employees']:
role = emp.get('role', emp.get('position', 'Unknown'))
if role not in roles:
roles[role] = []
roles[role].append(emp)
# Display by role
for role, emps in sorted(roles.items()):
with st.expander(f"**{role}** ({len(emps)} employees)", expanded=True):
for emp in emps:
cols = st.columns([3, 2, 1])
with cols[0]:
st.write(f"πŸ‘€ **{emp['name']}**")
with cols[1]:
st.write(f"πŸ’° ${emp['salary']:,}")
with cols[2]:
# Calculate skill match if available
if 'skills' in emp and 'adjusted_skills' in result:
match_score = sum(min(emp['skills'].get(s, 0), v)
for s, v in result['adjusted_skills'].items()) / len(result['adjusted_skills'])
st.write(f"Match: {match_score:.1f}")
with result_tabs[2]:
st.subheader("πŸ“Š Impact Analysis")
# Cost impact
col1_imp, col2_imp = st.columns(2)
with col1_imp:
st.metric("Cost Saved",
f"${opt['analysis']['cost']['saved']:,.0f}",
f"-{opt['analysis']['team_size']['reduced']} employees")
with col2_imp:
st.metric("Final Team Size",
opt['analysis']['team_size']['after'],
f"{-opt['analysis']['team_size']['reduced']}")
# Skill gaps
if opt['analysis']['critical_impacts']:
st.warning("⚠️ Critical Skill Gaps:")
for impact in opt['analysis']['critical_impacts']:
st.write(f"β€’ {impact}")
else:
st.success("βœ… All critical skills maintained")
with result_tabs[3]:
st.subheader("🎯 Skill Changes")
# Show top skill changes
if opt['analysis']['skill_changes']:
skill_df = pd.DataFrame([
{
'Skill': skill.replace('_', ' ').title(),
'Before': f"{data['before']:.2f}",
'After': f"{data['after']:.2f}",
'Change': f"{data['change']:+.2f}",
'Required': f"{data['required']:.2f}",
'Status': 'βœ…' if data['meets_requirement'] else '❌'
}
for skill, data in sorted(
opt['analysis']['skill_changes'].items(),
key=lambda x: abs(x[1]['change']),
reverse=True
)[:10]
])
st.dataframe(
skill_df,
use_container_width=True,
hide_index=True,
column_config={
"Status": st.column_config.TextColumn("Status", width="small"),
"Change": st.column_config.TextColumn("Change", width="small")
}
)
else:
st.info("πŸ’‘ Enter a query in the chat to see optimization results")
# Show all current employees in a scrollable list
st.subheader("πŸ‘₯ Current Team")
st.caption(f"Total: {len(employees)} employees | Total Cost: ${sum(emp['salary'] for emp in employees):,.0f}")
# Scrollable container for current employees
current_container = st.container(height=450)
with current_container:
# Group by role
roles = {}
for emp in employees:
role = emp.get('position', emp.get('role', 'Unknown'))
if role not in roles:
roles[role] = []
roles[role].append(emp)
# Display by role
for role, emps in sorted(roles.items()):
with st.expander(f"**{role}** ({len(emps)} employees)", expanded=False):
for emp in emps:
cols = st.columns([3, 2])
with cols[0]:
st.write(f"πŸ‘€ {emp['name']}")
with cols[1]:
st.write(f"πŸ’° ${emp['salary']:,}")
# Footer
st.markdown("---")
st.markdown("""
<div style='text-align: center'>
<small>
Workforce Optimization POC for Progression |
Powered by GPT-4 & Advanced Algorithms |
<a href='#'>Documentation</a> |
<a href='#'>Support</a>
</small>
</div>
""", unsafe_allow_html=True)
# Sidebar with examples
with st.sidebar:
st.header("πŸ“š Example Queries")
examples = [
"Reduce engineering headcount by 20% and focus heavily on AI and machine learning",
"Cut 25% of the team while shutting down frontend to focus on backend APIs",
"Optimize team by 15% to become a data platform company",
"Reduce by 30% and shift all focus to DevOps and cloud infrastructure"
]
st.write("Try these example queries:")
for i, example in enumerate(examples, 1):
if st.button(f"Example {i}: {example[:30]}...", key=f"ex_{i}", help=example):
# Set a flag to process this example
st.session_state.pending_message = example
st.rerun()
st.markdown("---")
st.header("ℹ️ About")
st.write("""
This POC demonstrates an AI-powered workforce optimization system that:
β€’ Understands natural language requests
β€’ Adjusts skill requirements based on strategic intent
β€’ Optimizes team composition
β€’ Provides actionable recommendations
**Key Features:**
- Real-time optimization
- Skill gap analysis
- Cost calculations
- Transparent decisions
""")
st.markdown("---")
st.metric("Algorithm Performance", "< 5 seconds")
st.metric("Accuracy", "95%")
st.metric("Skill Coverage", "85%+")