Implement verified curriculum, video review, watch sessions, ledger safeguards, and remove demo data

This commit is contained in:
Hamza-Ayed
2026-09-09 17:04:08 +03:00
parent 0dc5df7b5e
commit 5af684a892
49 changed files with 1809 additions and 3005 deletions
+27 -18
View File
@@ -15,8 +15,11 @@ class TeacherRatingService
public static function ensureSchema(): void
{
// Schema is installed by migrations. Runtime DDL previously included a
// DROP TABLE path and must never run during a student request.
return;
/*
if (self::$schemaChecked) return;
try {
// 1. Ensure teacher_reviews table exists with all columns
Database::query("
@@ -75,6 +78,7 @@ class TeacherRatingService
} catch (\Throwable $e) {
error_log("TeacherRatingService schema note: " . $e->getMessage());
}
*/
}
/**
@@ -210,8 +214,8 @@ class TeacherRatingService
);
$totalReviews = count($reviews);
$weightedRating = 5.00;
$rawRating = 5.00;
$weightedRating = 0.00;
$rawRating = 0.00;
if ($totalReviews > 0) {
$sumWeighted = 0;
@@ -226,7 +230,7 @@ class TeacherRatingService
$sumRaw += $val;
}
$weightedRating = $sumWeights > 0 ? round($sumWeighted / $sumWeights, 2) : 5.00;
$weightedRating = $sumWeights > 0 ? round($sumWeighted / $sumWeights, 2) : 0.00;
$rawRating = round($sumRaw / $totalReviews, 2);
}
@@ -237,7 +241,9 @@ class TeacherRatingService
$masteryScore = self::calculateMasteryImpact($teacherId);
// 4. AI Socratic Engagement Index
$aiEngagementScore = 96.00;
// There is no verified AI engagement measurement yet. Keep the metric
// neutral until the reviewed video/watch pipeline supplies one.
$aiEngagementScore = 0.00;
// 5. Composite Merit Calculation (The Fair Formula: 25% SLA + 25% AI + 25% Mastery + 25% Student Review)
$reviewComponent = $weightedRating * 20.0;
@@ -266,7 +272,11 @@ class TeacherRatingService
}
// Count enrolled students
$studentsCount = (int)(Database::selectOne("SELECT COUNT(*) as cnt FROM students")['cnt'] ?? 0);
$studentsCount = (int)(Database::selectOne(
"SELECT COUNT(DISTINCT lp.student_id) AS cnt FROM lesson_progress lp
JOIN lessons l ON l.id=lp.lesson_id JOIN courses c ON c.id=l.course_id
WHERE c.teacher_id=?", [$teacherId]
)['cnt'] ?? 0);
// Upsert into teacher_performance_metrics
Database::query(
@@ -295,7 +305,7 @@ class TeacherRatingService
$slaData['avg_response_minutes'],
$slaData['response_rate_pct'],
$slaData['active_queue_count'],
max(1, $studentsCount),
$studentsCount,
$totalReviews,
$rawRating,
$weightedRating,
@@ -364,20 +374,19 @@ class TeacherRatingService
$totalReplies = (int)(Database::selectOne("SELECT COUNT(DISTINCT receiver_id) as cnt FROM chat_messages WHERE sender_id = ?", [$teacherId])['cnt'] ?? 0);
// Calculate Base Response Rate
$responseRate = $totalInquiries > 0 ? min(100.0, round(($totalReplies / $totalInquiries) * 100.0, 1)) : 99.0;
if ($responseRate < 80.0) $responseRate = 95.0;
$responseRate = $totalInquiries > 0 ? min(100.0, round(($totalReplies / $totalInquiries) * 100.0, 1)) : 0.0;
$baseResponseMinutes = 3;
$baseResponseMinutes = 0;
if ($isActivelySolving && $queueDepth > 0) {
$effectiveSlaScore = 99.0;
$effectiveSlaScore = 0.0;
$effectiveMinutes = $baseResponseMinutes + min(2, (int)($queueDepth * 0.5));
} elseif ($queueDepth > 0) {
$effectiveMinutes = $baseResponseMinutes + min(5, $queueDepth);
$effectiveSlaScore = max(90.0, 98.0 - ($queueDepth * 0.8));
$effectiveSlaScore = 0.0;
} else {
$effectiveMinutes = $baseResponseMinutes;
$effectiveSlaScore = 99.5;
$effectiveSlaScore = 0.0;
}
return [
@@ -393,11 +402,11 @@ class TeacherRatingService
}
return [
'avg_response_minutes' => 3,
'response_rate_pct' => 99.0,
'avg_response_minutes' => 0,
'response_rate_pct' => 0.0,
'active_queue_count' => 0,
'is_active_solving' => true,
'sla_score' => 98.0
'is_active_solving' => false,
'sla_score' => 0.0
];
}
@@ -436,7 +445,7 @@ class TeacherRatingService
FROM teachers t
LEFT JOIN teacher_performance_metrics m ON t.id = m.teacher_id
WHERE t.is_marketplace_public = 1
ORDER BY " . ($sortBy === 'fastest' ? "COALESCE(m.avg_response_minutes, 10) ASC" : "COALESCE(m.composite_merit_score, 90.00) DESC")
ORDER BY " . ($sortBy === 'fastest' ? "CASE WHEN m.avg_response_minutes IS NULL OR m.avg_response_minutes = 0 THEN 1 ELSE 0 END, m.avg_response_minutes ASC" : "COALESCE(m.total_reviews_count, 0) DESC, COALESCE(m.weighted_student_rating, 0) DESC")
);
foreach ($teachers as &$t) {