///|
/// A complete, portable input document for preferred roster optimization.
pub(all) struct RosterRequest {
workers : Array[Worker]
shifts : Array[Shift]
policy : RosterPolicy
penalties : Array[AssignmentPenalty]
candidate_limit : Int
} derive(Eq, Debug, ToJson, FromJson)
///|
/// A portable optimization request with a composable soft objective.
///
/// This type extends the JSON integration without changing the established
/// `RosterRequest` schema used by existing clients.
pub(all) struct RosterOptimizationRequest {
workers : Array[Worker]
shifts : Array[Shift]
policy : RosterPolicy
objective : RosterObjective
candidate_limit : Int
} derive(Eq, Debug, ToJson, FromJson)
///|
/// Errors produced while decoding a roster request.
pub(all) enum RosterJsonError {
InvalidJson(String)
InvalidRosterSchema(String)
} derive(Eq, Debug)
///|
/// Explain a JSON decoding failure with its parser or field path detail.
pub fn RosterJsonError::message(self : RosterJsonError) -> String {
match self {
InvalidJson(detail) => "invalid JSON: \{detail}"
InvalidRosterSchema(detail) => "invalid roster schema: \{detail}"
}
}
///|
/// Decode a complete roster request from JSON text.
pub fn decode_roster_request(
text : String,
) -> Result[RosterRequest, RosterJsonError] {
let json = @json.parse(text) catch {
error => return Err(InvalidJson(error.to_string()))
}
let request : RosterRequest = @json.from_json(json) catch {
error => return Err(InvalidRosterSchema(error.to_string()))
}
Ok(request)
}
///|
/// Decode an objective-aware roster optimization request from JSON text.
pub fn decode_roster_optimization_request(
text : String,
) -> Result[RosterOptimizationRequest, RosterJsonError] {
let json = @json.parse(text) catch {
error => return Err(InvalidJson(error.to_string()))
}
let request : RosterOptimizationRequest = @json.from_json(json) catch {
error => return Err(InvalidRosterSchema(error.to_string()))
}
Ok(request)
}
///|
/// Solve a decoded request using its hard policy and soft penalties.
pub fn RosterRequest::solve(
self : RosterRequest,
) -> Result[PreferredRoster, RosterError] {
build_preferred_roster(
self.workers,
self.shifts,
self.policy,
self.penalties,
self.candidate_limit,
)
}
///|
/// Solve an objective-aware request using all hard and soft rules.
pub fn RosterOptimizationRequest::solve(
self : RosterOptimizationRequest,
) -> Result[PreferredRoster, RosterError] {
build_preferred_roster_with_objective(
self.workers,
self.shifts,
self.policy,
self.objective,
self.candidate_limit,
)
}
///|
/// Encode an optimized roster as stable JSON text.
pub fn encode_preferred_roster(
result : PreferredRoster,
indent : Int,
) -> String {
@json.to_json(result).stringify(indent~)
}