Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 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 152 153 | 52x 163x 469x 163x 254x 254x 2220x 2219x 52x 399x 79x 80x 399x 50x 399x 527x 19176x 1043x 527x 77x 77x 77x 527x 37x 496x 399x 399x 399x 490x 55x 399x 399x 399x | import { partition, unique } from "remeda";
import { config } from "shared/config";
import { DIRECT_SIGNUP_PRIORITY } from "shared/constants/signups";
import { EventLogAction } from "shared/types/models/eventLog";
import { ProgramItem, State } from "shared/types/models/programItem";
import { User } from "shared/types/models/user";
import { isLotterySignupProgramItem } from "shared/utils/isLotterySignupProgramItem";
import { isSameTime } from "shared/utils/timeComparison";
import { getStartingProgramItems } from "server/features/assignment/utils/getStartingProgramItems";
import { DirectSignupsForProgramItem } from "server/features/direct-signup/directSignupTypes";
export interface AssignmentBonusContext {
thisRunsProgramItemIds: ReadonlySet<string>;
currentStartTimes: readonly string[];
stillRunningProgramItemIds: ReadonlySet<string>;
}
// Built once per algorithm pass: every attendee group asks the same questions of the same
// programme
export const getAssignmentBonusContext = (
allProgramItems: readonly ProgramItem[],
assignmentTime: string,
): AssignmentBonusContext => {
// The lottery program items this starting time decides. Not only the ones still being
// lotteried: a retry drops the items an earlier attempt placed people into, and that
// attempt's results must still read as this run's rather than as a lottery they lost.
const startingProgramItems = getStartingProgramItems(
allProgramItems,
assignmentTime,
).filter(
(programItem) =>
isLotterySignupProgramItem(programItem) &&
programItem.state === State.ACCEPTED,
);
return {
thisRunsProgramItemIds: new Set(
startingProgramItems.map((programItem) => programItem.programItemId),
),
// A rejection names no program item, so it is matched by the time it carries: the run's own
// hour, or any hour its program items start
currentStartTimes: unique([
assignmentTime,
...startingProgramItems.map((programItem) => programItem.startTime),
]),
// A placement the attendee never got to attend is not one they spent: cancelling their own
// sign-up costs the bonus, the program item being cancelled does not. Asked of the whole
// programme, since a placement at any other start time is still a placement.
stillRunningProgramItemIds: new Set(
allProgramItems
.filter((programItem) => programItem.state === State.ACCEPTED)
.map((programItem) => programItem.programItemId),
),
};
};
export const getAssignmentBonus = (
attendeeGroup: User[],
lotteryParticipantDirectSignups: readonly DirectSignupsForProgramItem[],
{
thisRunsProgramItemIds,
currentStartTimes,
stillRunningProgramItemIds,
}: AssignmentBonusContext,
): number => {
/** First time bonus */
const isCurrentStartTime = (startTime: string): boolean =>
currentStartTimes.some((currentStartTime) =>
isSameTime(startTime, currentStartTime),
);
// This run wrote it: for one of the program items it decides, and at one of the hours it
// covers. Neither half alone will do - an item can carry a placement from before it was
// rescheduled onto this hour, and another lottery can cover this hour with its own items.
const isThisRunsOwn = (programItemId: string, startTime: string): boolean =>
thisRunsProgramItemIds.has(programItemId) && isCurrentStartTime(startTime);
// Get group members with previous direct sign-ups or NEW_ASSIGNMENT event log items
const [groupMembersWithDirectSignups, groupMembersWithoutDirectSignups] =
partition(attendeeGroup, (groupMember) => {
const previousDirectSignup = lotteryParticipantDirectSignups.find(
(directSignup) => {
return directSignup.userSignups.find(
(userSignup) =>
userSignup.username === groupMember.username &&
// Exclude this run's own win (priority > 0), but keep first-come-first-served
// (priority 0) sign-ups counting as "previous"
!(
isThisRunsOwn(
directSignup.programItemId,
userSignup.signedToStartTime,
) && userSignup.priority !== DIRECT_SIGNUP_PRIORITY
),
);
},
);
const newAssignmentEvent = groupMember.eventLogItems.find(
(eventLogItem) => {
const previousAssignment =
eventLogItem.action === EventLogAction.NEW_ASSIGNMENT;
const programItemExists = stillRunningProgramItemIds.has(
eventLogItem.programItemId,
);
return (
previousAssignment &&
programItemExists &&
!isThisRunsOwn(
eventLogItem.programItemId,
eventLogItem.programItemStartTime,
)
);
},
);
if (previousDirectSignup ?? newAssignmentEvent) {
return true;
}
return false;
});
// Give first time bonus to the whole group if half of the group members don't have previous direct sign-ups
const averagePreviousDirectSignups =
groupMembersWithDirectSignups.length / attendeeGroup.length;
const firstTimeBonus =
averagePreviousDirectSignups <= 0.5 ? config.server().firstSignupBonus : 0;
/** Additional first time bonus */
// Get group members with previous NO_ASSIGNMENT event log items and without direct sign-ups.
// Ignore a NO_ASSIGNMENT from the current assignmentTime. A run that rejected everyone and
// then failed before marking its items can be run again, and counting the items it wrote
// would make the retry boost the very attendees the first attempt turned down.
const groupMembersWithPreviousFailedLotterySignup =
groupMembersWithoutDirectSignups.filter((groupMember) => {
return groupMember.eventLogItems.find(
(eventLogItem) =>
eventLogItem.action === EventLogAction.NO_ASSIGNMENT &&
!isCurrentStartTime(eventLogItem.programItemStartTime),
);
});
// Give additional first time bonus to the whole group if half of the group members have previous failed lottery sign-ups
const averageFailedLotterySignups =
groupMembersWithPreviousFailedLotterySignup.length / attendeeGroup.length;
const additionalFirstTimeBonus =
averageFailedLotterySignups >= 0.5
? config.server().additionalFirstSignupBonus
: 0;
return firstTimeBonus + additionalFirstTimeBonus;
};
|