fix financial issues

This commit is contained in:
root
2026-06-01 02:08:27 -04:00
parent 090cb88573
commit 6444b61416
56 changed files with 4196 additions and 1824 deletions
+148
View File
@@ -2357,6 +2357,7 @@ public function belowSixty()
's.id AS student_id',
's.firstname',
's.lastname',
's.age',
's.school_id',
'cs.class_section_name',
'LOWER(TRIM(ss.semester)) AS sem_key',
@@ -2389,6 +2390,7 @@ public function belowSixty()
'school_id' => $row['school_id'] ?? '',
'firstname' => $row['firstname'] ?? '',
'lastname' => $row['lastname'] ?? '',
'age' => $row['age'] ?? null,
'class_section_name' => $row['class_section_name'] ?? '',
'fall_score' => null,
'spring_score' => null,
@@ -3397,6 +3399,8 @@ public function allDecisions()
's.school_id',
's.firstname',
's.lastname',
's.gender',
'ss.class_section_id',
'cs.class_section_name',
'LOWER(TRIM(ss.semester)) AS sem_key',
'ss.semester_score',
@@ -3428,6 +3432,8 @@ public function allDecisions()
'school_id' => $sr['school_id'] ?? '',
'firstname' => $sr['firstname'] ?? '',
'lastname' => $sr['lastname'] ?? '',
'gender' => $sr['gender'] ?? '',
'class_section_id' => (int)($sr['class_section_id'] ?? 0),
'class_section_name' => $sr['class_section_name'] ?? '',
'fall_score' => null,
'spring_score' => null,
@@ -3513,6 +3519,8 @@ public function allDecisions()
'school_id' => $info['school_id'],
'firstname' => $info['firstname'],
'lastname' => $info['lastname'],
'gender' => $info['gender'] ?? '',
'class_section_id' => (int)($info['class_section_id'] ?? 0),
'class_section_name' => $info['class_section_name'],
'fall_score' => $fall,
'spring_score' => $spring,
@@ -3521,9 +3529,47 @@ public function allDecisions()
'source' => $source,
'notes' => $notes,
'saved' => isset($savedMap[$sid]),
'is_trophy' => false,
];
}
$rowsByClass = [];
foreach ($rows as $index => $row) {
$classSectionId = (int)($row['class_section_id'] ?? 0);
if ($classSectionId <= 0) {
continue;
}
$rowsByClass[$classSectionId][] = $index;
}
foreach ($rowsByClass as $classIndexes) {
$scores = [];
foreach ($classIndexes as $rowIndex) {
$yearScore = $rows[$rowIndex]['year_score'] ?? null;
if (is_numeric($yearScore)) {
$scores[] = (float)$yearScore;
}
}
$thresholdInfo = $this->calculateTrophyThreshold($scores, 75.0);
$threshold = $thresholdInfo['threshold'];
if ($threshold === null) {
continue;
}
foreach ($classIndexes as $rowIndex) {
$yearScore = $rows[$rowIndex]['year_score'] ?? null;
$rows[$rowIndex]['is_trophy'] = is_numeric($yearScore) && (float)$yearScore >= $threshold;
}
}
$generated = !empty($saved);
return view('grading/all_decisions', [
@@ -3696,6 +3742,108 @@ public function generateAllDecisions()
->with('status', "Decisions generated for {$savedCount} students.");
}
private function calculateTrophyThreshold(array $scores, float $percentile = 75.0): array
{
$scores = array_values(array_filter(
$scores,
static fn ($value): bool => is_numeric($value) && $value !== null
));
$scores = array_map('floatval', $scores);
sort($scores);
$count = count($scores);
if ($count === 0) {
return ['threshold' => null, 'winners' => 0, 'method' => 'empty'];
}
$minWinners = 3;
$maxWinners = max($minWinners, (int) floor($count * (1 - $percentile / 100)));
$threshold = $this->empiricalTrophyPercentile($scores, $percentile);
$winners = $this->countScoresAtOrAbove($scores, $threshold);
if ($winners < $minWinners) {
$target = min($minWinners, $count);
$descending = array_reverse($scores);
$threshold = $descending[$target - 1];
$winners = $this->countScoresAtOrAbove($scores, $threshold);
return ['threshold' => $threshold, 'winners' => $winners, 'method' => 'min3_reduced'];
}
if ($winners <= $maxWinners) {
return ['threshold' => $threshold, 'winners' => $winners, 'method' => 'empirical_percentile'];
}
$result = $this->capTrophyThresholdByRank($scores, $maxWinners);
if ($result['winners'] < $minWinners) {
$target = min($minWinners, $count);
$descending = array_reverse($scores);
$threshold = $descending[$target - 1];
$winners = $this->countScoresAtOrAbove($scores, $threshold);
return ['threshold' => $threshold, 'winners' => $winners, 'method' => 'min3_after_cap'];
}
return $result;
}
private function capTrophyThresholdByRank(array $sortedScores, int $max): array
{
$descending = array_reverse($sortedScores);
$threshold = $descending[$max - 1];
$winners = $this->countScoresAtOrAbove($sortedScores, $threshold);
if ($winners <= $max) {
return ['threshold' => $threshold, 'winners' => $winners, 'method' => 'capped_25pct'];
}
$uniqueHigherScores = array_values(array_unique(array_filter(
$sortedScores,
static fn ($score): bool => $score > $threshold
)));
sort($uniqueHigherScores);
foreach ($uniqueHigherScores as $candidate) {
$winnerCount = $this->countScoresAtOrAbove($sortedScores, $candidate);
if ($winnerCount <= $max) {
return ['threshold' => $candidate, 'winners' => $winnerCount, 'method' => 'capped_25pct'];
}
}
return ['threshold' => $sortedScores[0], 'winners' => count($sortedScores), 'method' => 'all_equal'];
}
private function empiricalTrophyPercentile(array $sortedScores, float $percentile): float
{
$count = count($sortedScores);
if ($count === 0) {
return 0.0;
}
$index = ($percentile / 100.0) * ($count - 1);
$lower = (int) floor($index);
$upper = (int) ceil($index);
if ($lower === $upper) {
return $sortedScores[$lower];
}
return $sortedScores[$lower] + ($index - $lower) * ($sortedScores[$upper] - $sortedScores[$lower]);
}
private function countScoresAtOrAbove(array $scores, float $threshold): int
{
return count(array_filter(
$scores,
static fn ($score): bool => $score >= $threshold
));
}
public function getScoreComment()
{
// Get all students for the current semester and school year