Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
name = "NurseScheduling"
name = "NurseSchedulingSolver"
uuid = "92cd2c7d-a66b-49fa-9b79-6db41388eebc"
authors = ["Paweł Renc <[email protected]> and contributors"]
version = "0.1.0"

[deps]
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
Genie = "c43c736e-a2d1-11e8-161f-af95117fbd1e"
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
LoggingExtras = "e6f89c97-d47a-5376-807f-9c37f3926c36"
Expand All @@ -14,3 +14,9 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
SuperEnum = "5c958174-e7d9-5990-9618-4567de4ba542"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test"]
47 changes: 0 additions & 47 deletions config/default/shifts.json

This file was deleted.

File renamed without changes.
35 changes: 0 additions & 35 deletions src/NurseScheduling.jl

This file was deleted.

8 changes: 8 additions & 0 deletions src/NurseSchedulingSolver.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
module NurseSchedulingSolver

include("model/Model.jl")

end
File renamed without changes.
38 changes: 38 additions & 0 deletions src/defaults/shifts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[
{
"code": "R",
"from": 7,
"to": 15,
"type": "WORKING"
},
{
"code": "P",
"from": 15,
"to": 19,
"type": "WORKING"
},
{
"code": "D",
"from": 7,
"to": 19,
"type": "WORKING"
},
{
"code": "N",
"from": 19,
"to": 7,
"type": "WORKING"
},
{
"code": "DN",
"from": 7,
"to": 7,
"type": "WORKING"
},
{
"code": "PN",
"from": 15,
"to": 7,
"type": "WORKING"
}
]
File renamed without changes.
File renamed without changes.
33 changes: 33 additions & 0 deletions src/model/Model.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
module Model

export Schedule
# Neighborhood,
# get_shift_options,
# get_penalties,
# get_shifts,
# get_max_nbhd_size,
# get_month_info,
# get_workers_info,
# update_shifts!,
# n_split_nbhd,
# perform_random_jumps!,
# get_shifts_distance,
# Shifts

include("schedule.jl")
# include("scoring.jl")
# include("neighborhood.jl")

using .schedule

# using .scoring
# export score

# using .neighborhood

# using .schedulevalidation

end
37 changes: 37 additions & 0 deletions src/model/constants.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
module constants

using JSON

const PROJECT_SRC = dirname(@__FILE__) |> dirname

const DEFAULT_SHIFTS = JSON.parsefile(joinpath(PROJECT_SRC, "defaults/shifts.json"))
const DEFAULT_CONFIG = JSON.parsefile(joinpath(PROJECT_SRC, "defaults/priorities.json"))

const REQ_CHLDN_PER_NRS_DAY = 3
const REQ_CHLDN_PER_NRS_NIGHT = 5

const LONG_BREAK_HOURS = 35

# under and overtime pen is equal to hours from <0, MAX_OVERTIME>
const MAX_OVERTIME = 10 # scaled by the number of weeks
const MAX_UNDERTIME = 0 # scaled by the number of weeks

const DAY_BEGIN = 6
const NIGHT_BEGIN = 22

const PERIOD_BEGIN = 7

# weekly worktime
const WORKTIME_BASE = 40

const DAY_HOURS_NO = 24
const WEEK_DAYS_NO = 7
const NUM_WORKING_DAYS = 5
const SUNDAY_NO = 0

const WORKTIME_DAILY = WORKTIME_BASE / NUM_WORKING_DAYS

end # constants
File renamed without changes.
166 changes: 166 additions & 0 deletions src/model/schedule.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
module schedule

export Schedule,
Shifts,
shifts,
base_shifts,
actual_shifts,
decode_shifts,
employee_uuid,
employee_shifts,
employee_base_shifts,
employee_actual_shifts

using JSON

include("constants.jl")
include("types.jl")

using .constants: DEFAULT_SHIFTS
using .types: BasicShift

Shifts = Matrix{UInt8}

struct Schedule
meta::Dict{String,Any}
shift_coding::Dict{String,UInt8}

function Schedule(schedule_json::Dict)
available_shifts = get(schedule_json, "available_shifts", DEFAULT_SHIFTS)
shift_coding = _make_shift_coding(available_shifts)

new(schedule_json, shift_coding)
end

Schedule(filepath::String) = Schedule(JSON.parsefile(filepath))
end

