Skip to content

Commit

Permalink
feat: bed allocation improvement (#366)
Browse files Browse the repository at this point in the history
  • Loading branch information
zepfred authored Mar 27, 2024
1 parent 1eafbcd commit 7dba481
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,21 @@
import ai.timefold.solver.core.api.score.buildin.hardmediumsoft.HardMediumSoftScore;
import ai.timefold.solver.core.api.solver.SolverStatus;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;

@PlanningSolution
public class BedPlan {

@ProblemFactCollectionProperty
private List<Department> departments;
@ProblemFactCollectionProperty
private List<DepartmentSpecialty> departmentSpecialties;
@ProblemFactCollectionProperty
private List<Room> rooms;
@ProblemFactCollectionProperty
@ValueRangeProvider
private List<Bed> beds;
@PlanningEntityCollectionProperty
private List<Stay> stays;
@JsonIgnore
private List<Room> rooms;
@JsonIgnore
private List<Bed> beds;

@PlanningScore
private HardMediumSoftScore score;
Expand All @@ -34,6 +35,21 @@ public class BedPlan {
public BedPlan() {
}

@JsonCreator
public BedPlan(@JsonProperty("departments") List<Department> departments, @JsonProperty("stays") List<Stay> stays) {
this.departments = departments;
this.stays = stays;
this.rooms = departments.stream()
.filter(d -> d.getRooms() != null)
.flatMap(d -> d.getRooms().stream())
.toList();
this.beds = departments.stream()
.filter(d -> d.getRooms() != null)
.flatMap(d -> d.getRooms().stream())
.flatMap(r -> r.getBeds().stream())
.toList();
}

public BedPlan(HardMediumSoftScore score, SolverStatus solverStatus) {
this.score = score;
this.solverStatus = solverStatus;
Expand All @@ -50,14 +66,7 @@ public void setDepartments(List<Department> departments) {
this.departments = departments;
}

public void setDepartmentSpecialties(List<DepartmentSpecialty> departmentSpecialties) {
this.departmentSpecialties = departmentSpecialties;
}

public List<DepartmentSpecialty> getDepartmentSpecialties() {
return departmentSpecialties;
}

@ProblemFactCollectionProperty
public List<Room> getRooms() {
return rooms;
}
Expand All @@ -66,6 +75,8 @@ public void setRooms(List<Room> rooms) {
this.rooms = rooms;
}

@ProblemFactCollectionProperty
@ValueRangeProvider
public List<Bed> getBeds() {
return beds;
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ public int calculateSameNightCount(Stay other) {
return Math.max(0, (int) DAYS.between(maxArrivalDate, minDepartureDate) + 1); // TODO is + 1 still desired?
}

@JsonIgnore
public boolean hasDepartmentSpecialty() {
return getDepartment().getSpecialtyToPriority().containsKey(specialty);
}

@JsonIgnore
public int getSpecialtyPriority() {
return getDepartment().getSpecialtyToPriority().get(specialty);
}

@JsonIgnore
public Room getRoom() {
if (bed == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import org.acme.bedallocation.domain.Bed;
import org.acme.bedallocation.domain.BedPlan;
import org.acme.bedallocation.domain.Department;
import org.acme.bedallocation.domain.DepartmentSpecialty;
import org.acme.bedallocation.domain.Room;
import org.acme.bedallocation.domain.Stay;

Expand All @@ -48,21 +47,15 @@ public BedPlan generateDemoData() {
schedule.getDepartments().get(0).getSpecialtyToPriority().put(SPECIALTIES.get(0), 1);
schedule.getDepartments().get(0).getSpecialtyToPriority().put(SPECIALTIES.get(1), 2);
schedule.getDepartments().get(0).getSpecialtyToPriority().put(SPECIALTIES.get(2), 2);
schedule.setDepartmentSpecialties(departments.stream()
.flatMap(d -> d.getSpecialtyToPriority().entrySet().stream()
.map(e -> new DepartmentSpecialty("%s-%s".formatted(d.getId(), e.getKey()), d, e.getKey(),
e.getValue()))
.toList()
.stream())
.toList());

// Rooms
int countRooms = 10;
schedule.getDepartments().get(0).setRooms(generateRooms(countRooms, departments));
schedule.setRooms(departments.stream().flatMap(d -> d.getRooms().stream()).toList());
List<Room> rooms = generateRooms(countRooms, departments);
schedule.getDepartments().get(0).setRooms(rooms);
schedule.setRooms(rooms);
// Beds
generateBeds(schedule.getRooms());
generateBeds(rooms);
schedule.setBeds(departments.stream()
.filter(d -> d.getRooms() != null)
.flatMap(d -> d.getRooms().stream())
.flatMap(r -> r.getBeds().stream())
.toList());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import ai.timefold.solver.core.api.score.stream.ConstraintProvider;

import org.acme.bedallocation.domain.Department;
import org.acme.bedallocation.domain.DepartmentSpecialty;
import org.acme.bedallocation.domain.Gender;
import org.acme.bedallocation.domain.GenderLimitation;
import org.acme.bedallocation.domain.Stay;
Expand Down Expand Up @@ -130,22 +129,15 @@ public Constraint preferredMaximumRoomCapacity(ConstraintFactory constraintFacto

public Constraint departmentSpecialty(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(Stay.class)
.ifNotExists(DepartmentSpecialty.class,
equal(Stay::getDepartment, DepartmentSpecialty::getDepartment),
equal(Stay::getSpecialty, DepartmentSpecialty::getSpecialty))
.filter(st -> !st.hasDepartmentSpecialty())
.penalize(HardMediumSoftScore.ofSoft(10), Stay::getNightCount)
.asConstraint("departmentSpecialty");
}

public Constraint departmentSpecialtyNotFirstPriority(ConstraintFactory constraintFactory) {
return constraintFactory.forEach(Stay.class)
.filter(st -> st.getSpecialty() != null)
.join(constraintFactory.forEach(DepartmentSpecialty.class)
.filter(ds -> ds.getPriority() > 1),
equal(Stay::getDepartment, DepartmentSpecialty::getDepartment),
equal(Stay::getSpecialty, DepartmentSpecialty::getSpecialty))
.penalize(HardMediumSoftScore.ofSoft(10),
(bd, rs) -> (rs.getPriority() - 1) * bd.getNightCount())
.filter(st -> st.getSpecialtyPriority() > 1)
.penalize(HardMediumSoftScore.ofSoft(10), stay -> (stay.getSpecialtyPriority() - 1) * stay.getNightCount())
.asConstraint("departmentSpecialtyNotFirstPriority");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,9 @@ function renderScheduleByRoom(schedule) {
unassignedPatientElement.append($("<div />").prop("class", "d-flex justify-content-end").append($(`<small class="ms-2 mt-1 card-text text-muted"/>`)
.text(stay.patientPreferredMaximumRoomCapacity)));

unassignedPatients.append($(`<div class="col"/>`).append($(`<div class="card"/>`).append(unassignedPatientElement)));
const color = stay.patientGender == 'MALE' ? '#729FCF' : '#FCE94F';
unassignedPatients.append($(`<div class="col"/>`).append($(`<div class="card" style="background-color: ${color}"/>`).append(unassignedPatientElement)));
console.log(stay)
byRoomItemData.add({
id: stay.id,
group: stay.id,
Expand Down Expand Up @@ -208,13 +210,15 @@ function renderScheduleByRoom(schedule) {
}
byPatientElement.append($("<div />").prop("class", "d-flex justify-content-end").append($(`<small class="ms-2 mt-1 card-text text-muted"/>`)
.text(stay.patientPreferredMaximumRoomCapacity)));
const color = stay.patientGender == 'MALE' ? '#729FCF' : '#FCE94F';

byRoomItemData.add({
id: stay.id,
group: stay.bed,
content: byPatientElement.html(),
start: stay.arrivalDate,
end: stay.departureDate
end: stay.departureDate,
style: `background-color: ${color}`
});
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import org.acme.bedallocation.domain.Bed;
import org.acme.bedallocation.domain.BedPlan;
import org.acme.bedallocation.domain.Department;
import org.acme.bedallocation.domain.DepartmentSpecialty;
import org.acme.bedallocation.domain.Gender;
import org.acme.bedallocation.domain.GenderLimitation;
import org.acme.bedallocation.domain.Room;
Expand Down Expand Up @@ -171,7 +170,6 @@ void assignEveryPatientToABed() {
@Test
void preferredMaximumRoomCapacity() {


Room room = new Room();
room.setCapacity(6);

Expand Down Expand Up @@ -205,9 +203,10 @@ void preferredPatientEquipment() {
}

@Test
void departmentSpecialtY() {
void departmentSpecialty() {

Department department = new Department("0", "0");
department.setSpecialtyToPriority(Map.of("spec1", 1));

Room roomInDep = new Room();
roomInDep.setDepartment(department);
Expand All @@ -225,20 +224,16 @@ void departmentSpecialtY() {

Stay staySpec2 = new Stay("1", ZERO_NIGHT, FIVE_NIGHT, spec2, bedInRoomInDep);

DepartmentSpecialty departmentSpecialtyWithOneSpec = new DepartmentSpecialty();
departmentSpecialtyWithOneSpec.setDepartment(department);
departmentSpecialtyWithOneSpec.setSpecialty(spec1);

constraintVerifier.verifyThat(BedAllocationConstraintProvider::departmentSpecialty)
.given(staySpec1, staySpec2, departmentSpecialtyWithOneSpec)
.given(staySpec1, staySpec2)
.penalizesBy(6);
}

@Test
void departmentSpecialtYNotFirstPriorityConstraint() {
void departmentSpecialtyNotFirstPriorityConstraint() {

Department department = new Department("0", "0");
department.setSpecialtyToPriority(Map.of("spec1", 2));
department.setSpecialtyToPriority(Map.of("spec1", 2, "spec2", 1));

Room roomInDep = new Room("1");
roomInDep.setDepartment(department);
Expand All @@ -255,13 +250,8 @@ void departmentSpecialtYNotFirstPriorityConstraint() {
String spec2 = "spec2";
Stay stay2 = new Stay("1", ZERO_NIGHT, FIVE_NIGHT, spec2, bedInDep);

DepartmentSpecialty departmentSpecialty = new DepartmentSpecialty();
departmentSpecialty.setDepartment(department);
departmentSpecialty.setSpecialty(spec1);
departmentSpecialty.setPriority(2);

constraintVerifier.verifyThat(BedAllocationConstraintProvider::departmentSpecialtyNotFirstPriority)
.given(stay1, stay2, departmentSpecialty)
.given(stay1, stay2)
.penalizesBy(6);
}

Expand Down

0 comments on commit 7dba481

Please sign in to comment.