11import { Service } from '../../shared/kernel'
22import { MainContext } from '../context'
33import { student } from '../repos/StudentRepository'
4+ import { getTriggerLogic } from '../../shared/triggers'
45
56interface AutoScoreRule {
67 id : number
@@ -129,7 +130,6 @@ export class AutoScoreService extends Service {
129130 const data = await fs . readJsonFile < AutoScoreRulesFile > ( RULES_FILE_NAME , 'automatic' )
130131 if ( data && data . rules ) {
131132 this . rules = data . rules . map ( ( rule : any ) => {
132- // 数据迁移:将旧格式转换为新格式
133133 const migratedRule = this . migrateRule ( rule )
134134 return {
135135 ...migratedRule ,
@@ -138,7 +138,6 @@ export class AutoScoreService extends Service {
138138 : undefined
139139 }
140140 } )
141- // 如果有数据迁移,保存新格式
142141 if (
143142 data . rules . some (
144143 ( rule : any ) => rule . intervalMinutes !== undefined || rule . scoreValue !== undefined
@@ -159,12 +158,10 @@ export class AutoScoreService extends Service {
159158 }
160159
161160 private migrateRule ( rule : any ) : AutoScoreRule {
162- // 如果已经是新格式,直接返回
163161 if ( ! rule . intervalMinutes && ! rule . scoreValue ) {
164162 return rule
165163 }
166164
167- // 迁移旧格式到新格式
168165 const migratedRule : AutoScoreRule = {
169166 id : rule . id ,
170167 enabled : rule . enabled ,
@@ -175,7 +172,6 @@ export class AutoScoreService extends Service {
175172 actions : rule . actions || [ ]
176173 }
177174
178- // 将intervalMinutes迁移到triggers
179175 if (
180176 rule . intervalMinutes &&
181177 ! migratedRule . triggers ?. find ( ( t ) => t . event === 'interval_time_passed' )
@@ -187,7 +183,6 @@ export class AutoScoreService extends Service {
187183 } )
188184 }
189185
190- // 将scoreValue和reason迁移到actions
191186 if (
192187 rule . scoreValue !== undefined &&
193188 ! migratedRule . actions ?. find ( ( a ) => a . event === 'add_score' )
@@ -280,25 +275,28 @@ export class AutoScoreService extends Service {
280275 this . timers . delete ( rule . id )
281276 }
282277
283- // 从triggers中读取间隔时间
284- const intervalTrigger = rule . triggers ?. find ( ( t ) => t . event === 'interval_time_passed' )
285- const intervalMinutes = intervalTrigger ?. value ? parseInt ( intervalTrigger . value , 10 ) : 0
278+ const now = new Date ( )
279+ let delayMs = 0
280+ let primaryTrigger : { event : string ; value ?: string } | undefined
281+
282+ for ( const trigger of rule . triggers || [ ] ) {
283+ const logic = getTriggerLogic ( trigger . event )
284+ if ( logic ?. calculateNextTime ) {
285+ const result = logic . calculateNextTime ( trigger . value || '' , rule . lastExecuted , now )
286+ if ( delayMs === 0 || result . delayMs < delayMs ) {
287+ delayMs = result . delayMs
288+ primaryTrigger = trigger
289+ }
290+ }
291+ }
286292
287- if ( ! intervalMinutes || intervalMinutes <= 0 ) {
288- this . logger . warn ( `Rule ${ rule . name } has no valid interval time , skipping timer ` )
293+ if ( ! primaryTrigger ) {
294+ this . logger . warn ( `Rule ${ rule . name } has no valid triggers with timing logic , skipping` )
289295 return
290296 }
291297
292- const now = new Date ( )
293- const intervalMs = intervalMinutes * 60 * 1000
294-
295- let delayMs = intervalMs
296- if ( rule . lastExecuted ) {
297- const timeSinceLastExecution = now . getTime ( ) - rule . lastExecuted . getTime ( )
298- delayMs = intervalMs - ( timeSinceLastExecution % intervalMs )
299- if ( timeSinceLastExecution >= intervalMs ) {
300- delayMs = 0
301- }
298+ if ( delayMs < 0 ) {
299+ delayMs = 0
302300 }
303301
304302 const timer = setTimeout ( ( ) => {
@@ -307,21 +305,32 @@ export class AutoScoreService extends Service {
307305 } , delayMs )
308306
309307 this . timers . set ( rule . id , timer )
308+ this . logger . info ( `Rule ${ rule . name } scheduled to execute in ${ delayMs } ms` )
310309 }
311310
312311 private setRuleInterval ( rule : AutoScoreRule ) {
313- // 从triggers中读取间隔时间
314- const intervalTrigger = rule . triggers ?. find ( ( t ) => t . event === 'interval_time_passed' )
315- const intervalMinutes = intervalTrigger ?. value ? parseInt ( intervalTrigger . value , 10 ) : 0
312+ const now = new Date ( )
313+ let minDelayMs = Infinity
314+ let primaryTrigger : { event : string ; value ?: string } | undefined
315+
316+ for ( const trigger of rule . triggers || [ ] ) {
317+ const logic = getTriggerLogic ( trigger . event )
318+ if ( logic ?. calculateNextTime ) {
319+ const result = logic . calculateNextTime ( trigger . value || '' , rule . lastExecuted , now )
320+ if ( result . delayMs < minDelayMs ) {
321+ minDelayMs = result . delayMs
322+ primaryTrigger = trigger
323+ }
324+ }
325+ }
316326
317- if ( ! intervalMinutes || intervalMinutes <= 0 ) {
327+ if ( ! primaryTrigger || minDelayMs === Infinity ) {
318328 return
319329 }
320330
321- const intervalMs = intervalMinutes * 60 * 1000
322331 const timer = setInterval ( ( ) => {
323332 this . executeRule ( rule )
324- } , intervalMs )
333+ } , minDelayMs )
325334
326335 this . timers . set ( rule . id , timer )
327336 }
@@ -331,7 +340,6 @@ export class AutoScoreService extends Service {
331340 this . logger . info ( `Executing auto score rule: ${ rule . name } ` )
332341
333342 const studentRepo = this . mainCtx . students
334- const eventRepo = this . mainCtx . events
335343
336344 let studentsToScore : student [ ] = [ ]
337345 if ( rule . studentNames . length === 0 ) {
@@ -347,17 +355,30 @@ export class AutoScoreService extends Service {
347355 }
348356 }
349357
350- // 从actions中读取分数和理由
351- const scoreAction = rule . actions ?. find ( ( a ) => a . event === 'add_score' )
352- const scoreValue = scoreAction ?. value ? parseInt ( scoreAction . value , 10 ) : 0
353- const reason = scoreAction ?. reason || `自动化加分 - ${ rule . name } `
358+ for ( const trigger of rule . triggers || [ ] ) {
359+ const logic = getTriggerLogic ( trigger . event )
360+ if ( logic ?. check ) {
361+ const context = {
362+ students : studentsToScore ,
363+ events : [ ] ,
364+ rule : {
365+ id : rule . id ,
366+ name : rule . name ,
367+ studentNames : rule . studentNames ,
368+ triggers : rule . triggers ,
369+ actions : rule . actions
370+ } ,
371+ now : new Date ( )
372+ }
373+ const result = logic . check ( context , trigger . value || '' )
374+ if ( result . matchedStudents && result . matchedStudents . length > 0 ) {
375+ studentsToScore = result . matchedStudents
376+ }
377+ }
378+ }
354379
355- for ( const student of studentsToScore ) {
356- await eventRepo . create ( {
357- student_name : student . name ,
358- reason_content : reason ,
359- delta : scoreValue
360- } )
380+ for ( const action of rule . actions || [ ] ) {
381+ await this . executeAction ( action , studentsToScore , rule . name )
361382 }
362383
363384 rule . lastExecuted = new Date ( )
@@ -371,6 +392,54 @@ export class AutoScoreService extends Service {
371392 }
372393 }
373394
395+ private async executeAction (
396+ action : { event : string ; value ?: string ; reason ?: string } ,
397+ students : student [ ] ,
398+ ruleName : string
399+ ) {
400+ const eventRepo = this . mainCtx . events
401+
402+ switch ( action . event ) {
403+ case 'add_score' : {
404+ const scoreValue = action . value ? parseInt ( action . value , 10 ) : 0
405+ const reason = action . reason || `自动化加分 - ${ ruleName } `
406+ for ( const student of students ) {
407+ await eventRepo . create ( {
408+ student_name : student . name ,
409+ reason_content : reason ,
410+ delta : scoreValue
411+ } )
412+ }
413+ break
414+ }
415+ case 'add_tag' : {
416+ const tagName = action . value
417+ if ( tagName ) {
418+ const studentRepo = this . mainCtx . students
419+ for ( const student of students ) {
420+ const currentTags = student . tags || [ ]
421+ if ( ! currentTags . includes ( tagName ) ) {
422+ await studentRepo . update ( student . id , {
423+ tags : [ ...currentTags , tagName ]
424+ } )
425+ }
426+ }
427+ }
428+ break
429+ }
430+ case 'send_notification' : {
431+ this . logger . info ( `Notification action: ${ action . value } ` )
432+ break
433+ }
434+ case 'set_student_status' : {
435+ this . logger . info ( `Set student status action: ${ action . value } (not implemented - student type has no status field)` )
436+ break
437+ }
438+ default :
439+ this . logger . warn ( `Unknown action event: ${ action . event } ` )
440+ }
441+ }
442+
374443 private stopRules ( ) {
375444 for ( const [ timer ] of this . timers ) {
376445 clearTimeout ( timer )
0 commit comments