Explainable fair workforce rostering and resilience analysis in MoonBit
git clone https://github.com/zhou506403/zhou.git
cd zhou
moon check
moon test
moon run cmd/mainlet problem = @fairshift.volunteer_scenario()
let config = @fairshift.SolverConfig::default()
.with_iterations(400)
.with_seed(202608)
let solution = @fairshift.solve_with_config(problem, config)
println(@fairshift.render_text_report(problem, solution))id,name,min_minutes,target_minutes,max_minutes,max_consecutive,min_rest,skills
ada,Ada,0,960,1440,5,600,first-aid:4|lead:3
grace,Grace,0,960,1440,5,600,first-aid:2id,label,start,end,required,preferred,kind,weekend,requirements
day-1,Day 1,480,960,1,1,day,false,first-aid:2:1
day-2,Day 2,1920,2400,1,1,day,false,moon fmt --check
moon check
moon test
moon run cmd/main
moon infopub struct AuditEvent {
sequence : Int
kind : AuditEventKind
message : String
worker_id : String?
shift_id : String?
score_before : Int?
score_after : Int?
} derive(Eq, Show, ToJson)fn AuditEvent::for_assignment(self : AuditEvent, worker_id : String, shift_id : String) -> AuditEventfn AuditTrail::record_assignment(self : AuditTrail, kind : AuditEventKind, message : String, worker_id : String, shift_id : String) -> AuditTrailpub struct CapacityReport {
shifts : Array[ShiftCapacity]
skills : Array[SkillCapacity]
critical_shifts : Int
critical_skills : Int
total_required_slots : Int
total_eligible_instances : Int
resilience_score : Int
} derive(Eq, Show, ToJson)pub struct ConstraintReport {
violations : Array[ConstraintViolation]
hard_count : Int
soft_count : Int
} derive(Eq, Show, ToJson)fn ConstraintReport::add(self : ConstraintReport, violation : ConstraintViolation) -> ConstraintReportpub struct ConstraintViolation {
kind : ViolationKind
message : String
worker_id : String?
shift_id : String?
hard : Bool
magnitude : Int
} derive(Eq, Show, ToJson)fn ConstraintViolation::for_shift(self : ConstraintViolation, shift_id : String) -> ConstraintViolationfn ConstraintViolation::for_worker(self : ConstraintViolation, worker_id : String) -> ConstraintViolationfn ConstraintViolation::new(kind : ViolationKind, message : String, hard : Bool) -> ConstraintViolationfn ConstraintViolation::with_magnitude(self : ConstraintViolation, magnitude : Int) -> ConstraintViolationpub struct Diagnostic {
code : String
severity : DiagnosticSeverity
message : String
worker_id : String?
shift_id : String?
} derive(Eq, Show, ToJson)pub struct Explanation {
kind : ExplanationKind
title : String
detail : String
worker_id : String?
shift_id : String?
priority : Int
} derive(Eq, Show, ToJson)fn Explanation::new(kind : ExplanationKind, title : String, detail : String, priority : Int) -> Explanationfn PlanningProblem::lock(self : PlanningProblem, worker_id : String, shift_id : String) -> PlanningProblempub struct ScheduleSummary {
problem_name : String
worker_count : Int
shift_count : Int
assignment_count : Int
coverage_per_mille : Int
workload_spread : Int
night_spread : Int
weekend_spread : Int
feasible : Bool
score : ScoreBreakdown
workers : Array[WorkerSummary]
shifts : Array[ShiftSummary]
} derive(Eq, Show, ToJson)pub struct ShiftPreference {
shift_id : String
kind : PreferenceKind
weight : Int
} derive(Eq, Show, ToJson)pub struct SkillRequirement {
skill_id : String
minimum_level : SkillLevel
count : Int
} derive(Eq, Show, ToJson)fn SkillRequirement::new(skill_id : String, minimum_level : SkillLevel, count : Int) -> SkillRequirementpub struct Solution {
schedule : Schedule
score : ScoreBreakdown
diagnostics : Array[Diagnostic]
feasible : Bool
stats : SolveStats
} derive(Eq, Show, ToJson)pub struct SolutionComparison {
left_score : Int
right_score : Int
score_delta : Int
left_coverage : Int
right_coverage : Int
coverage_delta : Int
left_workload_spread : Int
right_workload_spread : Int
workload_spread_delta : Int
changed_assignments : Int
recommendation : String
} derive(Eq, Show, ToJson)pub struct Worker {
id : String
name : String
skills : Array[WorkerSkill]
availability : Array[TimeRange]
preferences : Array[ShiftPreference]
min_minutes : Int
target_minutes : Int
max_minutes : Int
max_consecutive_shifts : Int
min_rest_minutes : Int
} derive(Eq, Show, ToJson)fn analyze_absence(problem : PlanningProblem, worker_id : String, config : SolverConfig) -> AbsenceImpact?fn assignment_delta(problem : PlanningProblem, schedule : Schedule, worker_id : String, shift_id : String) -> Intfn capacity_recommendations(problem : PlanningProblem, capacity : CapacityReport, sensitivity : SensitivityReport) -> Array[CapacityRecommendation]fn check_candidate(problem : PlanningProblem, schedule : Schedule, worker_id : String, shift_id : String) -> ConstraintReportfn compare_solutions(problem : PlanningProblem, left : Solution, right : Solution) -> SolutionComparisonfn eligible_workers(problem : PlanningProblem, schedule : Schedule, shift_id : String) -> Array[String]fn explain_candidate(problem : PlanningProblem, schedule : Schedule, worker_id : String, shift_id : String) -> Array[Explanation]fn parse_problem_csv(name : String, workers_csv : String, shifts_csv : String) -> ProblemParseResultfn repair_schedule(problem : PlanningProblem, existing : Schedule, config : SolverConfig) -> Solutionfn summarize_shift(problem : PlanningProblem, schedule : Schedule, shift_id : String) -> ShiftSummary?fn summarize_worker(problem : PlanningProblem, schedule : Schedule, worker_id : String) -> WorkerSummary?fn worker_assigned_minutes(problem : PlanningProblem, schedule : Schedule, worker_id : String) -> IntInstall
Download zipExplainable fair workforce rostering and resilience analysis in MoonBit