Spaces:
Paused
Paused
| """Stochastic quarterly events that create cross-quarter cascading effects.""" | |
| from __future__ import annotations | |
| import random | |
| from dataclasses import dataclass | |
| from typing import List, Optional | |
| from hr_env.server.company import Company | |
| class Event: | |
| """A stochastic event that affects the company.""" | |
| name: str | |
| description: str | |
| probability: float | |
| affected_department: Optional[str] = None # None = company-wide | |
| def apply(self, company: Company, rng: random.Random) -> str: | |
| """Apply the event's effects. Returns description of what happened.""" | |
| raise NotImplementedError | |
| class MarketDownturn(Event): | |
| def __init__(self): | |
| super().__init__( | |
| name="market_downturn", | |
| description="Economic downturn reduces market demand", | |
| probability=0.10, | |
| ) | |
| def apply(self, company: Company, rng: random.Random) -> str: | |
| company.market_modifier *= 0.88 # Revenue -12% | |
| # Increase flight risk for top performers (they have options) | |
| for emp in company.all_active_employees: | |
| if emp.performance_score >= 4.0: | |
| emp.flight_risk = min(0.95, emp.flight_risk + 0.05) | |
| return "Market downturn: revenue reduced by 12%. Top performer flight risk increased." | |
| class CompetitorPoaching(Event): | |
| def __init__(self): | |
| super().__init__( | |
| name="competitor_poaching", | |
| description="Competitor aggressively recruiting from one department", | |
| probability=0.12, | |
| ) | |
| def apply(self, company: Company, rng: random.Random) -> str: | |
| dept_name = rng.choice(list(company.departments.keys())) | |
| self.affected_department = dept_name | |
| dept = company.departments[dept_name] | |
| for emp in dept.active_employees: | |
| emp.flight_risk = min(0.95, emp.flight_risk + 0.25) | |
| emp.update_engagement(-5) | |
| return f"Competitor poaching in {dept_name}: flight risk +25% for all department employees." | |
| class ProductLaunch(Event): | |
| def __init__(self): | |
| super().__init__( | |
| name="product_launch", | |
| description="Major product launch increases workload", | |
| probability=0.10, | |
| ) | |
| def apply(self, company: Company, rng: random.Random) -> str: | |
| for dept_name in ["Engineering", "Sales"]: | |
| dept = company.departments.get(dept_name) | |
| if dept: | |
| for emp in dept.active_employees: | |
| emp.update_engagement(-8) # Stress from overwork | |
| # But productivity boost | |
| emp.performance_score = min(5.0, emp.performance_score + 0.1) | |
| company.market_modifier *= 1.08 # Revenue boost from launch | |
| return "Product launch: Engineering/Sales workload +25%, engagement decreased, but revenue +8%." | |
| class ExecDeparture(Event): | |
| def __init__(self): | |
| super().__init__( | |
| name="exec_departure", | |
| description="Senior executive departs suddenly", | |
| probability=0.07, | |
| ) | |
| def apply(self, company: Company, rng: random.Random) -> str: | |
| dept_name = rng.choice(list(company.departments.keys())) | |
| self.affected_department = dept_name | |
| dept = company.departments[dept_name] | |
| # Remove highest-level employee | |
| seniors = sorted(dept.active_employees, key=lambda e: (-e.level, -e.tenure_months)) | |
| if seniors: | |
| seniors[0].is_active = False | |
| # Leadership gap: engagement drops | |
| for emp in dept.active_employees: | |
| emp.update_engagement(-12) | |
| return f"Executive departure in {dept_name}: leadership gap, engagement -12% department-wide." | |
| class MinWageIncrease(Event): | |
| def __init__(self): | |
| super().__init__( | |
| name="min_wage_increase", | |
| description="Regulatory minimum wage increase", | |
| probability=0.06, | |
| ) | |
| def apply(self, company: Company, rng: random.Random) -> str: | |
| floor_increase = 0.06 | |
| affected = 0 | |
| for emp in company.all_active_employees: | |
| if emp.salary < 50000: | |
| emp.salary *= (1 + floor_increase) | |
| affected += 1 | |
| return f"Minimum wage increase: {affected} employees received 6% salary floor adjustment." | |
| class EmployerAward(Event): | |
| def __init__(self): | |
| super().__init__( | |
| name="employer_award", | |
| description="Company wins employer award, boosting brand", | |
| probability=0.06, | |
| ) | |
| def apply(self, company: Company, rng: random.Random) -> str: | |
| # Boost engagement and reduce flight risk | |
| for emp in company.all_active_employees: | |
| emp.update_engagement(5) | |
| emp.flight_risk = max(0.02, emp.flight_risk - 0.05) | |
| return "Employer award: company brand boost, engagement +5, flight risk reduced." | |
| class BudgetCut(Event): | |
| def __init__(self): | |
| super().__init__( | |
| name="budget_cut", | |
| description="Corporate budget reduction for HR", | |
| probability=0.08, | |
| ) | |
| def apply(self, company: Company, rng: random.Random) -> str: | |
| reduction = company.hr_budget_remaining * 0.20 | |
| company.hr_budget_remaining -= reduction | |
| return f"Budget cut: HR budget reduced by 20% (${reduction:,.0f} lost)." | |
| class TrainingBreakthrough(Event): | |
| def __init__(self): | |
| super().__init__( | |
| name="training_breakthrough", | |
| description="Training program yields exceptional results in one department", | |
| probability=0.05, | |
| ) | |
| def apply(self, company: Company, rng: random.Random) -> str: | |
| dept_name = rng.choice(list(company.departments.keys())) | |
| self.affected_department = dept_name | |
| dept = company.departments[dept_name] | |
| for emp in dept.active_employees: | |
| emp.performance_score = min(5.0, emp.performance_score + 0.3) | |
| emp.update_engagement(5) | |
| return f"Training breakthrough in {dept_name}: productivity +8%, performance improved." | |
| ALL_EVENTS: List[Event] = [ | |
| MarketDownturn(), | |
| CompetitorPoaching(), | |
| ProductLaunch(), | |
| ExecDeparture(), | |
| MinWageIncrease(), | |
| EmployerAward(), | |
| BudgetCut(), | |
| TrainingBreakthrough(), | |
| ] | |
| def draw_quarterly_events(rng: random.Random, max_events: int = 2) -> List[Event]: | |
| """Draw 0-2 random events for a quarter based on probabilities.""" | |
| triggered = [] | |
| for event in ALL_EVENTS: | |
| if rng.random() < event.probability: | |
| triggered.append(event) | |
| # Cap at max_events | |
| if len(triggered) > max_events: | |
| triggered = rng.sample(triggered, max_events) | |
| return triggered | |
| def apply_events(company: Company, rng: random.Random) -> List[str]: | |
| """Draw and apply quarterly events. Returns list of event descriptions.""" | |
| events = draw_quarterly_events(rng) | |
| descriptions = [] | |
| for event in events: | |
| desc = event.apply(company, rng) | |
| descriptions.append(desc) | |
| return descriptions | |