CoolFace
Apppublic

HNS8273/FlexTime-AI

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes
App README

๐Ÿ• FlexTime โ€” AI Workforce Scheduling Environment

![OpenEnv](https://github.com/openenv/openenv) ![Python](https://python.org) ![FastAPI](https://fastapi.tiangolo.com) ![License: MIT](LICENSE)


FlexTime is a fully-featured 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

PropertyValue
DomainWorkforce Scheduling / Operations Research
Tasks3 (Easy โ†’ Medium โ†’ Hard)
Episode horizon20 / 60 / 120 steps
RewardDense, shaped (โ€“1.0 to +1.0)
Action spaceDiscrete: assign, remove, swap, noop
ObservationStructured JSON: employees, shifts, assignments, metrics
Constraints4 hard (H1โ€“H4) + 4 soft (S1โ€“S4)
Baseline agentGreedy (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]

ComponentSignalValue
shift_coveredNew shift filled+0.15 ร— ฮ”coverage
demand_signalDemand-weighted coverage+0.05 ร— ฮ”demand
constraint_violatedNew hard violationโ€“0.20 per violation
constraint_resolvedViolation removed+0.10 per resolution
fairnessFairness score improved+0.03 ร— ฮ”fairness
conflict_resolvedPre-seeded conflict fixed+0.10 bonus
invalid_actionNon-existent IDs, etc.โ€“0.05
noopNo-operation0.0

๐Ÿ”’ Constraint System

Hard Constraints (must not be violated)

IDNameDescription
H1No Overlapping ShiftsOne shift per employee per (day, period)
H2Skillโ€“Role MatchEmployee skills โЇ shift required_skill
H3Max Hoursฮฃ hours โ‰ค maxhoursper_week (typically 40h)
H4AvailabilityEmployee must be available on shift day

Soft Constraints (penalized in objective)

IDNameDescription
S1Fair Distributionmax(hours) โ€“ min(hours) โ‰ค 4h
S2Preference MatchingAssign preferred shift period when possible
S3Min 11h Rest Gapโ‰ฅ 11h between consecutive shifts
S4Consecutive Daysโ‰ค 5 consecutive working days

๐Ÿ“Š Baseline Scores

Reproducible scores with seed=42, GreedyBaseline agent:

TaskScoreStepsPass?
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. 1.Create a new HF Space: Docker template, tagged openenv
  2. 2.Push this repo to the Space
  3. 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