11import {
22 avoidSimilarFirstNames ,
3+ maximizeAssignmentGaps ,
34 mustNotHaveRoles ,
45 onlyMultipleGroupRounds ,
56 preferLaterGroups ,
@@ -14,8 +15,22 @@ const buildGroup = (id: number, groupNumber: number): Activity =>
1415 id,
1516 name : `Group ${ groupNumber } ` ,
1617 activityCode : `333-r1-g${ groupNumber } ` ,
17- startTime : `2024-01-01T10:0${ groupNumber } :00Z` ,
18- endTime : `2024-01-01T10:1${ groupNumber } :00Z` ,
18+ startTime : new Date ( Date . UTC ( 2024 , 0 , 1 , 10 , ( groupNumber - 1 ) * 10 ) ) . toISOString ( ) ,
19+ endTime : new Date ( Date . UTC ( 2024 , 0 , 1 , 10 , groupNumber * 10 ) ) . toISOString ( ) ,
20+ } ) ;
21+
22+ const buildTimedActivity = (
23+ id : number ,
24+ activityCode : string ,
25+ startTime : string ,
26+ endTime : string
27+ ) : Activity =>
28+ buildActivity ( {
29+ id,
30+ name : activityCode ,
31+ activityCode,
32+ startTime,
33+ endTime,
1934 } ) ;
2035
2136const scoreProps = ( activity : Activity , activities : Activity [ ] ) => ( {
@@ -113,4 +128,121 @@ describe('recipe constraints', () => {
113128 expect ( avoidSimilarFirstNames . score ( props ) ) . toBeNull ( ) ;
114129 expect ( avoidSimilarFirstNames . score ( { ...props , activity : activities [ 1 ] } ) ) . toBe ( 0 ) ;
115130 } ) ;
131+
132+ it ( 'penalizes but does not reject competitor assignments immediately after helping' , ( ) => {
133+ const staffActivity = buildTimedActivity (
134+ 21 ,
135+ '333-r1-g1' ,
136+ '2024-01-01T10:00:00.000Z' ,
137+ '2024-01-01T10:10:00.000Z'
138+ ) ;
139+ const immediateCompetitorActivity = buildTimedActivity (
140+ 22 ,
141+ '333-r1-g2' ,
142+ '2024-01-01T10:10:00.000Z' ,
143+ '2024-01-01T10:20:00.000Z'
144+ ) ;
145+ const props = {
146+ ...scoreProps ( immediateCompetitorActivity , [ immediateCompetitorActivity ] ) ,
147+ wcif : buildWcif ( [ staffActivity , immediateCompetitorActivity ] ) ,
148+ person : buildPerson ( {
149+ assignments : [ { activityId : 21 , assignmentCode : 'staff-judge' , stationNumber : null } ] ,
150+ } ) ,
151+ options : { noGapPenalty : 100 , gapCapMinutes : 120 } ,
152+ } ;
153+
154+ expect ( maximizeAssignmentGaps . score ( props ) ) . toBe ( - 100 ) ;
155+ } ) ;
156+
157+ it ( 'scores larger positive staff-to-competitor gaps higher' , ( ) => {
158+ const staffActivity = buildTimedActivity (
159+ 21 ,
160+ '333-r1-g1' ,
161+ '2024-01-01T10:00:00.000Z' ,
162+ '2024-01-01T10:10:00.000Z'
163+ ) ;
164+ const shortGapActivity = buildTimedActivity (
165+ 22 ,
166+ '333-r1-g2' ,
167+ '2024-01-01T10:15:00.000Z' ,
168+ '2024-01-01T10:25:00.000Z'
169+ ) ;
170+ const longGapActivity = buildTimedActivity (
171+ 23 ,
172+ '333-r1-g3' ,
173+ '2024-01-01T10:50:00.000Z' ,
174+ '2024-01-01T11:00:00.000Z'
175+ ) ;
176+ const props = {
177+ ...scoreProps ( shortGapActivity , [ shortGapActivity , longGapActivity ] ) ,
178+ wcif : buildWcif ( [ staffActivity , shortGapActivity , longGapActivity ] ) ,
179+ person : buildPerson ( {
180+ assignments : [ { activityId : 21 , assignmentCode : 'staff-runner' , stationNumber : null } ] ,
181+ } ) ,
182+ options : { noGapPenalty : 100 , gapCapMinutes : 120 } ,
183+ } ;
184+
185+ expect ( maximizeAssignmentGaps . score ( { ...props , activity : longGapActivity } ) ) . toBeGreaterThan (
186+ maximizeAssignmentGaps . score ( props ) ?? 0
187+ ) ;
188+ } ) ;
189+
190+ it ( 'scores competitor assignments farther from other rounds higher' , ( ) => {
191+ const otherRoundActivity = buildTimedActivity (
192+ 31 ,
193+ '222-r1-g1' ,
194+ '2024-01-01T10:00:00.000Z' ,
195+ '2024-01-01T10:10:00.000Z'
196+ ) ;
197+ const nearActivity = buildTimedActivity (
198+ 41 ,
199+ '333-r1-g1' ,
200+ '2024-01-01T10:15:00.000Z' ,
201+ '2024-01-01T10:25:00.000Z'
202+ ) ;
203+ const farActivity = buildTimedActivity (
204+ 42 ,
205+ '333-r1-g2' ,
206+ '2024-01-01T11:00:00.000Z' ,
207+ '2024-01-01T11:10:00.000Z'
208+ ) ;
209+ const props = {
210+ ...scoreProps ( nearActivity , [ nearActivity , farActivity ] ) ,
211+ wcif : buildWcif ( [ otherRoundActivity , nearActivity , farActivity ] ) ,
212+ person : buildPerson ( {
213+ assignments : [ { activityId : 31 , assignmentCode : 'competitor' , stationNumber : null } ] ,
214+ } ) ,
215+ options : { noGapPenalty : 100 , gapCapMinutes : 120 } ,
216+ } ;
217+
218+ expect ( maximizeAssignmentGaps . score ( { ...props , activity : farActivity } ) ) . toBeGreaterThan (
219+ maximizeAssignmentGaps . score ( props ) ?? 0
220+ ) ;
221+ } ) ;
222+
223+ it ( 'penalizes staff assignments immediately before future competitor assignments' , ( ) => {
224+ const immediateStaffActivity = buildTimedActivity (
225+ 51 ,
226+ '333-r1-g1' ,
227+ '2024-01-01T10:00:00.000Z' ,
228+ '2024-01-01T10:10:00.000Z'
229+ ) ;
230+ const futureCompetitorActivity = buildTimedActivity (
231+ 61 ,
232+ '222-r1-g1' ,
233+ '2024-01-01T10:10:00.000Z' ,
234+ '2024-01-01T10:20:00.000Z'
235+ ) ;
236+ const props = {
237+ ...scoreProps ( immediateStaffActivity , [ immediateStaffActivity ] ) ,
238+ assignmentCode : 'staff-judge' ,
239+ wcif : buildWcif ( [ immediateStaffActivity , futureCompetitorActivity ] ) ,
240+ person : buildPerson ( {
241+ assignments : [ { activityId : 61 , assignmentCode : 'competitor' , stationNumber : null } ] ,
242+ } ) ,
243+ options : { noGapPenalty : 100 , gapCapMinutes : 120 } ,
244+ } ;
245+
246+ expect ( maximizeAssignmentGaps . score ( props ) ) . toBe ( - 100 ) ;
247+ } ) ;
116248} ) ;
0 commit comments