hcm21 / hr_env /server /events.py
ParetoOptimal's picture
Initial release: HCM:21 HR Productivity Measurement Environment
8697ae6
Raw
History Blame Contribute Delete
7.06 kB
"""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
@dataclass
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