0 ? ($processed / $total) * 100 : 0; writeLog(sprintf("Progress: %d/%d (%.1f%%) | Rate: %.1f/min | ETA: %s", $processed, $total, $progress, $rate, $eta)); } try { writeLog("=== Enhanced OptimAIze Analysis Started ==="); writeLog("Batch Size: " . BATCH_SIZE); writeLog("Parallel Requests: " . PARALLEL_REQUESTS); writeLog("Rate Limit Delay: " . RATE_LIMIT_DELAY . " seconds"); $db = Database::getInstance(); writeLog("✅ Database connected"); // Reset GPT rate limits for faster processing GptHelper::resetRateLimit(); writeLog("✅ GPT rate limits reset"); $startTime = microtime(true); $batchCount = 0; $totalProcessed = 0; $successCount = 0; $errorCount = 0; while (true) { // Check if analysis should pause or stop $stateQuery = $db->query("SELECT is_running, should_pause FROM optimization_analysis_state ORDER BY id DESC LIMIT 1"); $state = $stateQuery ? $stateQuery->fetch_assoc() : null; if (!$state || !$state['is_running']) { writeLog("❌ Analysis not running - stopping"); break; } if ($state['should_pause']) { writeLog("⏸️ Pause requested - stopping gracefully"); break; } $batchCount++; $batchStartTime = microtime(true); // Get next batch of unprocessed combinations $query = $db->query(" SELECT id, attribute1_id, attribute2_id, choice1, choice2, attribute1_name, attribute2_name FROM panel_directives WHERE llm_checked = 0 ORDER BY id ASC LIMIT " . BATCH_SIZE ); if (!$query || $query->num_rows == 0) { writeLog("🎉 No more combinations to process - analysis complete!"); // Mark analysis as completed $db->query("UPDATE optimization_analysis_state SET is_running = 0, completed_at = NOW() WHERE is_running = 1"); break; } $combinations = []; while ($row = $query->fetch_assoc()) { $combinations[] = $row; } writeLog("📦 Batch #$batchCount: Processing " . count($combinations) . " combinations"); // Process combinations in parallel groups $batchResults = processCombinationsInParallel($combinations, $db); $batchSuccessCount = 0; $batchErrorCount = 0; foreach ($batchResults as $result) { if ($result['success']) { $batchSuccessCount++; $successCount++; } else { $batchErrorCount++; $errorCount++; } } $totalProcessed += count($combinations); $batchTime = microtime(true) - $batchStartTime; $batchRate = count($combinations) / ($batchTime / 60); // per minute writeLog("✅ Batch completed: {$batchSuccessCount} success, {$batchErrorCount} errors, {$batchRate:.1f} combinations/min"); // Update progress in database every few batches if ($batchCount % STATUS_UPDATE_INTERVAL == 0) { updateProgress($db, $totalProcessed); // Calculate overall statistics $totalTime = microtime(true) - $startTime; $overallRate = $totalProcessed / ($totalTime / 60); $remainingQuery = $db->query("SELECT COUNT(*) as count FROM panel_directives WHERE llm_checked = 0"); $remaining = $remainingQuery ? $remainingQuery->fetch_assoc()['count'] : 0; $eta = $remaining > 0 && $overallRate > 0 ? gmdate("H:i:s", ($remaining / $overallRate) * 60) : "Unknown"; writeProgressLog($totalProcessed, $totalProcessed + $remaining, $overallRate, $eta); } // Brief delay to prevent overwhelming the API if (RATE_LIMIT_DELAY > 0) { sleep(RATE_LIMIT_DELAY); } } // Final statistics $totalTime = microtime(true) - $startTime; $overallRate = $totalProcessed / ($totalTime / 60); writeLog("=== Analysis Complete ==="); writeLog("Total Processed: $totalProcessed"); writeLog("Success: $successCount"); writeLog("Errors: $errorCount"); writeLog("Total Time: " . gmdate("H:i:s", $totalTime)); writeLog("Average Rate: {$overallRate:.1f} combinations/minute"); // Final progress update updateProgress($db, $totalProcessed); } catch (Exception $e) { writeLog("❌ Fatal Error: " . $e->getMessage()); writeLog("Stack trace: " . $e->getTraceAsString()); // Mark analysis as stopped due to error if (isset($db)) { $db->query("UPDATE optimization_analysis_state SET is_running = 0, completed_at = NOW() WHERE is_running = 1"); } } /** * Process combinations in parallel for maximum speed */ function processCombinationsInParallel($combinations, $db) { $results = []; $chunks = array_chunk($combinations, PARALLEL_REQUESTS); foreach ($chunks as $chunk) { $chunkResults = processChunkInParallel($chunk, $db); $results = array_merge($results, $chunkResults); } return $results; } /** * Process a small chunk of combinations in parallel */ function processChunkInParallel($combinations, $db) { $results = []; $curlHandles = []; $multiHandle = curl_multi_init(); // Prepare all requests foreach ($combinations as $index => $combination) { $prompt = buildAnalysisPrompt($combination); $curlHandle = createGptCurlRequest($prompt); if ($curlHandle) { $curlHandles[$index] = [ 'handle' => $curlHandle, 'combination' => $combination ]; curl_multi_add_handle($multiHandle, $curlHandle); } } // Execute all requests in parallel $running = null; do { curl_multi_exec($multiHandle, $running); curl_multi_select($multiHandle); } while ($running > 0); // Process results foreach ($curlHandles as $index => $data) { $response = curl_multi_getcontent($data['handle']); $httpCode = curl_getinfo($data['handle'], CURLINFO_HTTP_CODE); $result = processGptResponse($response, $httpCode, $data['combination'], $db); $results[] = $result; curl_multi_remove_handle($multiHandle, $data['handle']); curl_close($data['handle']); } curl_multi_close($multiHandle); return $results; } /** * Create a cURL handle for GPT API request */ function createGptCurlRequest($prompt) { $data = [ 'model' => 'gpt-4', 'messages' => [ [ 'role' => 'system', 'content' => 'You are an expert demographic analyst. Analyze if the given combination of demographic attributes is logically possible or impossible in real-world scenarios. Respond with either "POSSIBLE" or "IMPOSSIBLE" followed by a brief reason.' ], [ 'role' => 'user', 'content' => $prompt ] ], 'max_tokens' => 150, 'temperature' => 0.1 ]; $ch = curl_init('https://api.openai.com/v1/chat/completions'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($data), CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . OPENAI_API_KEY, 'Content-Type: application/json' ], CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1 ]); return $ch; } /** * Build analysis prompt for a combination */ function buildAnalysisPrompt($combination) { return sprintf( "Analyze this demographic combination:\n\n%s = %s\n%s = %s\n\nIs this combination logically possible in real-world demographics? Consider biological, legal, social, and logical constraints.", $combination['attribute1_name'], $combination['choice1'], $combination['attribute2_name'], $combination['choice2'] ); } /** * Process GPT API response and update database */ function processGptResponse($response, $httpCode, $combination, $db) { try { if ($httpCode !== 200) { throw new Exception("HTTP Error: $httpCode"); } $data = json_decode($response, true); if (!$data || !isset($data['choices'][0]['message']['content'])) { throw new Exception("Invalid response format"); } $content = trim($data['choices'][0]['message']['content']); $isImpossible = stripos($content, 'IMPOSSIBLE') === 0 ? 1 : 0; // Extract reasoning (everything after POSSIBLE/IMPOSSIBLE) $reasoning = trim(str_ireplace(['POSSIBLE', 'IMPOSSIBLE'], '', $content)); if (empty($reasoning)) { $reasoning = $content; } // Update database $stmt = $db->prepare("UPDATE panel_directives SET llm_checked = 1, is_impossible = ?, llm_reasoning = ?, updated_at = NOW() WHERE id = ?"); $stmt->bind_param('isi', $isImpossible, $reasoning, $combination['id']); if (!$stmt->execute()) { throw new Exception("Database update failed"); } return [ 'success' => true, 'combination_id' => $combination['id'], 'is_impossible' => $isImpossible, 'reasoning' => $reasoning ]; } catch (Exception $e) { // Mark as checked but with error $stmt = $db->prepare("UPDATE panel_directives SET llm_checked = 1, is_impossible = 0, llm_reasoning = ?, updated_at = NOW() WHERE id = ?"); $errorMsg = "Error: " . $e->getMessage(); $stmt->bind_param('si', $errorMsg, $combination['id']); $stmt->execute(); return [ 'success' => false, 'combination_id' => $combination['id'], 'error' => $e->getMessage() ]; } } /** * Update progress in database */ function updateProgress($db, $processed) { try { $stmt = $db->prepare("UPDATE optimization_analysis_state SET processed_combinations = ?, last_updated = NOW() WHERE is_running = 1"); $stmt->bind_param('i', $processed); $stmt->execute(); } catch (Exception $e) { writeLog("Warning: Could not update progress: " . $e->getMessage()); } } ?>