Spaces:
Paused
Paused
File size: 5,587 Bytes
670ccf0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | """Synthetic company generation using faker and numpy distributions."""
from __future__ import annotations
import random
from typing import Optional
import numpy as np
from faker import Faker
from hr_env.server.company import DEPARTMENT_NAMES, Company, Department
from hr_env.server.employee import Employee
# Department-specific role templates
ROLES = {
"Engineering": [
("Software Engineer", 1), ("Software Engineer", 2), ("Senior Engineer", 3),
("Staff Engineer", 4), ("Principal Engineer", 5),
],
"Sales": [
("Sales Rep", 1), ("Account Executive", 2), ("Senior AE", 3),
("Sales Manager", 4), ("VP Sales", 5),
],
"Operations": [
("Operations Analyst", 1), ("Operations Specialist", 2), ("Operations Manager", 3),
("Senior Ops Manager", 4), ("VP Operations", 5),
],
"HR": [
("HR Coordinator", 1), ("HR Specialist", 2), ("HR Manager", 3),
("Senior HR Manager", 4), ("VP People", 5),
],
"Finance": [
("Financial Analyst", 1), ("Senior Analyst", 2), ("Finance Manager", 3),
("Controller", 4), ("CFO", 5),
],
}
# Salary base by level (lognormal parameters)
SALARY_BASE = {1: 55000, 2: 75000, 3: 100000, 4: 135000, 5: 180000}
SALARY_SIGMA = 0.15 # lognormal spread
# Department size distribution (fraction of total)
DEPT_SIZE_FRACTIONS = {
"Engineering": 0.35,
"Sales": 0.25,
"Operations": 0.20,
"HR": 0.08,
"Finance": 0.12,
}
# Skills by department
DEPT_SKILLS = {
"Engineering": ["python", "java", "cloud", "ml", "devops", "frontend", "backend", "data"],
"Sales": ["negotiation", "crm", "pipeline", "presentation", "territory", "closing"],
"Operations": ["logistics", "process", "lean", "supply_chain", "quality", "planning"],
"HR": ["recruiting", "compliance", "training", "benefits", "employee_relations"],
"Finance": ["accounting", "budgeting", "forecasting", "audit", "tax", "reporting"],
}
def generate_company(
seed: int = 42,
size: int = 300,
name: str = "Simulated Corp",
base_revenue: Optional[float] = None,
hr_budget: Optional[float] = None,
) -> Company:
"""Generate a synthetic company with realistic employee distributions.
Args:
seed: Random seed for reproducibility.
size: Total number of employees (200-500 recommended).
name: Company name.
base_revenue: Annual baseline revenue. Defaults to size * 150_000.
hr_budget: Annual HR budget. Defaults to size * 6000.
"""
rng = np.random.default_rng(seed)
fake = Faker()
Faker.seed(seed)
if base_revenue is None:
base_revenue = size * 150_000
if hr_budget is None:
hr_budget = size * 6000
company = Company(
name=name,
base_revenue=base_revenue,
hr_budget=hr_budget,
hr_budget_remaining=hr_budget / 4, # First quarter allocation
rng=random.Random(seed), # seeded stream for deterministic turnover
)
for dept_name in DEPARTMENT_NAMES:
dept_size = max(5, int(size * DEPT_SIZE_FRACTIONS[dept_name]))
dept = Department(name=dept_name)
roles = ROLES[dept_name]
skills_pool = DEPT_SKILLS[dept_name]
for _ in range(dept_size):
# Level distribution: pyramid shape
level_probs = np.array([0.35, 0.30, 0.20, 0.10, 0.05])
level = int(rng.choice([1, 2, 3, 4, 5], p=level_probs))
# Find matching role
role_name = next((r for r, l in roles if l == level), roles[0][0])
# Salary: lognormal around base for level
base = SALARY_BASE[level]
salary = float(rng.lognormal(np.log(base), SALARY_SIGMA))
salary = round(max(35000, salary), -2) # Round to nearest 100
# Performance: normal(3.2, 0.8) clipped [1, 5]
perf = float(np.clip(rng.normal(3.2, 0.8), 1.0, 5.0))
# Tenure: exponential(lambda=24 months)
tenure = int(rng.exponential(24))
# Engagement: beta(7, 3) * 100
engagement = float(rng.beta(7, 3) * 100)
# Skills: 2-4 random from department pool
n_skills = min(len(skills_pool), int(rng.integers(2, 5)))
skills = list(rng.choice(skills_pool, size=n_skills, replace=False))
emp = Employee(
id=f"{dept_name[:3].lower()}_{fake.unique.random_int(min=1000, max=9999)}",
name=fake.name(),
department=dept_name,
role=role_name,
level=level,
salary=salary,
tenure_months=tenure,
performance_score=perf,
engagement=engagement,
skills=skills,
training_hours=float(rng.exponential(10)),
)
# Derived attributes
emp.promotability = float(np.clip(
0.1 + perf * 0.12 + (tenure / 60) * 0.1 + rng.normal(0, 0.1), 0, 1
))
emp.transferability = float(np.clip(
0.3 + len(skills) * 0.08 + rng.normal(0, 0.1), 0, 1
))
emp.retainability = float(np.clip(
0.4 + engagement / 200 + (tenure / 48) * 0.1 + rng.normal(0, 0.1), 0, 1
))
emp.update_flight_risk()
dept.employees.append(emp)
company.departments[dept_name] = dept
return company
|