isLoggedIn()) { http_response_code(401); echo json_encode(['success' => false, 'message' => 'Unauthorized']); exit; } $db = Database::getInstance(); $currentUser = $auth->getCurrentUser(); try { // Get total panel members count $panelCountResult = $db->query("SELECT COUNT(*) as count FROM panel_data"); $totalPanelists = $panelCountResult ? $panelCountResult->fetch_assoc()['count'] : 0; // If no panel data, alignment score is 0% if ($totalPanelists == 0) { $alignment_score = 0; $message = 'No panel data available'; } else { // Calculate actual percentages for each statistic combination first updateActualPercentages($db, $totalPanelists); // Now calculate alignment score using Root Mean Square Error (RMSE) method $alignmentResult = calculateStatisticAlignmentScoreWithStats($db); $alignment_score = $alignmentResult['score']; $rmse = $alignmentResult['rmse']; $combinationsCount = $alignmentResult['combinations_count']; $message = "RMS-based alignment calculated from {$combinationsCount} combinations (RMSE: " . number_format($rmse, 3) . ")"; } // Cache the result in session $_SESSION['cached_alignment_score'] = $alignment_score; $_SESSION['cached_alignment_timestamp'] = time(); // Format timestamp $calculated_time = new DateTime(); $calculated_time->setTimezone(new DateTimeZone('Asia/Kolkata')); $formatted_time = $calculated_time->format('M d, Y H:i'); echo json_encode([ 'success' => true, 'score' => number_format($alignment_score, 2), 'total_panelists' => $totalPanelists, 'calculated_at' => $formatted_time, 'calculated_by' => $currentUser['full_name'], 'message' => $message ]); } catch (Exception $e) { error_log("Error calculating panel alignment score: " . $e->getMessage()); echo json_encode([ 'success' => false, 'message' => 'Failed to calculate alignment score: ' . $e->getMessage() ]); } /** * Update actual percentages for all statistic combinations */ function updateActualPercentages($db, $totalPanelists) { // Get all statistic combinations $combinationsQuery = $db->query(" SELECT sc.id, sc.combination_values, GROUP_CONCAT(sa.attribute_id ORDER BY sa.id) as attribute_ids FROM statistic_combinations sc JOIN statistic_attributes sa ON sc.statistic_id = sa.statistic_id WHERE sc.percentage > 0 GROUP BY sc.id "); if (!$combinationsQuery) { return; } while ($combination = $combinationsQuery->fetch_assoc()) { $combinationId = $combination['id']; $combinationValues = json_decode($combination['combination_values'], true); $attributeIds = explode(',', $combination['attribute_ids']); if (!is_array($combinationValues) || count($combinationValues) != count($attributeIds)) { continue; } // Build query to count panel members matching this combination $whereConditions = []; $params = []; $types = ''; for ($i = 0; $i < count($attributeIds); $i++) { $attrId = trim($attributeIds[$i]); $value = $combinationValues[$i]; $whereConditions[] = "JSON_EXTRACT(attribute_values, '$.\"$attrId\"') = ?"; $params[] = $value; $types .= 's'; } if (!empty($whereConditions)) { $whereClause = implode(' AND ', $whereConditions); $countQuery = "SELECT COUNT(*) as count FROM panel_data WHERE $whereClause"; $stmt = $db->prepare($countQuery); if (!empty($params)) { $stmt->bind_param($types, ...$params); } $stmt->execute(); $result = $stmt->get_result(); $actualCount = $result->fetch_assoc()['count']; // Calculate actual percentage $actualPercentage = ($actualCount / $totalPanelists) * 100; // Update the statistic_combinations table $updateStmt = $db->prepare("UPDATE statistic_combinations SET actual_percentage = ? WHERE id = ?"); $updateStmt->bind_param('di', $actualPercentage, $combinationId); $updateStmt->execute(); $stmt->close(); $updateStmt->close(); } } } /** * Calculate alignment score using Root Mean Square (RMS) method * Based on statistic check data (Target % vs Panel % deviations) * Returns 100% when all targets match perfectly, 0% when completely misaligned */ function calculateStatisticAlignmentScore($db) { $result = calculateStatisticAlignmentScoreWithStats($db); return $result['score']; } /** * Enhanced RMS calculation that returns additional mathematical statistics */ function calculateStatisticAlignmentScoreWithStats($db) { // Get all combinations with their target and actual percentages $alignmentQuery = $db->query(" SELECT percentage as target_percentage, actual_percentage FROM statistic_combinations WHERE percentage > 0 AND actual_percentage IS NOT NULL "); if (!$alignmentQuery || $alignmentQuery->num_rows == 0) { // No statistical requirements = perfect alignment return [ 'score' => 100, 'rmse' => 0, 'combinations_count' => 0, 'mean_absolute_error' => 0 ]; } $squaredDeviations = []; $absoluteDeviations = []; $combinationCount = 0; // Step 1: Calculate squared deviations and absolute deviations for each combination while ($row = $alignmentQuery->fetch_assoc()) { $targetPercentage = floatval($row['target_percentage']); $actualPercentage = floatval($row['actual_percentage']); if ($targetPercentage <= 0) { continue; // Skip invalid targets } // Calculate deviation (actual - target) $deviation = $actualPercentage - $targetPercentage; // Square the deviation for RMSE calculation $squaredDeviation = $deviation * $deviation; $squaredDeviations[] = $squaredDeviation; // Absolute deviation for MAE calculation $absoluteDeviations[] = abs($deviation); $combinationCount++; } if ($combinationCount == 0) { return [ 'score' => 100, 'rmse' => 0, 'combinations_count' => 0, 'mean_absolute_error' => 0 ]; } // Step 2: Calculate Mean Squared Error (MSE) $meanSquaredError = array_sum($squaredDeviations) / $combinationCount; // Step 3: Calculate Root Mean Square Error (RMSE) $rmse = sqrt($meanSquaredError); // Step 4: Calculate Mean Absolute Error (MAE) for additional insight $mae = array_sum($absoluteDeviations) / $combinationCount; // Step 5: Convert RMSE to alignment score (0-100%) // SIMPLIFIED FORMULA AS REQUESTED: 100% - RMSE = Alignment Score // Perfect alignment: RMSE = 0 → Score = 100% // Poor alignment: Higher RMSE → Lower score $alignmentScore = max(0, 100 - $rmse); return [ 'score' => round($alignmentScore, 2), 'rmse' => $rmse, 'combinations_count' => $combinationCount, 'mean_absolute_error' => $mae ]; } /** * Get total count of statistic combinations for informational purposes */ function getTotalCombinationsCount($db) { $countResult = $db->query("SELECT COUNT(*) as count FROM statistic_combinations WHERE percentage > 0"); return $countResult ? $countResult->fetch_assoc()['count'] : 0; } ?>