--- title: FlexTime AI emoji: ๐Ÿ• colorFrom: green colorTo: blue sdk: docker app_port: 7860 tags: - openenv - scheduling - reinforcement-learning - workforce - optimization - real-world pinned: false license: mit short_description: Real-world AI workforce scheduling OpenEnv environment. --- # ๐Ÿ• FlexTime โ€” AI Workforce Scheduling Environment [![OpenEnv](https://img.shields.io/badge/OpenEnv-Compliant-22c55e?style=flat-square)](https://github.com/openenv/openenv) [![Python](https://img.shields.io/badge/Python-3.11-3b82f6?style=flat-square)](https://python.org) [![FastAPI](https://img.shields.io/badge/FastAPI-0.115-22c55e?style=flat-square)](https://fastapi.tiangolo.com) [![License: MIT](https://img.shields.io/badge/License-MIT-f59e0b?style=flat-square)](LICENSE) --- **FlexTime** is a fully-featured [OpenEnv](https://github.com/openenv/openenv)-compliant environment where AI agents learn to solve the **real-world workforce scheduling problem**: assigning employees to shifts while satisfying hard operational constraints (skill matching, availability, maximum hours) and optimizing soft objectives (fairness, employee preferences, demand coverage). This is a problem that operations managers in **retail, healthcare, logistics, and hospitality** face every week โ€” affecting millions of workers worldwide. FlexTime models it faithfully, making it both a genuine benchmark and a practical tool for developing AI scheduling assistants. --- ## ๐Ÿ—บ Environment Overview | Property | Value | |---|---| | **Domain** | Workforce Scheduling / Operations Research | | **Tasks** | 3 (Easy โ†’ Medium โ†’ Hard) | | **Episode horizon** | 20 / 60 / 120 steps | | **Reward** | Dense, shaped (โ€“1.0 to +1.0) | | **Action space** | Discrete: assign, remove, swap, noop | | **Observation** | Structured JSON: employees, shifts, assignments, metrics | | **Constraints** | 4 hard (H1โ€“H4) + 4 soft (S1โ€“S4) | | **Baseline agent** | Greedy (rule-based) + LLM (OpenAI API) | --- ## โšก Quick Start ### Docker ```bash git clone https://huggingface.co/spaces/your-org/flextime cd flextime docker build -t flextime . docker run -p 7860:7860 flextime # Environment live at http://localhost:7860 ``` ### Local (no Docker) ```bash pip install -r requirements.txt uvicorn app.main:app --host 0.0.0.0 --port 7860 --reload ``` ### Run Baseline ```bash # Rule-based greedy baseline (no API key needed) python -m scripts.baseline # LLM-based baseline (requires OpenAI key) export OPENAI_API_KEY=sk-... python -m scripts.baseline --llm --model gpt-4o-mini # Single task python -m scripts.baseline --task task_hard ``` --- ## ๐Ÿ“‹ Task Definitions ### Task 1 โ€” Basic Shift Coverage (Easy) **Target score: 1.0** Assign employees to 5 open morning shifts for a single day. All employees are available; skills match every shift. The agent needs to fill all slots without creating overlaps. ``` Employees: 5 Shifts: 5 Max steps: 20 Constraints: H1 (no overlap), H2 (skill match) ``` ### Task 2 โ€” Weekly Schedule with Constraints (Medium) **Target score: 0.85** Build a complete weekly schedule for 8 employees across 30 shifts. Must respect: - Skill requirements (cashier, supervisor, inventory) - Availability windows per employee per day - 40-hour maximum working week per employee - Soft fairness distribution (ยฑ4h between min/max hours) ``` Employees: 8 Shifts: 30 Max steps: 60 Constraints: H1โ€“H4 (hard) + S1โ€“S2 (soft) ``` ### Task 3 โ€” Fair Optimization Under Pressure (Hard) **Target score: 0.75 on ALL sub-metrics simultaneously** The hardest task: 12 employees, 50 shifts, 3 pre-seeded conflicts to resolve. The grader applies a joint threshold โ€” **all** of coverage, fairness, constraint satisfaction, and demand satisfaction must exceed 0.75 simultaneously. Missing any single threshold triggers a penalty: `score = min(sub_scores) ร— 0.9`. ``` Employees: 12 Shifts: 50 Max steps: 120 Constraints: H1โ€“H4 + S1โ€“S4 (all 8 active) Pre-seeded conflicts: 3 Skills: 5 (cashier, supervisor, inventory, customer_service, technician) ``` --- ## ๐Ÿ”Œ API Reference All endpoints are OpenEnv-compliant. Interactive docs at `/docs`. ### Core Endpoints #### `POST /reset` Initialize or reset the environment for a given task. ```json // Request { "task_id": "task_medium", "seed": 42 } // Response: Observation { "week_id": "week-a3f912", "task_id": "task_medium", "employees": [...], "shifts": [...], "assignments": [], "unassigned_shifts": ["shf001", "shf002", ...], "conflicts": [], "metrics": { "total_shifts": 30, "assigned_shifts": 0, "coverage_rate": 0.0, "hard_violations": 0, "fairness_delta": 0.0, "fairness_score": 1.0, "demand_satisfaction": 0.0, "preference_satisfaction": 0.0 }, "done": false, "step_count": 0, "max_steps": 60 } ``` #### `POST /step` Apply an action. Returns new observation, shaped reward, done flag, and info dict. ```json // Request โ€” Action { "action_type": "assign", // assign | remove | swap | noop "employee_id": "emp001", // required for assign/remove/swap "shift_id": "shf042", // required for assign/remove "target_employee_id": "emp005" // required for swap only } // Response: StepResult { "observation": { ... }, // full Observation "reward": { "total": 0.15, "components": { "shift_covered": 0.225, "demand_signal": 0.043, "constraint_violated": 0.0, "fairness": 0.02 }, "info": { "result": "action=assign, ฮ”coverage=+0.033, hard_violations=0" } }, "done": false, "info": { "step": "1", "coverage": "0.033" } } ``` #### `GET /state` Returns current observation without changing state. #### `GET /tasks` Returns all tasks with descriptions, schemas, and expected difficulty. #### `GET /grader` Returns normalized score (0.0โ€“1.0) for the current episode. ```json { "task_id": "task_medium", "score": 0.842, "breakdown": { "coverage_score": 0.9333, "fairness_score": 0.875, "constraint_score": 0.9, "demand_score": 0.891, "preference_score": 0.723 }, "passed": true, "summary": "Score 0.8420 (PASS) โ€” Coverage 93.3%, Fairness 87.5%, Hard violations: 1" } ``` #### `POST /baseline` Runs the baseline agent against all 3 tasks and returns reproducible scores. ```json // Query param: ?use_llm=true (requires OPENAI_API_KEY) { "model": "GreedyBaseline", "results": [...], "mean_score": 0.717, "timestamp": "2026-03-30T12:00:00Z" } ``` --- ## ๐Ÿ“ฆ Observation Space ```python class Observation(BaseModel): week_id: str # Unique episode identifier task_id: str # Which task is active employees: List[Employee] # Full roster with skills, availability, hours shifts: List[Shift] # All shifts with day, period, skill, demand assignments: List[Dict] # Active assignments [{employee_id, shift_id, hours}] unassigned_shifts: List[str] # Shift IDs still needing coverage conflicts: List[ConstraintViolation] # Active violations with type and severity metrics: ScheduleMetrics # Computed KPIs done: bool # Episode termination flag step_count: int max_steps: int ``` --- ## ๐ŸŽฎ Action Space ```python class Action(BaseModel): action_type: Literal["assign", "remove", "swap", "noop"] employee_id: Optional[str] # emp001 ... emp012 shift_id: Optional[str] # shf001 ... shf050 target_employee_id: Optional[str] # For swap only ``` --- ## ๐Ÿ† Reward Function Dense shaped reward at every step. Range: **[โ€“1.0, +1.0]** | Component | Signal | Value | |---|---|---| | `shift_covered` | New shift filled | +0.15 ร— ฮ”coverage | | `demand_signal` | Demand-weighted coverage | +0.05 ร— ฮ”demand | | `constraint_violated` | New hard violation | โ€“0.20 per violation | | `constraint_resolved` | Violation removed | +0.10 per resolution | | `fairness` | Fairness score improved | +0.03 ร— ฮ”fairness | | `conflict_resolved` | Pre-seeded conflict fixed | +0.10 bonus | | `invalid_action` | Non-existent IDs, etc. | โ€“0.05 | | `noop` | No-operation | 0.0 | --- ## ๐Ÿ”’ Constraint System ### Hard Constraints (must not be violated) | ID | Name | Description | |---|---|---| | H1 | No Overlapping Shifts | One shift per employee per (day, period) | | H2 | Skillโ€“Role Match | Employee skills โЇ shift required_skill | | H3 | Max Hours | ฮฃ hours โ‰ค max_hours_per_week (typically 40h) | | H4 | Availability | Employee must be available on shift day | ### Soft Constraints (penalized in objective) | ID | Name | Description | |---|---|---| | S1 | Fair Distribution | max(hours) โ€“ min(hours) โ‰ค 4h | | S2 | Preference Matching | Assign preferred shift period when possible | | S3 | Min 11h Rest Gap | โ‰ฅ 11h between consecutive shifts | | S4 | Consecutive Days | โ‰ค 5 consecutive working days | --- ## ๐Ÿ“Š Baseline Scores Reproducible scores with seed=42, GreedyBaseline agent: | Task | Score | Steps | Pass? | |---|---|---|---| | task_easy | ~0.95 | ~5 | โœ… | | task_medium | ~0.72 | ~28 | โŒ (target 0.85) | | task_hard | ~0.48 | ~52 | โŒ (target 0.75) | | **Mean** | **~0.72** | | | The gap between greedy (~0.72) and target (~0.85) on medium/hard provides excellent learning signal for RL agents. --- ## ๐Ÿงช Testing ```bash pip install pytest pytest-asyncio httpx # Run all tests pytest tests/ -v # Run specific test class pytest tests/test_environment.py::TestSpecCompliance -v # Run with coverage pytest tests/ --cov=app --cov-report=html ``` --- ## ๐Ÿ“ Project Structure ``` flextime/ โ”œโ”€โ”€ app/ โ”‚ โ”œโ”€โ”€ __init__.py โ”‚ โ”œโ”€โ”€ main.py # FastAPI application, all endpoints โ”‚ โ”œโ”€โ”€ engine.py # Core environment: state, step, reward, grader โ”‚ โ”œโ”€โ”€ models.py # Pydantic typed models (Observation, Action, Reward) โ”‚ โ””โ”€โ”€ static/ โ”‚ โ””โ”€โ”€ index.html # Interactive demo UI โ”œโ”€โ”€ scripts/ โ”‚ โ”œโ”€โ”€ __init__.py โ”‚ โ””โ”€โ”€ baseline.py # Greedy + LLM baseline agents & CLI โ”œโ”€โ”€ tests/ โ”‚ โ””โ”€โ”€ test_environment.py # Full pytest test suite โ”œโ”€โ”€ openenv.yaml # OpenEnv spec metadata โ”œโ”€โ”€ requirements.txt โ”œโ”€โ”€ Dockerfile โ””โ”€โ”€ README.md ``` --- ## ๐Ÿค— HuggingFace Spaces Deployment 1. Create a new HF Space: **Docker** template, tagged `openenv` 2. Push this repo to the Space 3. The Space will auto-build and serve at `https://huggingface.co/spaces/your-org/flextime` The HF Space will automatically: - Build the Docker image - Expose port 7860 - Respond to `reset()` pings for OpenEnv validation --- ## ๐Ÿง  Why Workforce Scheduling? Workforce scheduling is a **genuine, high-value operations problem**: - **Scale**: Affects billions of shift workers globally (retail, healthcare, logistics) - **Complexity**: Multi-constraint combinatorial optimization (NP-hard in general) - **Real cost**: Poor scheduling โ†’ $millions in overtime, turnover, and burnout - **AI gap**: Existing tools are rule-based; LLM/RL agents could outperform dramatically - **Fairness stakes**: Biased scheduling has real worker welfare consequences FlexTime provides the first OpenEnv environment in this domain, enabling the community to benchmark and train agents on a problem with immediate real-world deployment value. --- ## ๐Ÿ“„ License MIT ยฉ FlexTime Team