Spaces:
Paused
Paused
File size: 8,773 Bytes
8697ae6 80d9920 8697ae6 80d9920 | 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 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 | """FastAPI application entry point for the HR Productivity Environment."""
import json
from pathlib import Path
from typing import Optional
from fastapi import Query
from fastapi.responses import RedirectResponse
from openenv.core.env_server.http_server import create_app
from hr_env.models import HRAction, HRObservation
from hr_env.server.environment import HRProductivityEnvironment
app = create_app(
HRProductivityEnvironment,
HRAction,
HRObservation,
env_name="hr-productivity-env",
max_concurrent_envs=1,
)
@app.get("/")
async def root():
return RedirectResponse(url="/web")
# ββ Hyperparameters endpoint ββββββββββββββββββββββββββββββββββββββββ
@app.get("/hyperparameters")
async def hyperparameters():
"""Return all environment hyperparameters and configuration constants."""
from hr_env.server.company import (
BENEFITS_MULTIPLIER,
DEPT_CONFIGS,
RECRUITING_COST_PER_HIRE,
)
from hr_env.server.data_gen import (
DEPT_SIZE_FRACTIONS,
DEPT_SKILLS,
ROLES,
SALARY_BASE,
SALARY_SIGMA,
)
from hr_env.server.events import ALL_EVENTS
from hr_env.server.phases import PHASE_ACTIONS, PHASE_MIN_ACTIONS, PHASES
from hr_env.server.scoring import compute_final_score # noqa: F401
return {
"environment": {
"max_quarters": 6,
"max_steps": 300,
"max_concurrent_envs": 1,
"default_company_size": 300,
"recommended_size_range": [200, 500],
"departments": ["Engineering", "Sales", "Operations", "HR", "Finance"],
},
"company_financials": {
"base_revenue_formula": "size * 150,000",
"hr_budget_formula": "size * 6,000",
"quarterly_hr_budget": "hr_budget / 4",
"non_employment_cost_ratio": 0.55,
"benefits_multiplier": BENEFITS_MULTIPLIER,
"recruiting_cost_per_hire": RECRUITING_COST_PER_HIRE,
"training_cost_per_hour_per_employee": 50,
},
"cobb_douglas_production": {
dept: {
"A": cfg["A"],
"alpha": cfg["alpha"],
"beta": cfg["beta"],
"base_revenue_share": cfg["base_revenue_share"],
}
for dept, cfg in DEPT_CONFIGS.items()
},
"employee_generation": {
"salary_base_by_level": SALARY_BASE,
"salary_lognormal_sigma": SALARY_SIGMA,
"performance_distribution": {"type": "normal", "mean": 3.2, "std": 0.8, "clip": [1.0, 5.0]},
"tenure_distribution": {"type": "exponential", "lambda_months": 24},
"engagement_distribution": {"type": "beta", "alpha": 7, "beta": 3, "scale": 100},
"level_distribution": {
"type": "categorical",
"probabilities": {"L1": 0.35, "L2": 0.30, "L3": 0.20, "L4": 0.10, "L5": 0.05},
},
"dept_size_fractions": DEPT_SIZE_FRACTIONS,
"skills_per_employee": {"min": 2, "max": 4},
},
"employee_lifecycle": {
"promotion_salary_increase": 0.15,
"promotion_engagement_boost": 10,
"promotion_flight_risk_reduction": 0.15,
"quarterly_engagement_decay": -1.5,
"long_tenure_no_promotion_penalty": -3.0,
"long_tenure_threshold_quarters": 4,
"transfer_engagement_penalty": -5,
"termination_colleague_engagement_penalty": -3,
"new_hire_engagement_distribution": {"type": "beta", "alpha": 8, "beta": 2, "scale": 100},
},
"flight_risk": {
"model": "logistic",
"formula": "sigmoid(-1 + 2*pay_factor + 1.5*engagement_factor + 1*tenure_factor + 0.8*market_demand)",
"turnover_probability": "flight_risk * 0.4 per quarter",
"bounds": [0.02, 0.95],
},
"phase_system": {
"phases": PHASES,
"actions_per_phase": PHASE_ACTIONS,
"minimum_actions_per_phase": PHASE_MIN_ACTIONS,
},
"stochastic_events": [
{
"name": ev.name,
"description": ev.description,
"probability": ev.probability,
}
for ev in ALL_EVENTS
],
"scoring": {
"quarterly_reward_weights": {
"hcva_improvement": 0.40,
"hcroi_improvement": 0.20,
"qips_improvement": 0.20,
"five_indexes": 0.20,
},
"final_score_weights": {
"hcva_trajectory": 0.25,
"hcroi_final_vs_baseline": 0.20,
"employee_value": 0.20,
"qips_consistency": 0.15,
"five_indexes_cumulative": 0.10,
"financial_health": 0.10,
},
"calibration_targets": {
"random_agent": "0.15-0.25",
"reasonable_heuristic": "~0.50",
"strong_agent": "0.70+",
},
},
"heuristic_strategy": {
"description": "Data-driven heuristic used in the demo endpoint",
"hiring_rate": "12% of current headcount for primary dept, 8% for secondary",
"training_budget_fraction": 0.30,
"training_hours_per_session": 20,
"compensation_adjustment_pct": 3.0,
"retention_budget_fraction": 0.20,
"promotion_criteria": {"min_performance": 4.0, "max_level": 4, "max_per_quarter": 3},
"termination_criteria": {"max_performance": 1.8, "max_per_quarter": 2},
},
"roles_by_department": {
dept: [{"role": role, "level": level} for role, level in roles]
for dept, roles in ROLES.items()
},
"skills_by_department": DEPT_SKILLS,
}
# ββ Scenarios endpoint ββββββββββββββββββββββββββββββββββββββββββββββ
@app.get("/scenarios")
async def scenarios():
"""Return all available task scenarios with their configurations."""
tasks_dir = Path(__file__).resolve().parent.parent / "tasks"
task_list = []
if tasks_dir.exists():
for f in sorted(tasks_dir.glob("*.json")):
try:
task_list.append(json.loads(f.read_text()))
except (json.JSONDecodeError, OSError):
continue
# Also include scenario effect descriptions
scenario_effects = {
"high_eng_turnover": {
"modifications": "Engineering flight_risk += 0.20, engagement -= 10",
"challenge": "Stabilize retention in a bleeding engineering department",
},
"budget_cuts": {
"modifications": "HR budget halved (50% reduction)",
"challenge": "Optimize workforce with severely constrained resources",
},
"rapid_growth": {
"modifications": "Revenue +30%, 15% employees deactivated (understaffing)",
"challenge": "Scale the workforce while maintaining quality and culture",
},
"balanced_optimization": {
"modifications": "All employees: performance -0.3, engagement -8",
"challenge": "Improve below-average metrics across the board",
},
}
return {
"scenarios": [
{
**task,
"effects": scenario_effects.get(task.get("params", {}).get("scenario"), {}),
}
for task in task_list
],
"default": {
"seed": 42,
"size": 300,
"scenario": None,
"description": "Standard company with no scenario modifications",
},
}
# ββ Demo endpoint βββββββββββββββββββββββββββββββββββββββββββββββββββ
@app.get("/demo")
async def demo(
seed: int = Query(42, description="Random seed for reproducibility"),
size: int = Query(300, ge=50, le=1000, description="Company size (employees)"),
scenario: Optional[str] = Query(
None,
description="Scenario variant",
enum=["high_eng_turnover", "budget_cuts", "rapid_growth", "balanced_optimization"],
),
):
"""Run a full 6-quarter demo with a heuristic strategy and return results.
Executes data-driven HR decisions through all HCM:21 phases for 6 quarters.
No LLM API key required β uses a built-in heuristic agent.
"""
from demo import run_demo_json
return run_demo_json(seed=seed, size=size, scenario=scenario)
|