service = app(TrophyReportService::class); Configuration::query()->create([ 'config_key' => 'school_year', 'config_value' => $this->schoolYear, ]); $this->section = ClassSection::factory()->create([ 'class_section_id' => 401, 'class_section_name' => 'Grade 4A', ]); } public function test_calculate_threshold_enforces_minimum_of_three_winners(): void { $result = $this->service->calculateThreshold([50, 60, 70, 80, 90], 95.0); $this->assertSame('min3_reduced', $result['method']); $this->assertSame(3, $result['winners']); $this->assertSame(70.0, $result['threshold']); } public function test_projection_marks_students_without_fall_scores_as_not_projected(): void { $first = $this->enrollStudent('Amina', 'Top', 'Female', 'S-1', 98.0); $second = $this->enrollStudent('Bilal', 'Strong', 'Male', 'S-2', 95.0); $third = $this->enrollStudent('Celine', 'Good', 'Female', 'S-3', 90.0); $fourth = $this->enrollStudent('David', 'Missing', 'Male', 'S-4', null); $payload = $this->service->projection($this->schoolYear, 75.0); $this->assertSame($this->schoolYear, $payload['selected_year']); $this->assertSame(75.0, $payload['selected_percentile']); $this->assertSame([$this->schoolYear], $payload['years']); $this->assertCount(1, $payload['class_results']); $classResult = $payload['class_results'][0]; $this->assertSame(4, $classResult['student_count']); $this->assertSame(3, $classResult['scored_count']); $this->assertSame(3, $classResult['trophy_count']); $this->assertSame(90.0, $classResult['threshold']); $this->assertSame(4, $payload['summary']['students']); $this->assertSame(3, $payload['summary']['trophies']); $students = collect($classResult['students'])->keyBy('student_id'); $this->assertTrue($students[$first->id]['projected_trophy']); $this->assertTrue($students[$second->id]['projected_trophy']); $this->assertTrue($students[$third->id]['projected_trophy']); $this->assertFalse($students[$fourth->id]['projected_trophy']); $this->assertNull($students[$fourth->id]['fall_score']); } public function test_final_report_classifies_confirmed_surprise_and_missed_winners(): void { $this->enrollStudent('Adam', 'Confirmed', 'Male', 'S-10', 99.0, 97.0); $this->enrollStudent('Basma', 'Confirmed', 'Female', 'S-11', 95.0, 93.0); $missed = $this->enrollStudent('Cyrus', 'Missed', 'Male', 'S-12', 92.0, 68.0); $surprise = $this->enrollStudent('Dina', 'Surprise', 'Female', 'S-13', 88.0, 100.0); $this->enrollStudent('Evan', 'None', 'Male', 'S-14', 70.0, 50.0); $payload = $this->service->final($this->schoolYear, 75.0); $this->assertCount(1, $payload['class_results']); $this->assertSame(3, $payload['summary']['predicted']); $this->assertSame(3, $payload['summary']['actual']); $this->assertSame(2, $payload['summary']['confirmed']); $this->assertSame(1, $payload['summary']['surprises']); $this->assertSame(1, $payload['summary']['missed']); $this->assertSame(67.0, $payload['summary']['accuracy']); $this->assertSame(3, $payload['winner_gender_summary']['total']); $this->assertSame(1, $payload['winner_gender_summary']['boys']); $this->assertSame(2, $payload['winner_gender_summary']['girls']); $this->assertCount(3, $payload['winner_stickers']); $classResult = $payload['class_results'][0]; $this->assertSame(92.0, $classResult['fall_threshold']); $this->assertSame(94.0, $classResult['year_threshold']); $this->assertSame(2, $classResult['confirmed']); $this->assertSame(1, $classResult['surprises']); $this->assertSame(1, $classResult['missed']); $this->assertSame(67.0, $classResult['accuracy']); $students = collect($classResult['students'])->keyBy('student_id'); $this->assertSame('missed', $students[$missed->id]['status']); $this->assertTrue($students[$missed->id]['predicted']); $this->assertFalse($students[$missed->id]['actual']); $this->assertSame('surprise', $students[$surprise->id]['status']); $this->assertFalse($students[$surprise->id]['predicted']); $this->assertTrue($students[$surprise->id]['actual']); } private function enrollStudent( string $firstname, string $lastname, string $gender, string $schoolId, ?float $fallScore, ?float $springScore = null ): Student { $student = Student::factory()->create([ 'firstname' => $firstname, 'lastname' => $lastname, 'gender' => $gender, 'school_id' => $schoolId, 'school_year' => $this->schoolYear, 'is_active' => 1, ]); StudentClass::query()->create([ 'student_id' => $student->id, 'school_id' => null, 'class_section_id' => $this->section->class_section_id, 'school_year' => $this->schoolYear, 'semester' => 'Fall', 'is_event_only' => 0, ]); if ($fallScore !== null) { SemesterScore::query()->create([ 'student_id' => $student->id, 'school_id' => $schoolId, 'class_section_id' => $this->section->class_section_id, 'semester_score' => $fallScore, 'semester' => 'Fall', 'school_year' => $this->schoolYear, ]); } if ($springScore !== null) { SemesterScore::query()->create([ 'student_id' => $student->id, 'school_id' => $schoolId, 'class_section_id' => $this->section->class_section_id, 'semester_score' => $springScore, 'semester' => 'Spring', 'school_year' => $this->schoolYear, ]); } return $student; } }