Spaces:
Sleeping
Sleeping
| 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 | |
| [](https://github.com/openenv/openenv) | |
| [](https://python.org) | |
| [](https://fastapi.tiangolo.com) | |
| [](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 | |