isLoggedIn()) { http_response_code(401); echo json_encode(['success' => false, 'message' => 'Unauthorized']); exit; } $db = Database::getInstance(); $currentUser = $auth->getCurrentUser(); try { // Calculate alignment score $score = calculatePanelAlignmentScore($db); // Get total panelists count $count_result = $db->query("SELECT COUNT(*) as total FROM panel_data"); $total_panelists = $count_result ? $count_result->fetch_assoc()['total'] : 0; // Cache the result cachePanelAlignmentScore($db, $score, $total_panelists, $currentUser['id']); // 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($score, 2), 'total_panelists' => $total_panelists, 'calculated_at' => $formatted_time, 'calculated_by' => $currentUser['full_name'] ]); } catch (Exception $e) { error_log("Error calculating alignment score: " . $e->getMessage()); echo json_encode([ 'success' => false, 'message' => 'Failed to calculate alignment score: ' . $e->getMessage() ]); } function calculatePanelAlignmentScore($db) { try { // Get all attributes $attributes_result = $db->query("SELECT * FROM attributes ORDER BY id"); if (!$attributes_result) { throw new Exception("Failed to fetch attributes"); } $attributes = []; while ($attr = $attributes_result->fetch_assoc()) { $attributes[] = $attr; } if (empty($attributes)) { return 0.0; } // Get panel data $panel_result = $db->query("SELECT attribute_values FROM panel_data"); if (!$panel_result) { throw new Exception("Failed to fetch panel data"); } $panel_data = []; while ($row = $panel_result->fetch_assoc()) { $panel_data[] = json_decode($row['attribute_values'], true); } if (empty($panel_data)) { return 0.0; } // Calculate alignment score based on distribution balance $total_score = 0; $attribute_count = count($attributes); foreach ($attributes as $attribute) { $attr_id = $attribute['id']; $attr_score = calculateAttributeAlignment($panel_data, $attr_id, $attribute); $total_score += $attr_score; } return $attribute_count > 0 ? ($total_score / $attribute_count) : 0.0; } catch (Exception $e) { error_log("Error in calculatePanelAlignmentScore: " . $e->getMessage()); throw $e; } } function calculateAttributeAlignment($panel_data, $attr_id, $attribute) { try { $values = []; // Collect all values for this attribute foreach ($panel_data as $panelist) { if (isset($panelist[$attr_id])) { $value = $panelist[$attr_id]; // Handle multiple values (arrays) if (is_array($value)) { foreach ($value as $v) { if (!empty($v)) { $values[] = $v; } } } else if (!empty($value)) { $values[] = $value; } } } if (empty($values)) { return 0.0; } // Count frequency of each value $value_counts = array_count_values($values); $total_values = count($values); // Calculate distribution balance if ($attribute['type'] === 'multiple' || $attribute['type'] === 'single') { // For choice-based attributes, aim for even distribution $expected_options = json_decode($attribute['options'], true); if (is_array($expected_options) && !empty($expected_options)) { return calculateChoiceDistributionScore($value_counts, $expected_options, $total_values); } } // For other types, calculate variance-based score return calculateVarianceScore($value_counts, $total_values); } catch (Exception $e) { error_log("Error in calculateAttributeAlignment: " . $e->getMessage()); return 0.0; } } function calculateChoiceDistributionScore($value_counts, $expected_options, $total_values) { $expected_per_option = $total_values / count($expected_options); $variance_sum = 0; foreach ($expected_options as $option) { $actual_count = isset($value_counts[$option]) ? $value_counts[$option] : 0; $variance_sum += pow($actual_count - $expected_per_option, 2); } $variance = $variance_sum / count($expected_options); $max_possible_variance = pow($expected_per_option, 2) * (count($expected_options) - 1) + pow($total_values - $expected_per_option, 2); if ($max_possible_variance == 0) { return 100.0; } $alignment_score = max(0, (1 - ($variance / $max_possible_variance)) * 100); return $alignment_score; } function calculateVarianceScore($value_counts, $total_values) { if (count($value_counts) <= 1) { return 100.0; // Perfect score if only one unique value or no values } $mean = $total_values / count($value_counts); $variance_sum = 0; foreach ($value_counts as $count) { $variance_sum += pow($count - $mean, 2); } $variance = $variance_sum / count($value_counts); $max_possible_variance = pow($mean, 2) * (count($value_counts) - 1) + pow($total_values - $mean, 2); if ($max_possible_variance == 0) { return 100.0; } $alignment_score = max(0, (1 - ($variance / $max_possible_variance)) * 100); return $alignment_score; } function cachePanelAlignmentScore($db, $score, $total_panelists, $calculated_by) { try { // Create table if it doesn't exist $db->query(" CREATE TABLE IF NOT EXISTS panel_alignment_cache ( id INT AUTO_INCREMENT PRIMARY KEY, alignment_score DECIMAL(5,2) NOT NULL, total_panelists INT NOT NULL, calculated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, calculated_by INT, INDEX(calculated_at) ) "); // Clear old cache entries (keep only the latest 10) $db->query(" DELETE FROM panel_alignment_cache WHERE id NOT IN ( SELECT id FROM ( SELECT id FROM panel_alignment_cache ORDER BY calculated_at DESC LIMIT 10 ) as temp ) "); // Insert new cache entry $stmt = $db->prepare(" INSERT INTO panel_alignment_cache (alignment_score, total_panelists, calculated_by) VALUES (?, ?, ?) "); $stmt->bind_param('dii', $score, $total_panelists, $calculated_by); $stmt->execute(); } catch (Exception $e) { error_log("Error caching alignment score: " . $e->getMessage()); // Don't throw - caching failure shouldn't break the main function } } ?>