///|
fn validation_error(code : String, message : String) -> Diagnostic {
Diagnostic::new(code, Error, message)
}
///|
fn has_duplicate_worker_ids(workers : Array[Worker]) -> Array[Diagnostic] {
let diagnostics : Array[Diagnostic] = []
for i = 0; i < workers.length(); i = i + 1 {
for j = i + 1; j < workers.length(); j = j + 1 {
if workers[i].id == workers[j].id {
diagnostics.push(
validation_error(
"duplicate_worker",
"worker id appears more than once: " + workers[i].id,
).for_worker(workers[i].id),
)
}
}
}
diagnostics
}
///|
fn has_duplicate_shift_ids(shifts : Array[Shift]) -> Array[Diagnostic] {
let diagnostics : Array[Diagnostic] = []
for i = 0; i < shifts.length(); i = i + 1 {
for j = i + 1; j < shifts.length(); j = j + 1 {
if shifts[i].id == shifts[j].id {
diagnostics.push(
validation_error(
"duplicate_shift",
"shift id appears more than once: " + shifts[i].id,
).for_shift(shifts[i].id),
)
}
}
}
diagnostics
}
///|
fn validate_workers(problem : PlanningProblem) -> Array[Diagnostic] {
let diagnostics = has_duplicate_worker_ids(problem.workers)
for worker in problem.workers {
if worker.id.trim().length() == 0 {
diagnostics.push(
validation_error("empty_worker_id", "worker id cannot be empty"),
)
}
if worker.name.trim().length() == 0 {
diagnostics.push(
validation_error("empty_worker_name", "worker name cannot be empty").for_worker(
worker.id,
),
)
}
if worker.min_minutes < 0 ||
worker.target_minutes < 0 ||
worker.max_minutes < 0 {
diagnostics.push(
validation_error(
"negative_minutes", "worker minute limits cannot be negative",
).for_worker(worker.id),
)
}
if worker.min_minutes > worker.target_minutes ||
worker.target_minutes > worker.max_minutes {
diagnostics.push(
validation_error(
"invalid_minute_limits", "expected min_minutes <= target_minutes <= max_minutes",
).for_worker(worker.id),
)
}
if worker.max_consecutive_shifts <= 0 {
diagnostics.push(
validation_error(
"invalid_consecutive_limit", "max_consecutive_shifts must be positive",
).for_worker(worker.id),
)
}
if worker.min_rest_minutes < 0 {
diagnostics.push(
validation_error("negative_rest", "min_rest_minutes cannot be negative").for_worker(
worker.id,
),
)
}
for range in worker.availability {
if not(range.is_valid()) {
diagnostics.push(
validation_error(
"invalid_availability", "availability range is invalid",
).for_worker(worker.id),
)
}
}
for skill in worker.skills {
let mut found = false
for known in problem.skills {
if known.id == skill.skill_id {
found = true
}
}
if not(found) {
diagnostics.push(
validation_error(
"unknown_worker_skill",
"worker references unknown skill: " + skill.skill_id,
).for_worker(worker.id),
)
}
}
}
diagnostics
}
///|
fn validate_shifts(problem : PlanningProblem) -> Array[Diagnostic] {
let diagnostics = has_duplicate_shift_ids(problem.shifts)
for shift in problem.shifts {
if shift.id.trim().length() == 0 {
diagnostics.push(
validation_error("empty_shift_id", "shift id cannot be empty"),
)
}
if not(shift.range.is_valid()) {
diagnostics.push(
validation_error("invalid_shift_range", "shift time range is invalid").for_shift(
shift.id,
),
)
}
if shift.required_workers < 0 {
diagnostics.push(
validation_error(
"negative_required_workers", "required_workers cannot be negative",
).for_shift(shift.id),
)
}
if shift.preferred_workers < shift.required_workers {
diagnostics.push(
validation_error(
"preferred_below_required", "preferred_workers cannot be lower than required_workers",
).for_shift(shift.id),
)
}
for requirement in shift.requirements {
if requirement.count <= 0 {
diagnostics.push(
validation_error(
"invalid_skill_count", "skill requirement count must be positive",
).for_shift(shift.id),
)
}
let mut found = false
for skill in problem.skills {
if skill.id == requirement.skill_id {
found = true
}
}
if not(found) {
diagnostics.push(
validation_error(
"unknown_required_skill",
"shift references unknown skill: " + requirement.skill_id,
).for_shift(shift.id),
)
}
}
}
diagnostics
}
///|
fn validate_locked_assignments(problem : PlanningProblem) -> Array[Diagnostic] {
let diagnostics : Array[Diagnostic] = []
for assignment in problem.locked_assignments {
if problem.worker(assignment.worker_id) is None {
diagnostics.push(
validation_error(
"unknown_locked_worker",
"locked assignment references unknown worker: " + assignment.worker_id,
),
)
}
if problem.shift(assignment.shift_id) is None {
diagnostics.push(
validation_error(
"unknown_locked_shift",
"locked assignment references unknown shift: " + assignment.shift_id,
),
)
}
}
diagnostics
}
///|
pub fn PlanningProblem::validate(self : PlanningProblem) -> Array[Diagnostic] {
let diagnostics : Array[Diagnostic] = []
if self.name.trim().length() == 0 {
diagnostics.push(
validation_error("empty_problem_name", "problem name cannot be empty"),
)
}
if self.workers.length() == 0 {
diagnostics.push(
validation_error("no_workers", "at least one worker is required"),
)
}
if self.shifts.length() == 0 {
diagnostics.push(
validation_error("no_shifts", "at least one shift is required"),
)
}
diagnostics.append(validate_workers(self))
diagnostics.append(validate_shifts(self))
diagnostics.append(validate_locked_assignments(self))
diagnostics
}
///|
pub fn diagnostics_have_errors(diagnostics : Array[Diagnostic]) -> Bool {
for diagnostic in diagnostics {
if diagnostic.severity == Error {
return true
}
}
false
}