function _make_shift_coding(available_shifts::Vector)::Dict{String,UInt8}
shift_coding = Dict(
string(instance) => UInt8(instance) for
instance in instances(BasicShift.BasicShiftEnum)
)

for shift_info in available_shifts
if !haskey(shift_coding, shift_info["code"])
shift_coding[shift_info["code"]] = length(shift_coding)
end
end
return shift_coding
end

# schedule getters
function shifts(schedule::Schedule, shifts_key::String)::Shifts
shifts = [
map(code -> schedule.shift_coding[code], e[shifts_key]) for
e in schedule.meta["employees"]
]
shifts = transpose(hcat(shifts...))
return shifts
end

base_shifts(schedule::Schedule)::Shifts = shifts(schedule, "base_shifts")
actual_shifts(schedule::Schedule)::Shifts = shifts(schedule, "actual_shifts")

function decode_shifts(schedule::Schedule, shifts::Shifts)::Matrix{String}
decoding = Dict(v => k for (k, v) in pairs(schedule.shift_coding))
return map(coded_shift -> decoding[coded_shift], shifts)
end

employee_uuid(schedule::Schedule, idx::Int)::String =
schedule.meta["employees"][idx]["uuid"]

employee_shifts(schedule::Schedule, idx::Int, shifts_key::String)::Vector{String} =
schedule.meta["employees"][idx][shifts_key]

employee_actual_shifts(schedule::Schedule, idx::Int)::Vector{String} =
employee_shifts(schedule, idx, "actual_shifts")

employee_base_shifts(schedule::Schedule, idx::Int)::Vector{String} =
employee_shifts(schedule, idx, "base_shifts")

###########################################
# ☠☠☠ WATCH OUT! ☠☠☠ #
# everything below is possibly deprecated #
###########################################

function raw_options(schedule::Schedule)
return "shift_types" in keys(schedule.data) ? schedule.data["shift_types"] : SHIFTS
end

function shift_options(schedule::Schedule)
base = raw_options(schedule)
return Dict(schedule.shift_map[k] => v for (k, v) in base)
end

function penalties(schedule::Schedule)::Dict{String,Any}
weights = CONFIG["weight_map"]
default_priority = CONFIG["penalties"]
custom_priority = get(schedule.data, "penalty_priorities", nothing)
penalties = Dict{String,Any}()

if isnothing(custom_priority)
return Dict(key => pen for (key, pen) in zip(default_priority, weights))
end

for key in default_priority
if key in custom_priority
penalties[key] = weights[findall(x -> x == key, custom_priority)[1]]
else
penalties[key] = 0
end
end

return penalties
end

function daytime_range(schedule::Schedule)
daytime_begin = get(schedule.meta["settings"], "daytime_begin", DAY_BEGIN)
daytime_end = get(schedule.meta["settings"], "night_begin", NIGHT_BEGIN)
return (daytime_begin, daytime_end)
end

function changeable_shifts(schedule::Schedule)
filter(kv -> kv.second["is_working_shift"], shift_options(schedule))
end

function exempted_shifts(schedule::Schedule)
filter(
kv -> !kv.second["is_working_shift"] && kv.first != W_ID,
get_shift_options(schedule),
)
end

function disallowed_sequences(schedule::Schedule)
Dict(
first_shift_key => [
second_shift_key for
(second_shift_key, second_shift_val) in get_changeable_shifts(schedule) if
get_next_day_distance(first_shift_val, second_shift_val) <=
get_rest_length(first_shift_val)
] for (first_shift_key, first_shift_val) in changeable_shifts(schedule)
)
end

function earliest_shift_begin(schedule::Schedule)
changeable_shifts = collect(values(changeable_shifts(schedule)))
minimum(x -> x["from"], changeable_shifts)
end

function latest_shift_end(schedule::Schedule)
changeable_shifts = collect(values(changeable_shifts(schedule)))
maximum(x -> x["to"] > x["from"] ? x["to"] : 24 + x["to"], changeable_shifts)
end

function get_period_range()::Vector{Int}
vcat(collect(PERIOD_BEGIN:24), collect(1:(PERIOD_BEGIN - 1)))
end

function get_shifts_distance(shifts_1::Shifts, shifts_2::Shifts)::Int
return count(s -> s[1] != s[2], zip(shifts_1, shifts_2))
end

end # schedule
Loading