fix shift generation for day, ranking, family, etc...

This commit is contained in:
Dobromir Popov
2024-09-26 02:18:45 +03:00
parent bab62816b0
commit 1ca2b51e4b
3 changed files with 96 additions and 62 deletions

View File

@ -143,7 +143,7 @@ function flattenRegistry(dayKey) {
return Object.values(weekEntries).flat();
}
async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, autoFill = false, forDay, algType = 0) {
async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, autoFill = false, forDay, algType = 0, until) {
let missingPublishers = [];
let publishersWithChangedPref = [];
@ -176,7 +176,14 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
if (forDay) {
day = monthInfo.date;
endDate.setDate(monthInfo.date.getDate() + 1);
if (until === undefined) {
const oneDayInMs = 24 * 60 * 60 * 1000;
endDate = new Date(monthInfo.date.getTime() + oneDayInMs);
}
else {
endDate = new Date(until);
}
dayNr = monthInfo.date.getDate();
weekNr = common.getWeekNumber(monthInfo.date);
}
@ -184,7 +191,7 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
let publishersThisWeek = [];
// # # # # # # # # # # #
// 0. generate shifts and assign publishers from the previous month if still available
while (day < endDate) {
let availablePubsForTheDay = await data.filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type', day, false, false, false, true, false);
@ -262,7 +269,7 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
//ToDo: check if getAvailablePublishersForShift is working correctly. It seems not to!
let availablePublishers = await getAvailablePublishersForShiftNew(shiftStart, shiftEnd, availablePubsForTheDay, publishersThisWeek);
console.log("shift " + __shiftName + " needs " + publishersNeeded + " publishers, available: " + availablePublishers.length + " for the day: " + availablePubsForTheDay.length);
console.log("shift " + __shiftName + " needs " + publishersNeeded + " publishers, available: " + availablePublishers.length + ", for the day: " + availablePubsForTheDay.length);
const createdShift = await prisma.shift.create({
data: {
@ -294,21 +301,31 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
shiftEnd.setMinutes(shiftStart.getMinutes() + event.shiftDuration);
}
day.setDate(day.getDate() + 1);
dayNr++;
if (common.DaysOfWeekArray[day.getDayEuropean()] === DayOfWeek.Sunday) {
weekNr++;
publishersThisWeek = [];
publishers.forEach(p => p.currentWeekAssignments = 0);
if (forDay) { break; }
else {
day.setDate(day.getDate() + 1);
dayNr++;
if (common.DaysOfWeekArray[day.getDayEuropean()] === DayOfWeek.Sunday) {
weekNr++;
publishersThisWeek = [];
publishers.forEach(p => p.currentWeekAssignments = 0);
}
}
if (forDay) break;
}
let from = monthInfo.firstMonday, to = monthInfo.lastSunday;
if (forDay) {
from = day;
to = endDate;
}
let allShifts = await prisma.shift.findMany({
where: {
startTime: {
gte: monthInfo.firstMonday,
lt: monthInfo.lastSunday,
gte: from,
lt: to,
},
},
include: {
@ -320,17 +337,23 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
},
});
let publishersToday = [];
let rankedPublishers = [];
// # # # # # # # # # # #
// Second pass - prioritize shifts with transport where it is needed
if (forDay) { }
else {
day = new Date(monthInfo.firstMonday);
dayNr = 1;
weekNr = 1;
}
console.log("\r\n\r\n\r\n" + "# ".repeat(50));
console.log("Second pass - fix transports " + monthInfo.monthName + " " + monthInfo.year);
console.log("Second pass - fix transports " + day.toLocaleDateString());
day = new Date(monthInfo.firstMonday);
dayNr = 1;
weekNr = 1;
while (day < endDate) {
let dayOfWeekEnum = common.getDayOfWeekNameEnEnumForDate(day);
let event = events.find((event) => event.dayofweek == common.DaysOfWeekArray[day.getDayEuropean()]);
@ -371,7 +394,7 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
else if (publishersNeeded > 0) {
console.log("shift " + shift.name + " requires transport (" + transportCapable.length + " transport capable)");
let availablePubsForTheShift = await data.filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type', shift.startTime, true, false, false, true, false);
let availablePubsForTheShift = await data.filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type,familyHeadId', shift.startTime, true, false, false, true, false);
let availablePublishers = availablePubsForTheShift.filter(p => {
const hasTransportInAvailability = shift.transportIn && p.availabilities.some(avail => avail.isWithTransportIn);
@ -386,45 +409,35 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
} else if (algType == 1) {
rankedPublishers = await RankPublishersForShiftWeighted([...availablePublishers], scheduledPubsPerDayAndWeek, day, weekNr);
}
// if (rankedPublishers.length > 0) {
// const newAssignment = await prisma.assignment.create({
// data: {
// shift: {
// connect: {
// id: shift.id,
// },
// },
// publisher: {
// connect: {
// id: rankedPublishers[0].id,
// },
// },
// isWithTransport: true,
// isConfirmed: true,
// isBySystem: false,
// },
// });
// shift.assignments.push(newAssignment);
// publishersToday.push(rankedPublishers[0].id);
// updateRegistry(rankedPublishers[0].id, day, weekNr);
// }
AddPublisherAssignment(prisma, event, shift, availablePublishers, rankedPublishers, publishersToday, day, weekNr);
}
}
}
day.setDate(day.getDate() + 1);
if (forDay) { break; }
else {
day.setDate(day.getDate() + 1);
}
}
// Fill the rest of the shifts
// # # # # # # # # # # #
// 3. Fill the rest of the shifts
let goal = 1;
while (goal <= 4) {
console.log("\r\n\r\n\r\n" + "# ".repeat(50));
console.log("Filling shifts with " + goal + " publishers " + monthInfo.monthName + " " + monthInfo.year);
day = new Date(monthInfo.firstMonday);
dayNr = 1;
weekNr = 1;
if (forDay) {
}
else {
day = new Date(monthInfo.firstMonday);
dayNr = 1;
weekNr = 1;
}
console.log("\r\n\r\n" + "# ".repeat(50));
console.log("Filling shifts with " + goal + " publishers | " + day.toLocaleDateString());
while (day < endDate) {
let dayOfWeekEnum = common.getDayOfWeekNameEnEnumForDate(day);
let event = events.find((event) => event.dayofweek == common.DaysOfWeekArray[day.getDayEuropean()]);
@ -452,7 +465,7 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
console.log("Filling shift " + shift.name + " with " + goal + " publishers");
let publishersNeeded = event.numberOfPublishers - shift.assignments.length;
if (publishersNeeded > 0 && shift.assignments.length < goal) {
let availablePubsForTheShift = await data.filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type', shift.startTime, true, false, false, true, false);
let availablePubsForTheShift = await data.filterPublishersNew('id,firstName,lastName,email,isActive,desiredShiftsPerMonth,lastLogin,type,familyHeadId', shift.startTime, true, false, false, true, false);
let availablePublishers = await FilterInappropriatePublishers([...availablePubsForTheShift], publishersToday, shift);
if (algType == 0) {
@ -461,12 +474,17 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
rankedPublishers = await RankPublishersForShiftWeighted([...availablePublishers], scheduledPubsPerDayAndWeek, day, weekNr);
}
AddPublisherAssignment(prisma, event, shift, availablePublishers, rankedPublishers, publishersToday, day, weekNr);
await AddPublisherAssignment(prisma, event, shift, availablePublishers, rankedPublishers, publishersToday, day, weekNr);
}
}
}
day.setDate(day.getDate() + 1);
if (forDay) { break; }
else {
day.setDate(day.getDate() + 1);
}
}
goal += 1;
}
@ -486,14 +504,18 @@ async function GenerateSchedule(axios, date, copyFromPreviousMonth = false, auto
async function AddPublisherAssignment(prisma, event, shift, availablePubsForTheShift, rankedPublishers, publishersToday, day, weekNr) {
if (rankedPublishers.length == 0) {
console.log("No available publishers for shift " + shift.name);
console.log("! ! ! No available publishers for shift " + shift.name + " ! ! !");
} else {
for (let i = 0; i < rankedPublishers.length; i++) {
let mainPublisher = rankedPublishers[i];
let familyMembers = availablePubsForTheShift.filter(p => (p.familyHeadId && p.familyHeadId === mainPublisher.familyHeadId) || (p.familyHeadId === mainPublisher.id));
let familyMembers = availablePubsForTheShift.filter(p => (p.id !== mainPublisher.id && (p.id === mainPublisher.familyHeadId) || (p.familyHeadId === mainPublisher.id)));
if (familyMembers.length > 0 && (shift.assignments.length + familyMembers.length + 1) <= event.numberOfPublishers) {
console.log("Assigning " + mainPublisher.firstName + " " + mainPublisher.lastName + " and family members to " + new Date(shift.startTime).getDate() + " " + shift.name);
console.log("Assigning " + mainPublisher.firstName + " " + mainPublisher.lastName + " and " + familyMembers.length + " available family members to " + new Date(shift.startTime).getDate() + " " + shift.name);
const hasTransportInAvailability = shift.transportIn && mainPublisher.availabilities.some(avail => avail.isWithTransportIn);
const hasTransportOutAvailability = shift.transportOut && mainPublisher.availabilities.some(avail => avail.isWithTransportOut);
const newAssignment = await prisma.assignment.create({
data: {
shift: {
@ -508,7 +530,7 @@ async function AddPublisherAssignment(prisma, event, shift, availablePubsForTheS
},
isConfirmed: false,
isBySystem: false,
isWithTransport: shift.requiresTransport,
isWithTransport: (hasTransportInAvailability || hasTransportOutAvailability),
},
});
shift.assignments.push(newAssignment);
@ -540,6 +562,10 @@ async function AddPublisherAssignment(prisma, event, shift, availablePubsForTheS
break;
} else if (familyMembers.length == 0) {
console.log("Assigning " + mainPublisher.firstName + " " + mainPublisher.lastName + " to " + new Date(shift.startTime).getDate() + " " + shift.name);
const hasTransportInAvailability = shift.transportIn && mainPublisher.availabilities.some(avail => avail.isWithTransportIn);
const hasTransportOutAvailability = shift.transportOut && mainPublisher.availabilities.some(avail => avail.isWithTransportOut);
const newAssignment = await prisma.assignment.create({
data: {
shift: {
@ -554,7 +580,7 @@ async function AddPublisherAssignment(prisma, event, shift, availablePubsForTheS
},
isConfirmed: false,
isBySystem: false,
isWithTransport: shift.requiresTransport,
isWithTransport: (hasTransportInAvailability || hasTransportOutAvailability),
},
});
shift.assignments.push(newAssignment);
@ -564,17 +590,16 @@ async function AddPublisherAssignment(prisma, event, shift, availablePubsForTheS
}
}
}
}
async function FilterInappropriatePublishers(availablePublishers, pubsToExclude, shift) {
async function FilterInappropriatePublishers(availablePublishers, pubsToExclude, shift, maxShifts = 0) {
//ToDo: Optimization: store number of publishers, so we process the shifts from least to most available publishers later.
let goodPublishers = availablePublishers.filter(p => {
const isNotAssigned = !shift.assignments.some(a => a.publisher?.id === p.id);
const isNotAssignedToday = !pubsToExclude.includes(p.id);
const isAssignedEnough = p.currentMonthAssignments >= p.desiredShiftsPerMonth
|| p.currentMonthAssignments >= 6; // overwrite the desiredShiftsPerMonth to max 6 shifts per month
return isNotAssigned && isNotAssignedToday && !isAssignedEnough;
|| p.currentMonthAssignments >= maxShifts; // overwrite the desiredShiftsPerMonth to max 10 shifts per month
return isNotAssigned && isNotAssignedToday && (!isAssignedEnough || maxShifts == 0);
});
return goodPublishers;
}
@ -696,10 +721,17 @@ async function RankPublishersForShiftWeighted(publishers, scheduledPubsPerDayAnd
const result = calculateScoreAndPenalties(p);
p.score = result.score;
p.penalties = result.penalties;
p.finalScore = p.score;
if (p.finalScore > 0) {
p.penalties.forEach(penalty => {
p.finalScore *= penalty.penalty;
});
}
});
// Sort publishers based on score
let ranked = publishers.sort((a, b) => b.score - a.score);
let ranked = publishers.sort((a, b) => b.finalScore - a.finalScore);
// Log the scores and penalties of the top publisher
if (ranked.length > 0) {