Spaces:
Sleeping
Sleeping
| 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. | |
| 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 | |
| } | |