0, 'processed' => 0, 'success' => 0, 'error' => 0, 'total' => 0, 'status' => 'Started', 'completed' => false ]; file_put_contents($progress_file, json_encode($progress_data)); log_message("Progress file initialized"); // Reset GPT rate limits at the start - this is important GptHelper::resetRateLimit(); // Function to get a fresh database connection function getDbConnection() { // Use the same credentials as in your config file $conn = new mysqli('localhost', 'sridharmani', 'P@ssw0rd2016', 'syndia'); if ($conn->connect_error) { throw new Exception("Connection failed: " . $conn->connect_error); } $conn->set_charset("utf8mb4"); return $conn; } // Fetch all the data we need upfront to avoid multiple DB calls try { // Get selection details $db = getDbConnection(); $stmt = $db->prepare("SELECT s.*, p.id as project_id FROM selections s JOIN projects p ON s.project_id = p.id WHERE s.id = ?"); $stmt->bind_param('i', $selection_id); $stmt->execute(); $selection = $stmt->get_result()->fetch_assoc(); $db->close(); if (!$selection) { throw new Exception("Selection not found"); } // Get project's connected survey $db = getDbConnection(); $stmt = $db->prepare("SELECT s.id as survey_id FROM project_surveys ps JOIN surveys s ON ps.survey_id = s.id WHERE ps.project_id = ? LIMIT 1"); $stmt->bind_param('i', $selection['project_id']); $stmt->execute(); $connected_survey = $stmt->get_result()->fetch_assoc(); $db->close(); if (!$connected_survey) { throw new Exception("No survey connected to project"); } // Get survey questions $db = getDbConnection(); $stmt = $db->prepare("SELECT id, question_text, question_type, options, config FROM survey_questions WHERE survey_id = ? AND question_type NOT IN ('section_header', 'descriptive_text', 'page_break') ORDER BY question_order ASC"); $stmt->bind_param('i', $connected_survey['survey_id']); $stmt->execute(); $result = $stmt->get_result(); $questions = []; while ($question = $result->fetch_assoc()) { // Parse JSON fields if needed if (!empty($question['options']) && is_string($question['options'])) { $question['options'] = json_decode($question['options'], true); } if (!empty($question['config']) && is_string($question['config'])) { $question['config'] = json_decode($question['config'], true); } $questions[] = $question; } $db->close(); // Get selection members $db = getDbConnection(); $stmt = $db->prepare("SELECT sm.panelist_id, pd.attribute_values FROM selection_members sm LEFT JOIN panel_data pd ON sm.panelist_id = pd.panelist_id WHERE sm.selection_id = ? ORDER BY sm.id ASC"); $stmt->bind_param('i', $selection_id); $stmt->execute(); $result = $stmt->get_result(); $members = []; while ($member = $result->fetch_assoc()) { $members[] = $member; } $db->close(); // Get all attributes $db = getDbConnection(); $result = $db->query("SELECT id, name FROM attributes ORDER BY created_at ASC"); $attributes = []; while ($attr = $result->fetch_assoc()) { $attributes[$attr['id']] = $attr['name']; } $db->close(); // Clear existing responses $db = getDbConnection(); $stmt = $db->prepare("DELETE FROM synthetic_responses WHERE selection_id = ?"); $stmt->bind_param('i', $selection_id); $stmt->execute(); $db->close(); log_message("Cleared existing responses for selection"); $total_members = count($members); $progress_data['total'] = $total_members; file_put_contents($progress_file, json_encode($progress_data)); // Process each member $processed = 0; $success_count = 0; $error_count = 0; // Process members one by one foreach ($members as $index => $member) { log_message("Generating responses for member " . ($index + 1)); try { // Extract member attributes for context $attribute_values = json_decode($member['attribute_values'], true); $profile_data = []; foreach ($attribute_values as $attr_id => $value) { if (isset($attributes[$attr_id])) { $profile_data[$attributes[$attr_id]] = $value; } } // Convert to readable format for GPT $profile_text = "Respondent Profile:\n"; foreach ($profile_data as $attr => $value) { $profile_text .= "- $attr: $value\n"; } // Generate survey responses using GPT $prompt = "You are acting as a synthetic survey respondent with the following demographic profile:\n\n"; $prompt .= $profile_text . "\n"; $prompt .= "Please generate authentic and realistic responses for the following survey questions. Your responses should reflect the perspective of someone with the demographic profile above.\n\n"; foreach ($questions as $i => $question) { $prompt .= ($i+1) . ". " . $question['question_text'] . "\n"; // Add hints based on question type if ($question['question_type'] == 'single_choice' || $question['question_type'] == 'dropdown') { if (!empty($question['options'])) { $prompt .= " Options: " . implode(", ", $question['options']) . "\n"; $prompt .= " (Select one option)\n"; } } else if ($question['question_type'] == 'multiple_choice') { if (!empty($question['options'])) { $prompt .= " Options: " . implode(", ", $question['options']) . "\n"; $prompt .= " (Select one or more options, separate with commas)\n"; } } else if ($question['question_type'] == 'rating_scale') { $config = $question['config'] ?? []; $start = $config['start'] ?? 1; $end = $config['end'] ?? 5; $prompt .= " (Provide a rating between $start and $end)\n"; } } $prompt .= "\nFormat your response as numbered answers only, with one answer per line. Don't include any explanations, just the direct response for each question."; $responses = []; $used_gpt = false; $retry_count = 0; $max_retries = 3; // Try to use GPT with retry mechanism for rate limits while (!$used_gpt && $retry_count < $max_retries) { try { // Use GPT to generate responses $messages = [ ['role' => 'system', 'content' => 'You are a synthetic survey respondent. Generate realistic responses based on the demographic profile provided.'], ['role' => 'user', 'content' => $prompt] ]; $gpt_response = GptHelper::makeRequest($messages); log_message("GPT response received for member " . ($index + 1)); $response_lines = explode("\n", trim($gpt_response)); // Process response lines foreach ($response_lines as $i => $line) { // Strip line numbers if present (e.g., "1. Answer" -> "Answer") $responses[] = preg_replace('/^\d+\.\s*/', '', $line); } // Ensure we have enough responses while (count($responses) < count($questions)) { $responses[] = "No response provided"; } $used_gpt = true; } catch (Exception $e) { log_message("GPT error (attempt " . ($retry_count + 1) . "): " . $e->getMessage()); $retry_count++; if (strpos($e->getMessage(), 'rate limit') !== false) { // If rate limited, wait and retry log_message("Rate limited, waiting 20 seconds before retry"); sleep(20); // Wait 20 seconds before retry GptHelper::resetRateLimit(); // Reset rate limit tracker } else if ($retry_count >= $max_retries) { // If not rate limited and max retries reached, rethrow throw $e; } } } // If GPT failed after all retries, use fallback approach if (!$used_gpt) { log_message("All GPT attempts failed for member " . ($index + 1) . ", using fallback approach"); // Basic fallback responses foreach ($questions as $question) { $qtype = $question['question_type']; $qtext = $question['question_text']; if ($qtype == 'single_choice' || $qtype == 'dropdown') { $options = $question['options'] ?? []; $responses[] = !empty($options) ? $options[array_rand($options)] : "No option selected"; } else if ($qtype == 'multiple_choice') { $options = $question['options'] ?? []; if (!empty($options)) { $numSelections = rand(1, min(3, count($options))); $selectedIndices = array_rand($options, $numSelections); if (!is_array($selectedIndices)) { $selectedIndices = [$selectedIndices]; } $selections = []; foreach ($selectedIndices as $index) { $selections[] = $options[$index]; } $responses[] = implode(", ", $selections); } else { $responses[] = "No options selected"; } } else if ($qtype == 'rating_scale') { $config = $question['config'] ?? []; $min = $config['start'] ?? 1; $max = $config['end'] ?? 5; $responses[] = (string)rand($min, $max); } else { $responses[] = "Fallback response"; } } } // Insert all responses in a single transaction for efficiency $db = getDbConnection(); $db->begin_transaction(); $success = true; foreach ($questions as $q_index => $question) { $response = isset($responses[$q_index]) ? $responses[$q_index] : "No response"; // Use direct query to avoid bind_param reference issues $sql = sprintf( "INSERT INTO synthetic_responses (selection_id, panelist_id, question_id, response_text) VALUES (%d, '%s', %d, '%s')", $selection_id, $db->real_escape_string($member['panelist_id']), $question['id'], $db->real_escape_string($response) ); if (!$db->query($sql)) { $success = false; log_message("Failed to insert response: " . $db->error); break; } } if ($success) { $db->commit(); $success_count++; } else { $db->rollback(); $error_count++; } $db->close(); } catch (Exception $e) { log_message("Error generating responses: " . $e->getMessage()); $error_count++; } $processed++; $progress = ($processed / $total_members) * 100; // Update progress $progress_data = [ 'progress' => $progress, 'processed' => $processed, 'success' => $success_count, 'error' => $error_count, 'total' => $total_members, 'status' => "Generated responses for $processed of $total_members members", 'completed' => ($processed >= $total_members) ]; file_put_contents($progress_file, json_encode($progress_data)); log_message("Progress updated for member " . $processed); } // Final progress update $progress_data = [ 'progress' => 100, 'processed' => $total_members, 'success' => $success_count, 'error' => $error_count, 'total' => $total_members, 'status' => "Completed with $success_count successful and $error_count failed", 'completed' => true ]; file_put_contents($progress_file, json_encode($progress_data)); log_message("Test completed successfully"); log_message("Worker script finished"); } catch (Exception $e) { log_message("Fatal error: " . $e->getMessage()); $progress_data = [ 'progress' => 0, 'processed' => 0, 'success' => 0, 'error' => 1, 'total' => 0, 'status' => "Error: " . $e->getMessage(), 'completed' => true ]; file_put_contents($progress_file, json_encode($progress_data)); } ?>