Spaces:
Sleeping
Sleeping
File size: 5,141 Bytes
7596726 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | use chrono::{Duration, NaiveDate};
use rand::rngs::StdRng;
use std::collections::BTreeSet;
use crate::domain::{CareHub, Employee};
use super::time_utils::generate_name_permutations;
use super::vocabulary::*;
/// Draft workforce record used before we instantiate full `Employee` facts.
#[derive(Clone)]
pub(super) struct EmployeeBlueprint {
pub(super) name: String,
pub(super) skills: BTreeSet<String>,
pub(super) home_hub: CareHub,
pub(super) primary_off_weekday: usize,
}
/// Builds the fixed workforce composition for the public demo dataset.
pub(super) fn build_employee_blueprints(rng: &mut StdRng) -> Vec<EmployeeBlueprint> {
let names = generate_name_permutations(rng);
let mut skill_sets: Vec<Vec<&'static str>> = Vec::with_capacity(EMPLOYEE_COUNT);
// The generator used to hand almost every day shift to a generic Doctor or
// Nurse pool. That made most legal assignments interchangeable and flattened
// local search almost immediately. The redesign keeps the same workforce
// size, but assigns each employee to one or two service lines so every
// shift has a smaller, more meaningful candidate set.
//
// We still retain the base DOCTOR/NURSE tags so the witness builder can
// reason about role families, but public shifts now require service-line
// skills such as `Critical care doctor` or `Outpatient nurse`.
push_skill_sets(&mut skill_sets, 4, &[DOCTOR, CRITICAL_DOCTOR]);
push_skill_sets(
&mut skill_sets,
2,
&[DOCTOR, CRITICAL_DOCTOR, OUTPATIENT_DOCTOR],
);
push_skill_sets(&mut skill_sets, 4, &[DOCTOR, NEUROLOGY_DOCTOR, CARDIOLOGY]);
push_skill_sets(
&mut skill_sets,
3,
&[DOCTOR, AMBULATORY_DOCTOR, PEDIATRIC_DOCTOR],
);
push_skill_sets(&mut skill_sets, 4, &[DOCTOR, SURGERY_DOCTOR, ANAESTHETICS]);
push_skill_sets(
&mut skill_sets,
1,
&[DOCTOR, OUTPATIENT_DOCTOR, AMBULATORY_DOCTOR],
);
push_skill_sets(
&mut skill_sets,
4,
&[DOCTOR, RADIOLOGY_CALL, OUTPATIENT_DOCTOR],
);
push_skill_sets(&mut skill_sets, 5, &[NURSE, CRITICAL_NURSE]);
push_skill_sets(
&mut skill_sets,
3,
&[NURSE, CRITICAL_NURSE, OUTPATIENT_NURSE],
);
push_skill_sets(
&mut skill_sets,
4,
&[NURSE, AMBULATORY_NURSE, PEDIATRIC_NURSE],
);
push_skill_sets(
&mut skill_sets,
4,
&[NURSE, NEUROLOGY_NURSE, PEDIATRIC_NURSE],
);
push_skill_sets(
&mut skill_sets,
4,
&[NURSE, SURGERY_NURSE, OUTPATIENT_NURSE],
);
push_skill_sets(&mut skill_sets, 4, &[NURSE, RADIOLOGY_DAY, RADIOLOGY_NURSE]);
push_skill_sets(
&mut skill_sets,
2,
&[NURSE, RADIOLOGY_DAY, RADIOLOGY_NURSE, ANAESTHETICS],
);
push_skill_sets(
&mut skill_sets,
2,
&[NURSE, AMBULATORY_NURSE, OUTPATIENT_NURSE],
);
assert_eq!(
skill_sets.len(),
EMPLOYEE_COUNT,
"employee blueprint count should match workforce target"
);
skill_sets
.into_iter()
.enumerate()
.map(|(index, skills)| {
let home_hub = CareHub::infer_from_skills(skills.iter().copied());
EmployeeBlueprint {
name: names[index].clone(),
skills: skills.into_iter().map(str::to_string).collect(),
home_hub,
primary_off_weekday: 0,
}
})
.collect()
}
/// Appends `count` identical skill bundles to the blueprint list.
fn push_skill_sets(target: &mut Vec<Vec<&'static str>>, count: usize, skills: &[&'static str]) {
for _ in 0..count {
target.push(skills.to_vec());
}
}
impl EmployeeBlueprint {
/// Tiny convenience helper used by balancing heuristics.
pub(super) fn has_skill(&self, skill: &'static str) -> bool {
self.skills.contains(skill)
}
/// Counts the specialties that are intentionally scarce in this dataset.
pub(super) fn specialty_count(&self) -> usize {
usize::from(self.skills.contains(CARDIOLOGY))
+ usize::from(self.skills.contains(ANAESTHETICS))
+ usize::from(self.skills.contains(RADIOLOGY_CALL))
+ usize::from(self.skills.contains(RADIOLOGY_DAY))
}
}
/// Turns the blueprints into the actual `Employee` facts published by the app.
pub(super) fn instantiate_employees(
blueprints: &[EmployeeBlueprint],
start_date: NaiveDate,
) -> Vec<Employee> {
let mut employees = Vec::with_capacity(blueprints.len());
for (index, blueprint) in blueprints.iter().enumerate() {
let mut employee = Employee::new(index, blueprint.name.clone())
.with_home_hub(blueprint.home_hub)
.with_skills(blueprint.skills.iter().map(|skill| skill.as_str()));
for week in 0..(DAYS_IN_SCHEDULE / 7) {
let date = start_date + Duration::days(week * 7 + blueprint.primary_off_weekday as i64);
employee.unavailable_dates.insert(date);
}
employees.push(employee);
}
employees
}
|