chooseHigherSeverity( ['severity' => 2, 'action' => 'team_notify'], ['severity' => 4, 'action' => 'auto_email'] ); $this->assertSame(4, $result['severity']); } public function test_choose_higher_severity_breaks_ties_by_action_priority(): void { $tracking = Mockery::mock(AttendanceTracking::class); $parents = Mockery::mock(AttendanceParentLookupService::class); $service = new ViolationRuleEngineService($tracking, $parents); $result = $service->chooseHigherSeverity( ['severity' => 3, 'action' => 'team_notify'], ['severity' => 3, 'action' => 'auto_email'] ); $this->assertSame('team_notify', $result['action']); } public function test_window_weeks_for_violation_code_returns_expected_values(): void { $tracking = Mockery::mock(AttendanceTracking::class); $parents = Mockery::mock(AttendanceParentLookupService::class); $service = new ViolationRuleEngineService($tracking, $parents); $this->assertSame(3, $service->windowWeeksForViolationCode('ABS_2')); $this->assertSame(4, $service->windowWeeksForViolationCode('ABS_3')); $this->assertSame(5, $service->windowWeeksForViolationCode('ABS_4')); $this->assertSame(4, $service->windowWeeksForViolationCode('LATE_4')); } public function test_has_n_consecutive_items_detects_weekly_sequence(): void { $tracking = Mockery::mock(AttendanceTracking::class); $parents = Mockery::mock(AttendanceParentLookupService::class); $service = new ViolationRuleEngineService($tracking, $parents); $result = $service->hasNConsecutiveItems( ['2026-03-15', '2026-03-08', '2026-03-01'], 3, 7 ); $this->assertTrue($result); } public function test_has_n_in_w_active_weeks_detects_window_match(): void { $tracking = Mockery::mock(AttendanceTracking::class); $parents = Mockery::mock(AttendanceParentLookupService::class); $service = new ViolationRuleEngineService($tracking, $parents); $this->assertTrue($service->hasNInWActiveWeeks([0, 1], 2, 3)); $this->assertFalse($service->hasNInWActiveWeeks([0], 2, 3)); } public function test_two_lates_one_abs_in_w_weeks_detects_mix_rule(): void { $tracking = Mockery::mock(AttendanceTracking::class); $parents = Mockery::mock(AttendanceParentLookupService::class); $service = new ViolationRuleEngineService($tracking, $parents); $this->assertTrue($service->twoLatesOneAbsInWWeeks([1, 2], [3], 4)); $this->assertFalse($service->twoLatesOneAbsInWWeeks([1], [3], 4)); } public function test_derive_school_year_bounds_returns_expected_dates(): void { $tracking = Mockery::mock(AttendanceTracking::class); $parents = Mockery::mock(AttendanceParentLookupService::class); $service = new ViolationRuleEngineService($tracking, $parents); [$start, $end] = $service->deriveSchoolYearBounds('2025-2026'); $this->assertSame('2025-08-01', $start); $this->assertSame('2026-07-31', $end); } }