calculator = new TeacherStatisticsCalculator(); } // --- Distribution --- #[Test] public function distributionReturnsEmptyBinsWhenNoValues(): void { $bins = $this->calculator->calculateDistribution([]); self::assertSame([0, 0, 0, 0, 0, 0, 0, 0], $bins); } #[Test] public function distributionPlacesValuesInCorrectBins(): void { // Bins: [0-2.5[, [2.5-5[, [5-7.5[, [7.5-10[, [10-12.5[, [12.5-15[, [15-17.5[, [17.5-20] $values = [1.0, 3.0, 6.0, 9.0, 11.0, 14.0, 16.0, 19.0]; $bins = $this->calculator->calculateDistribution($values); self::assertSame([1, 1, 1, 1, 1, 1, 1, 1], $bins); } #[Test] public function distributionHandlesMaxValue20InLastBin(): void { $bins = $this->calculator->calculateDistribution([20.0]); self::assertSame([0, 0, 0, 0, 0, 0, 0, 1], $bins); } #[Test] public function distributionHandlesMultipleValuesInSameBin(): void { $values = [10.0, 10.5, 11.0, 12.0]; $bins = $this->calculator->calculateDistribution($values); // All in bin [10-12.5[ self::assertSame([0, 0, 0, 0, 4, 0, 0, 0], $bins); } #[Test] public function distributionHandlesBoundaryValues(): void { $values = [0.0, 2.5, 5.0, 7.5, 10.0, 12.5, 15.0, 17.5]; $bins = $this->calculator->calculateDistribution($values); // 0.0 → bin 0, 2.5 → bin 1, 5.0 → bin 2, 7.5 → bin 3 // 10.0 → bin 4, 12.5 → bin 5, 15.0 → bin 6, 17.5 → bin 7 self::assertSame([1, 1, 1, 1, 1, 1, 1, 1], $bins); } // --- Success Rate --- #[Test] public function successRateReturnsZeroWhenNoValues(): void { self::assertSame(0.0, $this->calculator->calculateSuccessRate([])); } #[Test] public function successRateCountsValuesAboveThreshold(): void { // Threshold defaults to 10.0 $values = [8.0, 10.0, 12.0, 14.0, 6.0]; // 10.0, 12.0, 14.0 are >= 10 → 3/5 = 60% self::assertSame(60.0, $this->calculator->calculateSuccessRate($values)); } #[Test] public function successRateWithCustomThreshold(): void { $values = [8.0, 10.0, 12.0, 14.0, 6.0]; // >= 12: 12.0, 14.0 → 2/5 = 40% self::assertSame(40.0, $this->calculator->calculateSuccessRate($values, 12.0)); } #[Test] public function successRateWithAllAbove(): void { $values = [15.0, 18.0, 12.0]; self::assertSame(100.0, $this->calculator->calculateSuccessRate($values)); } #[Test] public function successRateWithNoneAbove(): void { $values = [4.0, 6.0, 8.0]; self::assertSame(0.0, $this->calculator->calculateSuccessRate($values)); } // --- Trend Line --- #[Test] public function trendLineReturnsNullWhenLessThanTwoPoints(): void { self::assertNull($this->calculator->calculateTrendLine([])); self::assertNull($this->calculator->calculateTrendLine([[1, 10.0]])); } #[Test] public function trendLineCalculatesLinearRegression(): void { // Perfectly increasing line: y = 2x + 8 $points = [[1, 10.0], [2, 12.0], [3, 14.0]]; $result = $this->calculator->calculateTrendLine($points); self::assertNotNull($result); self::assertEqualsWithDelta(2.0, $result->slope, 0.01); self::assertEqualsWithDelta(8.0, $result->intercept, 0.01); } #[Test] public function trendLineWithFlatData(): void { $points = [[1, 12.0], [2, 12.0], [3, 12.0]]; $result = $this->calculator->calculateTrendLine($points); self::assertNotNull($result); self::assertEqualsWithDelta(0.0, $result->slope, 0.01); } #[Test] public function trendLineWithDecreasingData(): void { $points = [[1, 16.0], [2, 14.0], [3, 12.0]]; $result = $this->calculator->calculateTrendLine($points); self::assertNotNull($result); self::assertLessThan(0, $result->slope); } // --- Detect Trend --- #[Test] public function detectTrendReturnsStableWhenLessThanTwoAverages(): void { self::assertSame('stable', $this->calculator->detectTrend([])); self::assertSame('stable', $this->calculator->detectTrend([12.0])); } #[Test] public function detectTrendReturnsImprovingWhenLastIsHigherByThreshold(): void { // Last - first > 1.0 (default threshold) self::assertSame('improving', $this->calculator->detectTrend([10.0, 11.5, 13.0])); } #[Test] public function detectTrendReturnsDecliningWhenLastIsLowerByThreshold(): void { self::assertSame('declining', $this->calculator->detectTrend([14.0, 12.0, 10.0])); } #[Test] public function detectTrendReturnsStableWhenDifferenceIsBelowThreshold(): void { self::assertSame('stable', $this->calculator->detectTrend([12.0, 12.5, 12.8])); } // --- Percentile --- #[Test] public function percentileReturnsHundredWhenNoOtherValues(): void { self::assertSame(100.0, $this->calculator->calculatePercentile(12.0, [])); } #[Test] public function percentileCalculatesCorrectly(): void { // My value: 14.0, others: [10, 12, 16, 18] // 2 out of 4 are below → 50th percentile self::assertSame(50.0, $this->calculator->calculatePercentile(14.0, [10.0, 12.0, 16.0, 18.0])); } #[Test] public function percentileAtBottom(): void { self::assertSame(0.0, $this->calculator->calculatePercentile(5.0, [10.0, 12.0, 14.0])); } #[Test] public function percentileAtTop(): void { self::assertSame(100.0, $this->calculator->calculatePercentile(20.0, [10.0, 12.0, 14.0])); } #[Test] public function percentileWithDuplicateValues(): void { // My value: 12.0, others: [12.0, 12.0, 12.0] // 0 strictly below → 0th percentile self::assertSame(0.0, $this->calculator->calculatePercentile(12.0, [12.0, 12.0, 12.0])); } // --- Detect Trend edge cases --- #[Test] public function detectTrendAtExactThresholdReturnsStable(): void { // Difference is exactly 1.0 → should be stable (not strictly above threshold) self::assertSame('stable', $this->calculator->detectTrend([10.0, 11.0])); } #[Test] public function detectTrendJustAboveThresholdReturnsImproving(): void { // Difference is 1.01 → should be improving self::assertSame('improving', $this->calculator->detectTrend([10.0, 11.01])); } // --- Distribution edge cases --- #[Test] public function distributionWithSingleValue(): void { $bins = $this->calculator->calculateDistribution([10.0]); self::assertSame([0, 0, 0, 0, 1, 0, 0, 0], $bins); } #[Test] public function distributionWithAllSameValues(): void { $values = [12.0, 12.0, 12.0, 12.0, 12.0]; $bins = $this->calculator->calculateDistribution($values); // All in bin [10-12.5[ self::assertSame([0, 0, 0, 0, 5, 0, 0, 0], $bins); } // --- Trend Line edge cases --- #[Test] public function trendLineWithTwoPointsExact(): void { $points = [[1, 10.0], [2, 14.0]]; $result = $this->calculator->calculateTrendLine($points); self::assertNotNull($result); self::assertEqualsWithDelta(4.0, $result->slope, 0.01); self::assertEqualsWithDelta(6.0, $result->intercept, 0.01); } #[Test] public function trendLineWithNoisyData(): void { // Noisy but overall increasing $points = [[1, 8.0], [2, 12.0], [3, 10.0], [4, 14.0], [5, 13.0]]; $result = $this->calculator->calculateTrendLine($points); self::assertNotNull($result); self::assertGreaterThan(0, $result->slope); } }