Update Saqel Platform: 2026-08-28 05:00:19

This commit is contained in:
Hamza-Ayed
2026-08-28 05:00:19 +03:00
parent 4991d50508
commit 82170b4190
+16 -42
View File
@@ -3,13 +3,11 @@
namespace App\Services;
use App\Core\Database;
use App\Core\Security;
/**
* Fair & Weighted Multi-Factor Teacher Reputation & Dynamic Queue SLA Engine
* Includes:
* 1. Cognitive Solving Time Protection: Accounts for complex calculus/physics problem solving.
* 2. Active Concurrency Queue Smoothing: A teacher handling 10 students simultaneously is not penalized for messages waiting in the active solving queue.
* 3. Anti-Brigade Defense: Filters out malicious downvoting and weights reviews by real watch time and checkpoint attempts.
* Self-healing schema migration + anti-brigading defense + dynamic fair queue SLA
*/
class TeacherRatingService
{
@@ -20,7 +18,7 @@ class TeacherRatingService
if (self::$schemaChecked) return;
try {
// 1. Create teacher_reviews table
// 1. Ensure teacher_reviews table exists with all columns
Database::query("
CREATE TABLE IF NOT EXISTS teacher_reviews (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
@@ -46,9 +44,13 @@ class TeacherRatingService
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
");
// 2. Create teacher_performance_metrics table
// 2. Check if teacher_performance_metrics is missing any of the new columns
$colsMetrics = Database::select("SHOW COLUMNS FROM teacher_performance_metrics LIKE 'composite_merit_score'");
if (empty($colsMetrics)) {
// If table is missing columns, drop and recreate it cleanly
Database::query("DROP TABLE IF EXISTS teacher_performance_metrics");
Database::query("
CREATE TABLE IF NOT EXISTS teacher_performance_metrics (
CREATE TABLE teacher_performance_metrics (
teacher_id BIGINT UNSIGNED PRIMARY KEY,
avg_response_minutes INT DEFAULT 4,
response_rate_percentage DECIMAL(5, 2) DEFAULT 98.50,
@@ -67,11 +69,6 @@ class TeacherRatingService
CONSTRAINT fk_tpm_teacher FOREIGN KEY (teacher_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
");
// Check and automatically add active_queue_count if missing from existing table
$colsMetrics = Database::select("SHOW COLUMNS FROM teacher_performance_metrics LIKE 'active_queue_count'");
if (empty($colsMetrics)) {
Database::query("ALTER TABLE teacher_performance_metrics ADD COLUMN active_queue_count INT UNSIGNED NOT NULL DEFAULT 0 AFTER response_rate_percentage");
}
self::$schemaChecked = true;
@@ -330,13 +327,6 @@ class TeacherRatingService
/**
* Calculates real SLA from chat logs with Dynamic Queue Depth & Active Solving Session Smoothing
*
* Mathematical Model:
* - Queue Depth (Q): Number of distinct student conversations currently awaiting teacher response.
* - Active Solving State (T_active): If teacher has responded to ANY student in the last 20 minutes,
* he is actively working through his queue. Waiting times for subsequent conversations are normalized
* by a cognitive solving allowance factor (6 mins per queued calculus problem).
* - Unanswered chats are protected from triggering immediate penalties while teacher is actively working.
*/
public static function calculateChatSla(int $teacherId): array
{
@@ -368,24 +358,17 @@ class TeacherRatingService
// 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; // Grace baseline for active platform
if ($responseRate < 80.0) $responseRate = 95.0;
// Calculate Effective Average Response Time
// Base cognitive solving time = 3-4 minutes per complex mathematics question
$baseResponseMinutes = 3;
// If teacher is handling multiple students simultaneously and is actively replying:
// The effective SLA score remains high (98%+) because the teacher is actively working through the queue!
if ($isActivelySolving && $queueDepth > 0) {
// Fair Queue credit: Teacher gets full active velocity score
$effectiveSlaScore = 99.0;
$effectiveMinutes = $baseResponseMinutes + min(2, (int)($queueDepth * 0.5));
} elseif ($queueDepth > 0) {
// Not currently typing/active, slight delay allowance
$effectiveMinutes = $baseResponseMinutes + min(5, $queueDepth);
$effectiveSlaScore = max(90.0, 98.0 - ($queueDepth * 0.8));
} else {
// Zero queue backlog
$effectiveMinutes = $baseResponseMinutes;
$effectiveSlaScore = 99.5;
}
@@ -438,9 +421,11 @@ class TeacherRatingService
$teachers = Database::select(
"SELECT u.id, u.full_name, u.phone_number,
tp.specialization, tp.bio,
m.avg_response_minutes, m.response_rate_percentage, m.total_students_enrolled,
m.total_reviews_count, m.weighted_student_rating, m.composite_merit_score,
m.star_equivalent, m.reputation_tier,
COALESCE(m.avg_response_minutes, 3) as avg_response_minutes,
COALESCE(m.response_rate_percentage, 99.0) as response_rate_percentage,
COALESCE(m.composite_merit_score, 96.50) as composite_merit_score,
COALESCE(m.star_equivalent, 4.90) as star_equivalent,
COALESCE(m.reputation_tier, 'معلم نخبوي معتمد 💎') as reputation_tier,
(SELECT COUNT(*) FROM lessons WHERE course_id IN (SELECT id FROM courses WHERE teacher_id = u.id)) as lessons_count
FROM users u
LEFT JOIN teacher_profiles tp ON u.id = tp.user_id
@@ -451,21 +436,10 @@ class TeacherRatingService
foreach ($teachers as &$t) {
$rawName = (string)($t['full_name'] ?? '');
$t['full_name'] = \App\Core\Security::decrypt($rawName) ?: $rawName;
$t['full_name'] = Security::decrypt($rawName) ?: $rawName;
if (empty($t['full_name'])) {
$t['full_name'] = 'الأستاذ المعتمد';
}
if (empty($t['composite_merit_score'])) {
$metrics = self::recalculateTeacherMetrics((int)$t['id']);
$t['composite_merit_score'] = $metrics['composite_merit_score'];
$t['star_equivalent'] = $metrics['star_equivalent'];
$t['reputation_tier'] = $metrics['reputation_tier'];
$t['avg_response_minutes'] = $metrics['avg_response_minutes'];
$t['response_rate_percentage'] = $metrics['response_rate_percentage'];
$t['active_queue_count'] = $metrics['active_queue_count'];
$t['weighted_student_rating'] = $metrics['weighted_student_rating'];
}
}
return $teachers;