""" 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(""" """, 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("""