Shift Assignment
Assign workers to shifts based on availability while meeting minimum coverage and per-worker capacity constraints.
Assign workers to shifts based on availability while meeting minimum coverage and per-worker capacity constraints.
Assign workers to shifts based on availability to meet coverage requirements.
Browse files
Browse files
Browse files
What this template is for
Workforce planners often need to staff multiple shifts while respecting who is available and how much each person can work. This template models a small shift assignment problem where:
- Workers can only be assigned to shifts they’re available for.
- Each shift must meet a minimum coverage requirement.
- Each worker can work at most a limited number of shifts.
This template uses RelationalAI’s prescriptive reasoning (optimization) capabilities to find a feasible assignment that satisfies all constraints. Because this is a feasibility / constraint satisfaction problem, there is no objective value—any schedule that meets the rules is acceptable.
Who this is for
- You want an end-to-end example of prescriptive reasoning (optimization) with RelationalAI where the goal is feasibility.
- You’re comfortable with basic Python and the idea of decision variables and constraints.
What you’ll build
- A semantic model of workers, shifts, and worker–shift availability.
- A binary decision variable
Assignment.x_assignedfor each available worker–shift pair. - Constraints that enforce minimum shift coverage and per-worker assignment limits.
- A solve step that uses the
minizincbackend and prints an assignment table.
What’s included
- Model + solve script:
shift_assignment.py - Sample data:
data/workers.csv,data/shifts.csv,data/availability.csv - Outputs: solver termination status, an assignments table, and a per-shift coverage summary
Prerequisites
Access
- A Snowflake account that has the RAI Native App installed.
- A Snowflake user with permissions to access the RAI Native App.
Tools
- Python >= 3.10
- RelationalAI CLI (
rai) for setting up your profile
Quickstart
Follow these steps to run the template with the included sample data.
-
Download the ZIP file for this template and extract it:
Terminal window curl -O https://private.relational.ai/templates/zips/v0.13/shift_assignment.zipunzip shift_assignment.zipcd shift_assignment -
Create and activate a virtual environment
Terminal window python -m venv .venvsource .venv/bin/activatepython -m pip install -U pip -
Install dependencies
From this folder:
Terminal window python -m pip install . -
Configure Snowflake connection and RAI profile
Terminal window rai init -
Run the template
Terminal window python shift_assignment.py -
Expected output
Your exact assignments may vary between runs, but you should see a feasible status plus an assignments table and coverage summary:
Status: OPTIMALAssignments:worker shiftAlice MorningBob NightDiana AfternoonCoverage per shift:shift workersAfternoon 2Morning 2Night 2
Template structure
.├─ README.md├─ pyproject.toml├─ shift_assignment.py # main runner / entrypoint└─ data/ # sample input data ├─ workers.csv ├─ shifts.csv └─ availability.csvStart here: shift_assignment.py
Sample data
Data files are in data/.
workers.csv
| Column | Meaning |
|---|---|
id | Unique worker identifier |
name | Worker name (used for output labeling) |
shifts.csv
| Column | Meaning |
|---|---|
id | Unique shift identifier |
name | Shift name (e.g., Morning, Afternoon, Night) |
capacity | Sample data field (not enforced by this template as written) |
availability.csv
| Column | Meaning |
|---|---|
worker_id | Foreign key to workers.csv.id |
shift_id | Foreign key to shifts.csv.id |
Each row declares one allowed worker–shift pairing.
The script only creates Assignment entities for these rows, so unlisted pairings are impossible by construction.
Model overview
The semantic model for this template is built around three concepts.
Worker
A worker who may be assigned to at most a limited number of shifts.
| Property | Type | Identifying? | Notes |
|---|---|---|---|
id | int | Yes | Loaded as the key from data/workers.csv |
name | string | No | Used for output labeling |
Shift
A shift that must be staffed with at least min_coverage workers.
| Property | Type | Identifying? | Notes |
|---|---|---|---|
id | int | Yes | Loaded as the key from data/shifts.csv |
name | string | No | Used for output labeling |
Assignment (decision concept)
One entity per available worker–shift pair, created by joining availability.csv to Worker and Shift.
| Property | Type | Identifying? | Notes |
|---|---|---|---|
worker | Worker | Part of compound key | Joined via availability.csv.worker_id |
shift | Shift | Part of compound key | Joined via availability.csv.shift_id |
assigned | int | No | Binary decision variable (0/1) |
How it works
This section walks through the highlights in shift_assignment.py.
Import libraries and configure inputs
First, the script imports the Semantics APIs and configures local inputs like DATA_DIR:
from pathlib import Path
import pandasfrom pandas import read_csv
from relationalai.semantics import Model, data, require, select, sum, wherefrom relationalai.semantics.reasoners.optimization import Solver, SolverModel
# --------------------------------------------------# Configure inputs# --------------------------------------------------
DATA_DIR = Path(__file__).parent / "data"
# Disable pandas inference of string types. This ensures that string columns# in the CSVs are loaded as object dtype. This is only required when using# relationalai versions prior to v1.0.pandas.options.future.infer_string = FalseDefine concepts and load CSV data
Next, it creates a Model, defines Worker and Shift concepts, and loads the corresponding CSVs using data(...).into(...):
# --------------------------------------------------# Define semantic model & load data# --------------------------------------------------
# Create a Semantics model container.model = Model("shift_assignment", config=globals().get("config", None), use_lqp=False)
# Worker concept: employees available for scheduling.Worker = model.Concept("Worker")Worker.id = model.Property("{Worker} has {id:int}")Worker.name = model.Property("{Worker} has {name:string}")
# Load worker data from CSV.data(read_csv(DATA_DIR / "workers.csv")).into(Worker, keys=["id"])
# Shift concept: time periods that require staffing.Shift = model.Concept("Shift")Shift.id = model.Property("{Shift} has {id:int}")Shift.name = model.Property("{Shift} has {name:string}")
# Load shift data from CSV.data(read_csv(DATA_DIR / "shifts.csv")).into(Shift, keys=["id"])Create decision entities from availability
Then it defines an Assignment decision concept and uses where(...).define(...) to create one Assignment entity for each available worker–shift pair:
# Assignment decision concept: a worker-shift pair that can potentially be staffed.# The availability table determines which pairs exist.Assignment = model.Concept("Assignment")Assignment.worker = model.Property("{Assignment} has {worker:Worker}")Assignment.shift = model.Property("{Assignment} has {shift:Shift}")Assignment.x_assigned = model.Property("{Assignment} assigned {assigned:int}")
# Load availability data from CSV.avail = data(read_csv(DATA_DIR / "availability.csv"))
# Define Assignment entities by joining availability rows to Worker and Shift.where( Worker.id == avail.worker_id, Shift.id == avail.shift_id).define( Assignment.new(worker=Worker, shift=Shift))Define decision variables and constraints
With the feasible worker–shift pairs defined, the template creates a SolverModel, declares a binary decision variable, and adds two constraints using require(...) and sum(...).per(...):
# --------------------------------------------------# Model the decision problem# --------------------------------------------------
# Parameters.min_coverage = 2max_shifts_per_worker = 1
Asn = Assignment.ref()
s = SolverModel(model, "int")
# Variable: binary assignment (0 or 1)s.solve_for( Assignment.x_assigned, name=["x", Assignment.worker.name, Assignment.shift.name], type="bin",)
# Constraint: each shift has minimum coverageshift_coverage = where( Asn.shift == Shift).require( sum(Asn.assigned).per(Shift) >= min_coverage)s.satisfy(shift_coverage)
# Constraint: each worker works at most max_shifts_per_worker shiftsworker_capacity = where( Asn.worker == Worker).require( sum(Asn.assigned).per(Worker) <= max_shifts_per_worker)s.satisfy(worker_capacity)Solve and print results
Finally, it solves the model using the minizinc backend and prints the assignments and coverage summary:
# --------------------------------------------------# Solve and check solution# --------------------------------------------------
solver = Solver("minizinc")s.solve(solver, time_limit_sec=60)
print(f"Status: {s.termination_status}")
assignments = select( Assignment.worker.name.alias("worker"), Assignment.shift.name.alias("shift")).where(Assignment.x_assigned >= 1).to_df()
print("\nAssignments:")print(assignments.to_string(index=False))
print("\nCoverage per shift:")print(assignments.groupby("shift").size().reset_index(name="workers").to_string(index=False))Customize this template
Use your own data
- Replace the CSVs in
data/with your own data. - Keep the required headers:
workers.csv:id,nameshifts.csv:id,nameavailability.csv:worker_id,shift_id
- Ensure
availability.csv.worker_idvalues exist inworkers.csv.idandavailability.csv.shift_idvalues exist inshifts.csv.id.
Tune parameters
Update the parameters in shift_assignment.py:
min_coverage: minimum workers per shiftmax_shifts_per_worker: maximum shifts per worker
If you increase min_coverage or reduce availability, the problem may become infeasible.
Extend the model
Common extensions include:
- Max coverage (capacity): If you want to enforce a maximum number of workers per shift, add a
Shift.capacityproperty, load it fromshifts.csv, and requiresum(Asn.assigned).per(Shift) <= Shift.capacity. - Preferences or costs: Add a weight per worker–shift pair and switch from pure feasibility to minimizing total cost.
Troubleshooting
I got ModuleNotFoundError when running the script
- Confirm your virtual environment is active.
- Reinstall dependencies from the template folder:
python -m pip install . - Confirm you’re using Python 3.10+.
I can’t authenticate / the script can’t connect to Snowflake
- Re-run
rai initand confirm your profile is set up. - If you use multiple profiles, set
RAI_PROFILEand retry. - Confirm your Snowflake user has access to the RelationalAI Native App.
I got a CSV error (missing file or missing columns)
- Confirm these files exist in
data/:workers.csv,shifts.csv,availability.csv. - Confirm required headers:
workers.csv:id,nameshifts.csv:id,nameavailability.csv:worker_id,shift_id- Make sure IDs are consistent across files (foreign keys actually match).
The solver returns Status: INFEASIBLE
- Lower
min_coverageor increase worker availability inavailability.csv. - Increase
max_shifts_per_workerif you have too few workers to cover all shifts. - Sanity-check that every shift appears in
availability.csvat leastmin_coveragetimes.
What this template is for
Workforce planners often need to staff multiple shifts while respecting who is available and how much each person can work. This template models a small shift assignment problem where:
- Workers can only be assigned to shifts they’re available for.
- Each shift must meet a minimum coverage requirement.
- Each worker can work at most a limited number of shifts.
This template uses RelationalAI’s prescriptive reasoning (optimization) capabilities to find a feasible assignment that satisfies all constraints. Because this is a feasibility / constraint satisfaction problem, there is no objective value—any schedule that meets the rules is acceptable.
Who this is for
- You want an end-to-end example of prescriptive reasoning (optimization) with RelationalAI where the goal is feasibility.
- You’re comfortable with basic Python and the idea of decision variables and constraints.
What you’ll build
- A semantic model of workers, shifts, and worker–shift availability.
- A binary decision variable
Assignment.x_assignedfor each available worker–shift pair. - Constraints that enforce minimum shift coverage and per-worker assignment limits.
- A solve step that uses the
minizincbackend and prints an assignment table.
What’s included
- Model + solve script:
shift_assignment.py - Sample data:
data/workers.csv,data/shifts.csv,data/availability.csv - Outputs: solver termination status, an assignments table, and a per-shift coverage summary
Prerequisites
Access
- A Snowflake account that has the RAI Native App installed.
- A Snowflake user with permissions to access the RAI Native App.
Tools
- Python >= 3.10
- RelationalAI CLI (
rai) for setting up your profile
Quickstart
Follow these steps to run the template with the included sample data.
-
Download the ZIP file for this template and extract it:
Terminal window curl -O https://private.relational.ai/templates/zips/v0.14/shift_assignment.zipunzip shift_assignment.zipcd shift_assignment -
Create and activate a virtual environment
Terminal window python -m venv .venvsource .venv/bin/activatepython -m pip install -U pip -
Install dependencies
From this folder:
Terminal window python -m pip install . -
Configure Snowflake connection and RAI profile
Terminal window rai init -
Run the template
Terminal window python shift_assignment.py -
Expected output
Your exact assignments may vary between runs, but you should see a feasible status plus an assignments table and coverage summary:
Status: OPTIMALAssignments:worker shiftAlice MorningBob NightDiana AfternoonCoverage per shift:shift workersAfternoon 2Morning 2Night 2
Template structure
.├─ README.md├─ pyproject.toml├─ shift_assignment.py # main runner / entrypoint└─ data/ # sample input data ├─ workers.csv ├─ shifts.csv └─ availability.csvStart here: shift_assignment.py
Sample data
Data files are in data/.
workers.csv
| Column | Meaning |
|---|---|
id | Unique worker identifier |
name | Worker name (used for output labeling) |
shifts.csv
| Column | Meaning |
|---|---|
id | Unique shift identifier |
name | Shift name (e.g., Morning, Afternoon, Night) |
capacity | Sample data field (not enforced by this template as written) |
availability.csv
| Column | Meaning |
|---|---|
worker_id | Foreign key to workers.csv.id |
shift_id | Foreign key to shifts.csv.id |
Each row declares one allowed worker–shift pairing.
The script only creates Assignment entities for these rows, so unlisted pairings are impossible by construction.
Model overview
The semantic model for this template is built around three concepts.
Worker
A worker who may be assigned to at most a limited number of shifts.
| Property | Type | Identifying? | Notes |
|---|---|---|---|
id | int | Yes | Loaded as the key from data/workers.csv |
name | string | No | Used for output labeling |
Shift
A shift that must be staffed with at least min_coverage workers.
| Property | Type | Identifying? | Notes |
|---|---|---|---|
id | int | Yes | Loaded as the key from data/shifts.csv |
name | string | No | Used for output labeling |
Assignment (decision concept)
One entity per available worker–shift pair, created by joining availability.csv to Worker and Shift.
| Property | Type | Identifying? | Notes |
|---|---|---|---|
worker | Worker | Part of compound key | Joined via availability.csv.worker_id |
shift | Shift | Part of compound key | Joined via availability.csv.shift_id |
assigned | int | No | Binary decision variable (0/1) |
How it works
This section walks through the highlights in shift_assignment.py.
Import libraries and configure inputs
First, the script imports the Semantics APIs and configures local inputs like DATA_DIR:
from pathlib import Path
import pandasfrom pandas import read_csv
from relationalai.semantics import Model, Relationship, data, require, select, sum, wherefrom relationalai.semantics.reasoners.optimization import Solver, SolverModel
# --------------------------------------------------# Configure inputs# --------------------------------------------------
DATA_DIR = Path(__file__).parent / "data"
# Disable pandas inference of string types. This ensures that string columns# in the CSVs are loaded as object dtype. This is only required when using# relationalai versions prior to v1.0.pandas.options.future.infer_string = FalseDefine concepts and load CSV data
Next, it creates a Model, defines Worker and Shift concepts, and loads the corresponding CSVs using data(...).into(...):
# --------------------------------------------------# Define semantic model & load data# --------------------------------------------------
# Create a Semantics model container.model = Model("shift_assignment", config=globals().get("config", None))
# Worker concept: employees available for scheduling.Worker = model.Concept("Worker")Worker.id = model.Property("{Worker} has {id:int}")Worker.name = model.Property("{Worker} has {name:string}")
# Load worker data from CSV.data(read_csv(DATA_DIR / "workers.csv")).into(Worker, keys=["id"])
# Shift concept: time periods that require staffing.Shift = model.Concept("Shift")Shift.id = model.Property("{Shift} has {id:int}")Shift.name = model.Property("{Shift} has {name:string}")
# Load shift data from CSV.data(read_csv(DATA_DIR / "shifts.csv")).into(Shift, keys=["id"])Create decision entities from availability
Then it defines an Assignment decision concept and uses where(...).define(...) to create one Assignment entity for each available worker–shift pair:
# Assignment decision concept: a worker-shift pair that can potentially be staffed.# The availability table determines which pairs exist.Assignment = model.Concept("Assignment")Assignment.worker = model.Relationship("{Assignment} has {worker:Worker}")Assignment.shift = model.Relationship("{Assignment} has {shift:Shift}")Assignment.x_assigned = model.Property("{Assignment} assigned {assigned:int}")
# Load availability data from CSV.avail = data(read_csv(DATA_DIR / "availability.csv"))
# Define Assignment entities by joining availability rows to Worker and Shift.where( Worker.id == avail.worker_id, Shift.id == avail.shift_id).define( Assignment.new(worker=Worker, shift=Shift))Define decision variables and constraints
With the feasible worker–shift pairs defined, the template creates a SolverModel, declares a binary decision variable, and adds two constraints using require(...) and sum(...).per(...):
# --------------------------------------------------# Model the decision problem# --------------------------------------------------
# Parameters.min_coverage = 2max_shifts_per_worker = 1
AssignmentRef = Assignment.ref()
s = SolverModel(model, "int")
# Variable: binary assignment (0 or 1)s.solve_for( Assignment.x_assigned, name=["x", Assignment.worker.name, Assignment.shift.name], type="bin",)
# Constraint: each shift has minimum coverageshift_coverage = where( AssignmentRef.shift == Shift).require( sum(AssignmentRef.x_assigned).per(Shift) >= min_coverage)s.satisfy(shift_coverage)
# Constraint: each worker works at most max_shifts_per_worker shiftsworker_capacity = where( AssignmentRef.worker == Worker).require( sum(AssignmentRef.x_assigned).per(Worker) <= max_shifts_per_worker)s.satisfy(worker_capacity)Solve and print results
Finally, it solves the model using the minizinc backend and prints the assignments and coverage summary:
# --------------------------------------------------# Solve and check solution# --------------------------------------------------
solver = Solver("minizinc")s.solve(solver, time_limit_sec=60)
print(f"Status: {s.termination_status}")
assignments = select( Assignment.worker.name.alias("worker"), Assignment.shift.name.alias("shift")).where(Assignment.x_assigned >= 1).to_df()
print("\nAssignments:")print(assignments.to_string(index=False))
print("\nCoverage per shift:")print(assignments.groupby("shift").size().reset_index(name="workers").to_string(index=False))Customize this template
Use your own data
- Replace the CSVs in
data/with your own data. - Keep the required headers:
workers.csv:id,nameshifts.csv:id,nameavailability.csv:worker_id,shift_id
- Ensure
availability.csv.worker_idvalues exist inworkers.csv.idandavailability.csv.shift_idvalues exist inshifts.csv.id.
Tune parameters
Update the parameters in shift_assignment.py:
min_coverage: minimum workers per shiftmax_shifts_per_worker: maximum shifts per worker
If you increase min_coverage or reduce availability, the problem may become infeasible.
Extend the model
Common extensions include:
- Max coverage (capacity): If you want to enforce a maximum number of workers per shift, add a
Shift.capacityproperty, load it fromshifts.csv, and requiresum(AssignmentRef.x_assigned).per(Shift) <= Shift.capacity. - Preferences or costs: Add a weight per worker–shift pair and switch from pure feasibility to minimizing total cost.
Troubleshooting
I got ModuleNotFoundError when running the script
- Confirm your virtual environment is active.
- Reinstall dependencies from the template folder:
python -m pip install . - Confirm you’re using Python 3.10+.
I can’t authenticate / the script can’t connect to Snowflake
- Re-run
rai initand confirm your profile is set up. - If you use multiple profiles, set
RAI_PROFILEand retry. - Confirm your Snowflake user has access to the RelationalAI Native App.
I got a CSV error (missing file or missing columns)
- Confirm these files exist in
data/:workers.csv,shifts.csv,availability.csv. - Confirm required headers:
workers.csv:id,nameshifts.csv:id,nameavailability.csv:worker_id,shift_id- Make sure IDs are consistent across files (foreign keys actually match).
The solver returns Status: INFEASIBLE
- Lower
min_coverageor increase worker availability inavailability.csv. - Increase
max_shifts_per_workerif you have too few workers to cover all shifts. - Sanity-check that every shift appears in
availability.csvat leastmin_coveragetimes.
What this template is for
Workforce scheduling is a common operational challenge: given a set of workers, each with their own availability windows, you need to assign them to shifts so that every shift meets its minimum staffing requirements. Doing this manually becomes impractical as the number of workers, shifts, and constraints grows.
This template formulates the shift assignment problem as a constraint satisfaction model using RelationalAI’s prescriptive reasoning. Workers are assigned to shifts they are available for, subject to minimum coverage requirements per shift and a limit on how many shifts each worker can take. The solver (MiniZinc) finds feasible assignments that satisfy all constraints simultaneously.
The template also demonstrates scenario analysis by sweeping over different minimum coverage levels. This lets you quickly see which staffing targets are achievable with your current workforce and availability data, and where you might need to hire or adjust schedules.
Who this is for
- Operations managers building shift schedules for teams
- Analysts exploring feasibility of different staffing levels
- Developers learning constraint programming with RelationalAI
- Anyone new to prescriptive reasoning who wants a simple, intuitive example
What you’ll build
- A constraint model that assigns workers to shifts respecting availability and capacity limits
- Scenario analysis across multiple minimum-coverage levels (1, 2, 3 workers per shift)
- Post-solve verification via
problem.verify()to confirm constraint satisfaction across all scenarios
What’s included
shift_assignment.py— main script with ontology, constraints, and scenario analysisdata/workers.csv— 10 workers with IDs and namesdata/shifts.csv— 3 shifts (Morning, Afternoon, Night) with capacity limitsdata/availability.csv— worker-to-shift availability mappingspyproject.toml— Python package configuration
Prerequisites
Access
- A Snowflake account that has the RAI Native App installed.
- A Snowflake user with permissions to access the RAI Native App.
Tools
- Python >= 3.10
Quickstart
-
Download ZIP:
Terminal window curl -O https://docs.relational.ai/templates/zips/v1/shift_assignment.zipunzip shift_assignment.zipcd shift_assignment -
Create venv:
Terminal window python -m venv .venvsource .venv/bin/activatepython -m pip install --upgrade pip -
Install:
Terminal window python -m pip install . -
Configure:
Terminal window rai init -
Run:
Terminal window python shift_assignment.py -
Expected output:
Assignments per scenario:scenario worker shiftcoverage_1 Alice Morningcoverage_1 Bob Nightcoverage_1 Carlos Afternooncoverage_1 Diana Morningcoverage_1 Ethan Afternooncoverage_1 Frank Afternooncoverage_1 Grace Afternooncoverage_1 Henry Nightcoverage_1 Irene Nightcoverage_2 Alice Afternooncoverage_2 Bob Morningcoverage_2 Diana Afternooncoverage_2 Ethan Nightcoverage_2 Frank Afternooncoverage_2 Grace Nightcoverage_2 Henry Morningcoverage_2 Irene Nightcoverage_2 Jack Afternooncoverage_3 Alice Morningcoverage_3 Bob Morningcoverage_3 Carlos Afternooncoverage_3 Diana Nightcoverage_3 Ethan Nightcoverage_3 Frank Morningcoverage_3 Grace Afternooncoverage_3 Irene Nightcoverage_3 Jack AfternoonCoverage_1 (min_coverage=1) uses 9 workers with one per shift minimum. Coverage_2 (min_coverage=2) uses 9 workers with two per shift, dropping Carlos and adding Jack. Coverage_3 (min_coverage=3) requires 9 workers with three per shift, adding more Morning assignments.
Template structure
.├── README.md├── pyproject.toml├── shift_assignment.py└── data/ ├── workers.csv ├── shifts.csv └── availability.csvHow it works
1. Define the ontology and load data. Workers, shifts, and availability are modeled as concepts and relationships:
Worker = model.Concept("Worker", identify_by={"id": Integer})Worker.name = model.Property(f"{Worker} has {String:name}")
Shift = model.Concept("Shift", identify_by={"id": Integer})Shift.name = model.Property(f"{Shift} has {String:name}")Shift.capacity = model.Property(f"{Shift} has {Integer:capacity}")
Worker.available_for = model.Relationship(f"{Worker} is available for {Shift}")2. Define decision variables. A binary variable x_assign indicates whether a worker is assigned to a given shift:
Worker.x_assign = model.Property(f"{Worker} has {Shift} in {Scenario} if {Integer:assigned}")problem.solve_for( Worker.x_assign(Shift, Scenario, assigned_ref), type="bin", name=["x", Scenario.name, Worker.name, Shift.name], where=[Worker.available_for(Shift)],)3. Add constraints. Three constraints govern the assignment: minimum coverage, maximum shifts per worker, and shift capacity limits. Constraints are stored in named variables so they can be verified after solving:
coverage_ic = model.where( Worker.x_assign(Shift, Scenario, assigned_ref),).require(sum(Worker, assigned_ref).per(Shift, Scenario) >= Scenario.min_coverage)problem.satisfy(coverage_ic)
workload_ic = model.where( Worker.x_assign(Shift, Scenario, assigned_ref),).require(sum(Shift, assigned_ref).per(Worker, Scenario) <= max_shifts)problem.satisfy(workload_ic)
capacity_ic = model.where( Worker.x_assign(Shift, Scenario, assigned_ref),).require(sum(Worker, assigned_ref).per(Shift, Scenario) <= Shift.capacity)problem.satisfy(capacity_ic)4. Solve and verify. A single solve handles all scenarios simultaneously. After solving, problem.verify() fires the named constraints as integrity constraints to confirm the solution satisfies them:
problem.solve("minizinc", time_limit_sec=60)problem.solve_info().display()problem.verify(coverage_ic, workload_ic, capacity_ic)model.require(problem.termination_status() == "OPTIMAL")Customize this template
- Add more shifts or workers by editing the CSV files. The model scales automatically.
- Change the max shifts per worker by adjusting the
max_shiftsparameter. - Add shift preferences by introducing a preference score and converting from feasibility to optimization (minimize total dissatisfaction).
- Add skills or qualifications by introducing a skill-matching relationship between workers and shifts.
- Switch to optimization by adding an objective (e.g., maximize total coverage or minimize cost) with
problem.minimize()orproblem.maximize().
Troubleshooting
Solver returns INFEASIBLE
- With the single-solve approach, if any scenario’s constraints are unsatisfiable, the entire problem is infeasible.
- Verify that the
capacityinshifts.csvis at least as large as the highestmin_coveragescenario. If capacity < min_coverage for any shift, the problem is infeasible. - Check that
availability.csvhas enough worker-shift pairs to cover every shift at the highestmin_coveragelevel. - Ensure worker IDs and shift IDs in
availability.csvmatch those in the other CSV files.
Import error for relationalai
- Confirm your virtual environment is active:
which pythonshould point to.venv. - Reinstall dependencies:
python -m pip install ..
Authentication or configuration errors
- Run
rai initto create or update your RelationalAI/Snowflake configuration. - If you have multiple profiles, set
export RAI_PROFILE=<your_profile>.
MiniZinc solver not available
- This template uses the MiniZinc constraint solver. Ensure the RAI Native App version supports MiniZinc.
- As an alternative, you can try switching to
"highs"in theproblem.solve()call, though HiGHS is designed for linear/MIP problems.