isLoggedIn()) { echo json_encode(['success' => false, 'message' => 'Unauthorized']); exit; } $db = Database::getInstance(); $action = $_POST['action'] ?? ''; $response = ['success' => false, 'message' => 'Invalid action']; switch ($action) { case 'calculate_optimal': $response = calculateOptimalCount(); break; case 'calculate_optimal_with_directives': $response = calculateOptimalCountWithDirectives(); break; case 'calculate_realistic_optimal_count': $response = calculateRealisticOptimalCount(); break; case 'generate_panel': $count = intval($_POST['count'] ?? 0); if ($count > 0) { $response = generatePanelData($count); } else { $response = ['success' => false, 'message' => 'Invalid count']; } break; case 'generate_panel_with_directives': $count = intval($_POST['count'] ?? 0); if ($count > 0) { $response = generatePanelDataWithDirectives($count); } else { $response = ['success' => false, 'message' => 'Invalid count']; } break; case 'align_panel_directives': $response = alignPanelDirectives(); break; case 'get_progress': $response = getProgress(); break; case 'delete_panelist': $panelistId = $_POST['panelist_id'] ?? ''; if ($panelistId) { $response = deletePanelist($panelistId); } else { $response = ['success' => false, 'message' => 'Invalid panelist ID']; } break; case 'get_alignment_score': $response = calculateAlignmentScore(); break; case 'get_rms_alignment_score': $response = calculateRMSAlignmentScore(); break; case 'delete_panel': $response = deletePanelData(); break; } echo json_encode($response); function calculateOptimalCountWithDirectives() { global $db; try { // Get current panel count $existingCountQuery = $db->query("SELECT COUNT(*) as count FROM panel_data"); $existingCount = $existingCountQuery ? $existingCountQuery->fetch_assoc()['count'] : 0; // Get all statistical combinations to find the optimal count needed $allPercentages = []; $combinationsQuery = $db->query(" SELECT sc.percentage, s.name as statistic_name FROM statistic_combinations sc JOIN statistics s ON sc.statistic_id = s.id WHERE sc.percentage > 0 ORDER BY sc.percentage ASC "); // If no statistics exist, use a default optimal count if (!$combinationsQuery || $combinationsQuery->num_rows === 0) { $defaultOptimalCount = 2000; // Default for cases with no statistics // Get count of approved impossible combinations (directives) $directiveQuery = $db->query(" SELECT COUNT(*) as count FROM panel_directives WHERE status = 'approved' AND is_impossible = 1 "); $directiveCount = $directiveQuery ? $directiveQuery->fetch_assoc()['count'] : 0; return [ 'success' => true, 'optimal_count' => $defaultOptimalCount, 'existing_count' => $existingCount, 'directive_count' => $directiveCount, 'total_combinations' => 0, 'base_optimal' => $defaultOptimalCount, 'message' => 'No statistics found, using default optimal count' ]; } $totalCombinations = 0; while ($combo = $combinationsQuery->fetch_assoc()) { $percentage = floatval($combo['percentage']); if ($percentage > 0) { $allPercentages[] = $percentage; $totalCombinations++; } } if ($totalCombinations === 0) { // Use default optimal count if no valid combinations $defaultOptimalCount = 2000; $directiveQuery = $db->query(" SELECT COUNT(*) as count FROM panel_directives WHERE status = 'approved' AND is_impossible = 1 "); $directiveCount = $directiveQuery ? $directiveQuery->fetch_assoc()['count'] : 0; return [ 'success' => true, 'optimal_count' => $defaultOptimalCount, 'existing_count' => $existingCount, 'directive_count' => $directiveCount, 'total_combinations' => 0, 'base_optimal' => $defaultOptimalCount, 'message' => 'No valid statistical combinations found, using default optimal count' ]; } // Calculate LCM-based optimal count considering all percentages $optimalCount = calculateLCMBasedOptimalCount($allPercentages); // Get count of approved impossible combinations (directives) $directiveQuery = $db->query(" SELECT COUNT(*) as count FROM panel_directives WHERE status = 'approved' AND is_impossible = 1 "); $directiveCount = $directiveQuery ? $directiveQuery->fetch_assoc()['count'] : 0; // Adjust optimal count for directives (add buffer for impossible combinations) if ($directiveCount > 0) { // Add 10% buffer for impossible combinations $directiveBuffer = ceil($optimalCount * 0.1); $optimalCount += $directiveBuffer; } // If we already have existing panel members, calculate additional needed $additionalNeeded = $optimalCount; if ($existingCount > 0) { // Calculate how many more we need to reach the next optimal multiple $nextOptimalTotal = ceil($existingCount / $optimalCount) * $optimalCount; $additionalNeeded = max(0, $nextOptimalTotal - $existingCount); // If we're already above optimal, suggest a reasonable additional amount if ($additionalNeeded === 0) { $additionalNeeded = $optimalCount; // One more full optimal set } } return [ 'success' => true, 'optimal_count' => $additionalNeeded, 'existing_count' => $existingCount, 'directive_count' => $directiveCount, 'total_combinations' => $totalCombinations, 'base_optimal' => $optimalCount ]; } catch (Exception $e) { error_log("Calculate optimal count with directives error: " . $e->getMessage()); return ['success' => false, 'message' => 'Error calculating optimal count: ' . $e->getMessage()]; } } function calculateLCMBasedOptimalCount($percentages) { try { // Convert percentages to fractions and find LCM of denominators $denominators = []; foreach ($percentages as $percentage) { // Convert percentage to fraction (e.g., 2.5% = 25/1000) $fraction = $percentage / 100; // Find the denominator when expressed as a reduced fraction $denominator = 1; $numerator = $fraction; // Convert to integer fraction $iterations = 0; while ($numerator != floor($numerator) && $iterations < 10) { $numerator *= 10; $denominator *= 10; $iterations++; } // Reduce the fraction $gcd_value = gcd($numerator, $denominator); if ($gcd_value > 0) { $denominator = $denominator / $gcd_value; } $denominators[] = $denominator; } if (empty($denominators)) { return 2000; // Default fallback } // Calculate LCM of all denominators $lcm = $denominators[0]; for ($i = 1; $i < count($denominators); $i++) { $lcm = lcm($lcm, $denominators[$i]); } // Ensure we have at least 1000 for statistical validity $optimalCount = max(1000, $lcm); // For very large LCMs, use a reasonable approximation if ($optimalCount > 50000) { $optimalCount = 10000; // Reasonable default for complex statistics } return $optimalCount; } catch (Exception $e) { error_log("Error in calculateLCMBasedOptimalCount: " . $e->getMessage()); return 2000; // Default fallback } } function gcd($a, $b) { $a = abs($a); $b = abs($b); if ($a == 0 || $b == 0) { return max($a, $b); } while ($b != 0) { $temp = $b; $b = $a % $b; $a = $temp; } return $a; } function lcm($a, $b) { if ($a == 0 || $b == 0) { return max($a, $b); } $gcd_value = gcd($a, $b); if ($gcd_value == 0) { return max($a, $b); } return abs(($a * $b) / $gcd_value); } function calculateOptimalCount() { global $db; try { // Get statistics $statistics = []; $stats_query = $db->query(" SELECT s.*, GROUP_CONCAT(sa.attribute_id) as attribute_ids, GROUP_CONCAT(a.name) as attribute_names FROM statistics s LEFT JOIN statistic_attributes sa ON s.id = sa.statistic_id LEFT JOIN attributes a ON sa.attribute_id = a.id GROUP BY s.id ORDER BY s.created_at ASC "); if (!$stats_query) { return ['success' => false, 'message' => 'No statistics available']; } $minCount = 1000; while ($stat = $stats_query->fetch_assoc()) { $combinations_query = $db->query(" SELECT combination_values, percentage FROM statistic_combinations WHERE statistic_id = " . $stat['id'] ); while ($combo = $combinations_query->fetch_assoc()) { $percentage = floatval($combo['percentage']); if ($percentage > 0) { $requiredCount = ceil(100 / $percentage) * 10; $minCount = max($minCount, $requiredCount); } } } return [ 'success' => true, 'optimal_count' => $minCount ]; } catch (Exception $e) { error_log("Calculate optimal count error: " . $e->getMessage()); return ['success' => false, 'message' => 'Error calculating optimal count']; } } function calculateRealisticOptimalCount() { global $db; try { // Get all statistical combinations and find the smallest percentage $smallestPercentage = 100; $totalCombinations = 0; $combinationsQuery = $db->query(" SELECT sc.percentage FROM statistic_combinations sc JOIN statistics s ON sc.statistic_id = s.id WHERE sc.percentage > 0 ORDER BY sc.percentage ASC "); if (!$combinationsQuery) { return ['success' => false, 'message' => 'No statistical combinations available']; } while ($combo = $combinationsQuery->fetch_assoc()) { $percentage = floatval($combo['percentage']); if ($percentage > 0) { $smallestPercentage = min($smallestPercentage, $percentage); $totalCombinations++; } } if ($totalCombinations === 0) { return ['success' => false, 'message' => 'No valid statistical combinations found']; } // For realistic Indian national statistics, we need enough samples // to ensure even the smallest percentage has adequate representation $minSamplesPerCombination = 100; // Minimum samples per combination for statistical validity // Calculate based on smallest percentage needing adequate representation $baseOptimalCount = ceil($minSamplesPerCombination / ($smallestPercentage / 100)); // For national-level statistics, multiply by a factor to ensure robustness // Indian population segments require larger samples for accuracy $nationalFactor = 10; // Factor for national-level accuracy $optimalCount = $baseOptimalCount * $nationalFactor; // Get count of approved impossible combinations (directives) $directiveQuery = $db->query(" SELECT COUNT(*) as count FROM panel_directives WHERE status = 'approved' AND is_impossible = 1 "); $directiveCount = $directiveQuery ? $directiveQuery->fetch_assoc()['count'] : 0; // Ensure minimum of 2000 for robust statistics $optimalCount = max(2000, $optimalCount); return [ 'success' => true, 'optimal_count' => $optimalCount, 'smallest_percentage' => $smallestPercentage, 'total_combinations' => $totalCombinations, 'directive_count' => $directiveCount, 'base_calculation' => $baseOptimalCount ]; } catch (Exception $e) { error_log("Calculate realistic optimal count error: " . $e->getMessage()); return ['success' => false, 'message' => 'Error calculating realistic optimal count: ' . $e->getMessage()]; } } function generatePanelDataWithDirectives($additionalCount) { global $db; try { // Get current panel count $existingCountQuery = $db->query("SELECT COUNT(*) as count FROM panel_data"); $existingCount = $existingCountQuery ? $existingCountQuery->fetch_assoc()['count'] : 0; // Start panel generation session $_SESSION['panel_generation_progress'] = 0; $_SESSION['panel_generation_status'] = 'Starting generation of additional members...'; $_SESSION['panel_generation_target'] = $additionalCount; // Get attributes $attributes = []; $attr_query = $db->query("SELECT * FROM attributes ORDER BY created_at ASC"); while ($attr = $attr_query->fetch_assoc()) { $attr['choices'] = json_decode($attr['choices'], true); $attributes[] = $attr; } if (empty($attributes)) { return ['success' => false, 'message' => 'No attributes available for generation']; } // Get impossible combinations from approved directives $impossibleCombinations = []; $directives_query = $db->query(" SELECT attribute1_id, attribute2_id, choice1, choice2 FROM panel_directives WHERE status = 'approved' AND is_impossible = 1 "); while ($directive = $directives_query->fetch_assoc()) { $key = $directive['attribute1_id'] . '_' . $directive['attribute2_id']; if (!isset($impossibleCombinations[$key])) { $impossibleCombinations[$key] = []; } $impossibleCombinations[$key][] = [ 'choice1' => $directive['choice1'], 'choice2' => $directive['choice2'] ]; } // Calculate starting panelist ID $maxIdQuery = $db->query("SELECT MAX(CAST(panelist_id AS UNSIGNED)) as max_id FROM panel_data"); $maxId = $maxIdQuery ? $maxIdQuery->fetch_assoc()['max_id'] : 0; $startingId = $maxId + 1; $generated = 0; $batchSize = 100; $totalBatches = ceil($additionalCount / $batchSize); for ($batch = 0; $batch < $totalBatches; $batch++) { $currentBatchSize = min($batchSize, $additionalCount - $generated); if (generateBatchWithDirectives($currentBatchSize, $startingId + $generated, $attributes, $impossibleCombinations)) { $generated += $currentBatchSize; $progress = (($batch + 1) / $totalBatches) * 100; $_SESSION['panel_generation_progress'] = $progress; $_SESSION['panel_generation_status'] = "Generated {$generated} additional panelists..."; } else { return ['success' => false, 'message' => "Failed to generate batch " . ($batch + 1)]; } } $_SESSION['panel_generation_progress'] = 100; $_SESSION['panel_generation_status'] = 'Complete!'; // Get final count $finalCountQuery = $db->query("SELECT COUNT(*) as count FROM panel_data"); $finalCount = $finalCountQuery ? $finalCountQuery->fetch_assoc()['count'] : 0; return [ 'success' => true, 'generated_count' => $generated, 'existing_count' => $existingCount, 'final_count' => $finalCount, 'message' => "Successfully generated {$generated} additional panel members (total now: {$finalCount})" ]; } catch (Exception $e) { error_log("Generate panel data with directives error: " . $e->getMessage()); return ['success' => false, 'message' => 'Error generating panel data: ' . $e->getMessage()]; } } function generateBatchWithDirectives($batchSize, $startingId, $attributes, $impossibleCombinations) { global $db; try { // Start transaction $db->query("START TRANSACTION"); $stmt = $db->prepare("INSERT INTO panel_data (panelist_id, attribute_values, created_by) VALUES (?, ?, ?)"); for ($i = 0; $i < $batchSize; $i++) { $panelistId = str_pad($startingId + $i, 6, '0', STR_PAD_LEFT); $attributeValues = []; $maxAttempts = 100; // Prevent infinite loops $attempts = 0; $validCombination = false; while (!$validCombination && $attempts < $maxAttempts) { $attributeValues = []; // Generate random values for each attribute foreach ($attributes as $attr) { if ($attr['choice_type'] === 'single') { $attributeValues[$attr['id']] = $attr['choices'][array_rand($attr['choices'])]; } elseif ($attr['choice_type'] === 'multiple') { // For multiple choice, randomly select 1-3 choices $numChoices = rand(1, min(3, count($attr['choices']))); $selectedChoices = array_rand($attr['choices'], $numChoices); if (!is_array($selectedChoices)) { $selectedChoices = [$selectedChoices]; } $values = []; foreach ($selectedChoices as $choiceIndex) { $values[] = $attr['choices'][$choiceIndex]; } $attributeValues[$attr['id']] = $values; } } // Check if this combination violates any impossible directives $validCombination = true; foreach ($impossibleCombinations as $key => $combinations) { list($attr1Id, $attr2Id) = explode('_', $key); $value1 = $attributeValues[$attr1Id] ?? null; $value2 = $attributeValues[$attr2Id] ?? null; if ($value1 && $value2) { // Handle array values for multiple choice $values1 = is_array($value1) ? $value1 : [$value1]; $values2 = is_array($value2) ? $value2 : [$value2]; foreach ($combinations as $impossibleCombo) { if (in_array($impossibleCombo['choice1'], $values1) && in_array($impossibleCombo['choice2'], $values2)) { $validCombination = false; break 2; // Break out of both loops } } } } $attempts++; } if (!$validCombination) { // If we couldn't find a valid combination, use the last generated one error_log("Warning: Could not find valid combination for panelist $panelistId after $maxAttempts attempts"); } $jsonValues = json_encode($attributeValues); $userId = $_SESSION['user_id']; $stmt->bind_param('ssi', $panelistId, $jsonValues, $userId); if (!$stmt->execute()) { throw new Exception("Failed to insert panelist $panelistId"); } } // Commit transaction $db->query("COMMIT"); return true; } catch (Exception $e) { // Rollback on error $db->query("ROLLBACK"); error_log("Generate batch with directives error: " . $e->getMessage()); return false; } } function generatePanelData($count) { global $db; try { // Start panel generation session $_SESSION['panel_generation_progress'] = 0; $_SESSION['panel_generation_status'] = 'Starting generation...'; $_SESSION['panel_generation_target'] = $count; // Get attributes $attributes = []; $attr_query = $db->query("SELECT * FROM attributes ORDER BY created_at ASC"); while ($attr = $attr_query->fetch_assoc()) { $attr['choices'] = json_decode($attr['choices'], true); $attributes[] = $attr; } if (empty($attributes)) { return ['success' => false, 'message' => 'No attributes available for generation']; } // Clear existing panel data $db->query("DELETE FROM panel_data"); $db->query("DELETE FROM panel_processing_status"); $generated = 0; $batchSize = 100; $totalBatches = ceil($count / $batchSize); for ($batch = 0; $batch < $totalBatches; $batch++) { $currentBatchSize = min($batchSize, $count - $generated); if (generateBatch($currentBatchSize, $generated + 1, $attributes)) { $generated += $currentBatchSize; $progress = (($batch + 1) / $totalBatches) * 100; $_SESSION['panel_generation_progress'] = $progress; $_SESSION['panel_generation_status'] = "Generated {$generated} panelists..."; } else { return ['success' => false, 'message' => "Failed to generate batch " . ($batch + 1)]; } } $_SESSION['panel_generation_progress'] = 100; $_SESSION['panel_generation_status'] = 'Complete!'; return [ 'success' => true, 'generated_count' => $generated, 'message' => "Successfully generated {$generated} panel members" ]; } catch (Exception $e) { error_log("Generate panel data error: " . $e->getMessage()); return ['success' => false, 'message' => 'Error generating panel data']; } } function generateBatch($batchSize, $startingId, $attributes) { global $db; try { // Start transaction $db->query("START TRANSACTION"); $stmt = $db->prepare("INSERT INTO panel_data (panelist_id, attribute_values, created_by) VALUES (?, ?, ?)"); for ($i = 0; $i < $batchSize; $i++) { $panelistId = str_pad($startingId + $i, 6, '0', STR_PAD_LEFT); $attributeValues = []; foreach ($attributes as $attr) { if ($attr['choice_type'] === 'single') { $attributeValues[$attr['id']] = $attr['choices'][array_rand($attr['choices'])]; } elseif ($attr['choice_type'] === 'multiple') { // For multiple choice, randomly select 1-3 choices $numChoices = rand(1, min(3, count($attr['choices']))); $selectedChoices = array_rand($attr['choices'], $numChoices); if (!is_array($selectedChoices)) { $selectedChoices = [$selectedChoices]; } $values = []; foreach ($selectedChoices as $choiceIndex) { $values[] = $attr['choices'][$choiceIndex]; } $attributeValues[$attr['id']] = $values; } } $jsonValues = json_encode($attributeValues); $userId = $_SESSION['user_id']; $stmt->bind_param('ssi', $panelistId, $jsonValues, $userId); if (!$stmt->execute()) { throw new Exception("Failed to insert panelist $panelistId"); } } // Commit transaction $db->query("COMMIT"); return true; } catch (Exception $e) { // Rollback on error $db->query("ROLLBACK"); error_log("Generate batch error: " . $e->getMessage()); return false; } } function alignPanelDirectives() { global $db; try { // Get all panel members $panelQuery = $db->query("SELECT panelist_id, attribute_values FROM panel_data"); if (!$panelQuery) { return ['success' => false, 'message' => 'Failed to query panel data']; } // Get impossible combinations from approved directives $impossibleCombinations = []; $directives_query = $db->query(" SELECT attribute1_id, attribute2_id, choice1, choice2 FROM panel_directives WHERE status = 'approved' AND is_impossible = 1 "); while ($directive = $directives_query->fetch_assoc()) { $key = $directive['attribute1_id'] . '_' . $directive['attribute2_id']; if (!isset($impossibleCombinations[$key])) { $impossibleCombinations[$key] = []; } $impossibleCombinations[$key][] = [ 'choice1' => $directive['choice1'], 'choice2' => $directive['choice2'] ]; } if (empty($impossibleCombinations)) { return ['success' => true, 'message' => 'No impossible combinations to align against', 'removed_count' => 0]; } $panelistsToRemove = []; // Check each panelist while ($panelist = $panelQuery->fetch_assoc()) { $attributeValues = json_decode($panelist['attribute_values'], true); $shouldRemove = false; // Check against impossible combinations foreach ($impossibleCombinations as $key => $combinations) { list($attr1Id, $attr2Id) = explode('_', $key); $value1 = $attributeValues[$attr1Id] ?? null; $value2 = $attributeValues[$attr2Id] ?? null; if ($value1 && $value2) { // Handle array values for multiple choice $values1 = is_array($value1) ? $value1 : [$value1]; $values2 = is_array($value2) ? $value2 : [$value2]; foreach ($combinations as $impossibleCombo) { if (in_array($impossibleCombo['choice1'], $values1) && in_array($impossibleCombo['choice2'], $values2)) { $shouldRemove = true; break 2; // Break out of both loops } } } } if ($shouldRemove) { $panelistsToRemove[] = $panelist['panelist_id']; } } // Remove panelists with impossible combinations $removedCount = 0; if (!empty($panelistsToRemove)) { $placeholders = str_repeat('?,', count($panelistsToRemove) - 1) . '?'; $stmt = $db->prepare("DELETE FROM panel_data WHERE panelist_id IN ($placeholders)"); $stmt->bind_param(str_repeat('s', count($panelistsToRemove)), ...$panelistsToRemove); if ($stmt->execute()) { $removedCount = $stmt->affected_rows; } } return [ 'success' => true, 'message' => "Panel alignment completed", 'removed_count' => $removedCount, 'checked_combinations' => count($impossibleCombinations) ]; } catch (Exception $e) { error_log("Align panel directives error: " . $e->getMessage()); return ['success' => false, 'message' => 'Error during alignment: ' . $e->getMessage()]; } } function getProgress() { return [ 'success' => true, 'progress' => $_SESSION['panel_generation_progress'] ?? 0, 'status' => $_SESSION['panel_generation_status'] ?? 'Ready', 'target' => $_SESSION['panel_generation_target'] ?? 0 ]; } function deletePanelist($panelistId) { global $db; try { $stmt = $db->prepare("DELETE FROM panel_data WHERE panelist_id = ?"); $stmt->bind_param('s', $panelistId); if ($stmt->execute()) { return ['success' => true, 'message' => 'Panelist deleted successfully']; } else { return ['success' => false, 'message' => 'Failed to delete panelist']; } } catch (Exception $e) { error_log("Delete panelist error: " . $e->getMessage()); return ['success' => false, 'message' => 'Error deleting panelist']; } } function calculateAlignmentScore() { global $db; try { // Implementation for alignment score calculation // This would involve comparing actual panel distribution with statistical requirements return [ 'success' => true, 'alignment_score' => 85.5, // Placeholder 'message' => 'Alignment score calculated' ]; } catch (Exception $e) { return ['success' => false, 'message' => 'Error calculating alignment score']; } } function calculateRMSAlignmentScore() { global $db; try { // Implementation for RMS alignment score calculation return [ 'success' => true, 'rms_score' => 12.3, // Placeholder 'message' => 'RMS alignment score calculated' ]; } catch (Exception $e) { return ['success' => false, 'message' => 'Error calculating RMS alignment score']; } } function deletePanelData() { global $db; try { $db->query("DELETE FROM panel_data"); $db->query("DELETE FROM panel_processing_status"); return [ 'success' => true, 'message' => 'All panel data deleted successfully' ]; } catch (Exception $e) { return ['success' => false, 'message' => 'Error deleting panel data']; } } ?>