date('Y-m-d'), 'cost' => self::$dailyCost, 'updated_at' => date('Y-m-d H:i:s') ]; @file_put_contents(self::$dailyCostFile, json_encode($costData), LOCK_EX); } } private static function trackCost($usage) { if (!defined('ENABLE_COST_TRACKING') || !ENABLE_COST_TRACKING || !$usage) { return; } $promptTokens = $usage['prompt_tokens'] ?? 0; $completionTokens = $usage['completion_tokens'] ?? 0; $totalTokens = $usage['total_tokens'] ?? ($promptTokens + $completionTokens); // Calculate cost based on model - using safe constants $model = defined('GPT_MODEL') ? GPT_MODEL : 'gpt-4o-mini'; // Use appropriate pricing based on model if (strpos($model, 'gpt-4o-mini') !== false) { // Use gpt-4o-mini pricing if available if (defined('GPT4OMINI_COST_PER_1K_INPUT_TOKENS') && defined('GPT4OMINI_COST_PER_1K_OUTPUT_TOKENS')) { $inputCost = ($promptTokens / 1000) * GPT4OMINI_COST_PER_1K_INPUT_TOKENS; $outputCost = ($completionTokens / 1000) * GPT4OMINI_COST_PER_1K_OUTPUT_TOKENS; $requestCost = $inputCost + $outputCost; } else { // Fallback pricing for gpt-4o-mini $requestCost = ($totalTokens / 1000) * 0.000375; // Average of input/output } } elseif (strpos($model, 'gpt-3.5') !== false) { $costPer1kTokens = defined('GPT35_COST_PER_1K_TOKENS') ? GPT35_COST_PER_1K_TOKENS : 0.002; $requestCost = ($totalTokens / 1000) * $costPer1kTokens; } elseif (strpos($model, 'gpt-4') !== false) { $costPer1kTokens = defined('GPT4_COST_PER_1K_TOKENS') ? GPT4_COST_PER_1K_TOKENS : 0.030; $requestCost = ($totalTokens / 1000) * $costPer1kTokens; } else { // Default to gpt-4o-mini pricing $requestCost = ($totalTokens / 1000) * 0.000375; } self::$dailyCost += $requestCost; self::costLog("Request cost: $" . number_format($requestCost, 4) . " (Tokens: $totalTokens, Model: $model, Daily total: $" . number_format(self::$dailyCost, 4) . ")"); self::saveDailyCost(); return $requestCost; } private static function loadCache() { if (file_exists(self::$cacheFile)) { $cached = @json_decode(file_get_contents(self::$cacheFile), true); if (is_array($cached)) { self::$combinationCache = $cached; self::debugLog("Loaded " . count(self::$combinationCache) . " cached combinations"); } } } private static function saveCache() { if (defined('ENABLE_COMBINATION_CACHE') && ENABLE_COMBINATION_CACHE) { $maxEntries = defined('MAX_CACHE_ENTRIES') ? MAX_CACHE_ENTRIES : 50000; // Limit cache size if (count(self::$combinationCache) > $maxEntries) { self::$combinationCache = array_slice(self::$combinationCache, -$maxEntries, null, true); } @file_put_contents(self::$cacheFile, json_encode(self::$combinationCache), LOCK_EX); } } private static function enforceRateLimit($isOptimAIze = false) { self::init(); // Use OptimAIze-specific limits if applicable $requestsPerMinute = $isOptimAIze && defined('OPTIMAIZE_REQUESTS_PER_MINUTE') ? OPTIMAIZE_REQUESTS_PER_MINUTE : (defined('GPT_REQUESTS_PER_MINUTE') ? GPT_REQUESTS_PER_MINUTE : 60); $currentTime = time(); // Reset window if a minute has passed if ($currentTime - self::$windowStartTime >= 60) { self::$windowStartTime = $currentTime; self::$requestCount = 0; self::debugLog("Rate limit window reset. New window started. Requests per minute limit: $requestsPerMinute"); } // Check if we've hit the rate limit if (self::$requestCount >= $requestsPerMinute) { $waitTime = 60 - ($currentTime - self::$windowStartTime) + 5; // Add 5 second buffer self::debugLog("Rate limit reached ($requestsPerMinute/$requestsPerMinute). Waiting $waitTime seconds..."); sleep($waitTime); // Reset after waiting self::$windowStartTime = time(); self::$requestCount = 0; } // Ensure minimum time between requests $minInterval = 60 / $requestsPerMinute; $timeSinceLastRequest = $currentTime - self::$lastRequestTime; if ($timeSinceLastRequest < $minInterval) { $waitTime = ceil($minInterval - $timeSinceLastRequest); self::debugLog("Enforcing minimum interval: waiting $waitTime seconds"); sleep($waitTime); } self::$lastRequestTime = time(); self::$requestCount++; self::debugLog("Request #" . self::$requestCount . " in current window"); } public static function getRateLimitStatus($isOptimAIze = false) { self::init(); $requestsPerMinute = $isOptimAIze && defined('OPTIMAIZE_REQUESTS_PER_MINUTE') ? OPTIMAIZE_REQUESTS_PER_MINUTE : (defined('GPT_REQUESTS_PER_MINUTE') ? GPT_REQUESTS_PER_MINUTE : 60); $currentTime = time(); // Reset window if a minute has passed if ($currentTime - self::$windowStartTime >= 60) { self::$windowStartTime = $currentTime; self::$requestCount = 0; } $canMakeRequest = self::$requestCount < $requestsPerMinute; $cooldownRemaining = $canMakeRequest ? 0 : (60 - ($currentTime - self::$windowStartTime)); return [ 'can_make_request' => $canMakeRequest, 'requests_made' => self::$requestCount, 'requests_limit' => $requestsPerMinute, 'cooldown_remaining' => $cooldownRemaining, 'window_start' => self::$windowStartTime, 'rate_limit_hits' => self::$rateLimitHits, 'daily_cost' => self::$dailyCost, 'cost_limit' => defined('MAX_DAILY_COST') ? MAX_DAILY_COST : 0 ]; } public static function resetRateLimit() { self::$requestCount = 0; self::$windowStartTime = time(); self::$lastRequestTime = 0; self::$rateLimitHits = 0; self::debugLog("Rate limit counters reset"); } public static function makeRequest($messages, $model = null, $temperature = null, $isOptimAIze = false) { self::init(); // Check daily cost limit before making request if (defined('ENABLE_COST_TRACKING') && ENABLE_COST_TRACKING && defined('MAX_DAILY_COST') && self::$dailyCost >= MAX_DAILY_COST) { return [ 'success' => false, 'error' => 'Daily cost limit reached ($' . number_format(MAX_DAILY_COST, 2) . '). Processing stopped.', 'response' => null, 'cost_limited' => true ]; } $maxRetries = defined('GPT_MAX_RETRIES') ? GPT_MAX_RETRIES : 3; $retryDelay = defined('GPT_RETRY_DELAY') ? GPT_RETRY_DELAY : 15; for ($attempt = 1; $attempt <= $maxRetries; $attempt++) { try { // Enforce rate limiting before each attempt self::enforceRateLimit($isOptimAIze); // Use default values if not provided $model = $model ?? (defined('GPT_MODEL') ? GPT_MODEL : 'gpt-4o-mini'); $temperature = $temperature ?? (defined('GPT_TEMPERATURE') ? GPT_TEMPERATURE : 0.1); $data = [ 'model' => $model, 'messages' => $messages, 'max_tokens' => defined('GPT_MAX_TOKENS') ? GPT_MAX_TOKENS : 150, 'temperature' => $temperature, 'top_p' => 1, 'frequency_penalty' => 0, 'presence_penalty' => 0 ]; self::debugLog("Making OpenAI API request (attempt $attempt/$maxRetries, model: $model)"); $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => defined('GPT_API_ENDPOINT') ? GPT_API_ENDPOINT : 'https://api.openai.com/v1/chat/completions', CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($data), CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Authorization: Bearer ' . (defined('OPENAI_API_KEY') ? OPENAI_API_KEY : '') ], CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_SSL_VERIFYPEER => true, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1 ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlError = curl_error($ch); curl_close($ch); if ($curlError) { throw new Exception("cURL error: $curlError"); } $responseData = json_decode($response, true); if ($httpCode === 200 && isset($responseData['choices'][0]['message']['content'])) { $responseText = trim($responseData['choices'][0]['message']['content']); // Track cost if usage data is available if (isset($responseData['usage'])) { self::trackCost($responseData['usage']); } self::debugLog("API request successful (attempt $attempt)"); return [ 'success' => true, 'response' => $responseText, 'model_used' => $model, 'daily_cost' => self::$dailyCost, 'usage' => $responseData['usage'] ?? null ]; } elseif ($httpCode === 429) { self::$rateLimitHits++; $retryAfter = 60; // Default wait time if (isset($responseData['error']['message']) && preg_match('/retry after (\d+)/', $responseData['error']['message'], $matches)) { $retryAfter = (int)$matches[1]; } self::debugLog("Rate limit hit (attempt $attempt). Waiting $retryAfter seconds..."); if ($attempt < $maxRetries) { sleep($retryAfter); continue; } else { return [ 'success' => false, 'error' => 'Rate limit exceeded. Please try again later.', 'response' => null ]; } } else { $errorMessage = isset($responseData['error']['message']) ? $responseData['error']['message'] : "HTTP $httpCode: Unknown error"; self::debugLog("API error (attempt $attempt): $errorMessage"); if ($attempt < $maxRetries) { sleep($retryDelay); continue; } else { return [ 'success' => false, 'error' => $errorMessage, 'response' => null ]; } } } catch (Exception $e) { self::debugLog("Exception (attempt $attempt): " . $e->getMessage()); if ($attempt < $maxRetries) { sleep($retryDelay); continue; } else { return [ 'success' => false, 'error' => $e->getMessage(), 'response' => null ]; } } } return [ 'success' => false, 'error' => 'Max retries exceeded', 'response' => null ]; } public static function analyzeCombination($attr1, $choice1, $attr2, $choice2) { self::init(); // Check cache first $cacheKey = strtolower("$attr1|$choice1|$attr2|$choice2"); if (defined('ENABLE_COMBINATION_CACHE') && ENABLE_COMBINATION_CACHE && isset(self::$combinationCache[$cacheKey])) { $cached = self::$combinationCache[$cacheKey]; // Check cache expiry $expiryHours = defined('CACHE_EXPIRY_HOURS') ? CACHE_EXPIRY_HOURS : 24; if (isset($cached['timestamp']) && (time() - $cached['timestamp']) < ($expiryHours * 3600)) { self::debugLog("Cache hit: $attr1=$choice1 + $attr2=$choice2"); return [ 'success' => true, 'is_impossible' => $cached['is_impossible'], 'reasoning' => $cached['reasoning'], 'cached' => true, 'model_used' => $cached['model'] ?? 'cached', 'daily_cost' => self::$dailyCost ]; } } // Make API request for analysis $prompt = "Analyze this demographic combination for an Indian population:\n\n"; $prompt .= "Combination: $attr1 = '$choice1' AND $attr2 = '$choice2'\n\n"; $prompt .= "Consider:\n"; $prompt .= "- Cultural norms and social structures in India\n"; $prompt .= "- Economic realities and demographics\n"; $prompt .= "- Geographic and social factors\n"; $prompt .= "- Statistical likelihood\n\n"; $prompt .= "Respond with either 'POSSIBLE' or 'IMPOSSIBLE' followed by a brief reason (max 50 words)."; $messages = [ ['role' => 'system', 'content' => 'You are a demographic analysis expert specializing in Indian population statistics. Analyze combinations for realism.'], ['role' => 'user', 'content' => $prompt] ]; $response = self::makeRequest($messages, null, 0.1, true); if ($response['success']) { $result = trim($response['response']); $isImpossible = (stripos($result, 'IMPOSSIBLE') === 0) ? 1 : 0; $reasoning = substr($result, strpos($result, ' ') + 1); // Cache the result if (defined('ENABLE_COMBINATION_CACHE') && ENABLE_COMBINATION_CACHE) { self::$combinationCache[$cacheKey] = [ 'is_impossible' => $isImpossible, 'reasoning' => $reasoning, 'timestamp' => time(), 'model' => $response['model_used'] ?? 'unknown' ]; self::saveCache(); self::debugLog("Cached: $attr1=$choice1 + $attr2=$choice2 => " . ($isImpossible ? 'IMPOSSIBLE' : 'POSSIBLE') . " (Future cost savings!)"); } return [ 'success' => true, 'is_impossible' => $isImpossible, 'reasoning' => $reasoning, 'cached' => false, 'model_used' => $response['model_used'] ?? 'unknown', 'daily_cost' => self::$dailyCost ]; } return [ 'success' => false, 'error' => $response['error'], 'is_impossible' => null, 'reasoning' => null ]; } public static function getCacheStats() { self::init(); $total = count(self::$combinationCache); $impossible = 0; $possible = 0; foreach (self::$combinationCache as $entry) { if (isset($entry['is_impossible']) && $entry['is_impossible']) { $impossible++; } else { $possible++; } } // Calculate cost savings from cache using correct constants $avgInputTokens = defined('AVERAGE_INPUT_TOKENS_PER_REQUEST') ? AVERAGE_INPUT_TOKENS_PER_REQUEST : 150; $avgOutputTokens = defined('AVERAGE_OUTPUT_TOKENS_PER_REQUEST') ? AVERAGE_OUTPUT_TOKENS_PER_REQUEST : 50; // Use gpt-4o-mini pricing if available, otherwise fallback if (defined('GPT4OMINI_COST_PER_1K_INPUT_TOKENS') && defined('GPT4OMINI_COST_PER_1K_OUTPUT_TOKENS')) { $inputCost = ($avgInputTokens / 1000) * GPT4OMINI_COST_PER_1K_INPUT_TOKENS; $outputCost = ($avgOutputTokens / 1000) * GPT4OMINI_COST_PER_1K_OUTPUT_TOKENS; $avgCostPerRequest = $inputCost + $outputCost; } elseif (defined('GPT35_COST_PER_1K_TOKENS')) { $avgCostPerRequest = (($avgInputTokens + $avgOutputTokens) / 1000) * GPT35_COST_PER_1K_TOKENS; } else { // Fallback to gpt-4o-mini pricing $avgCostPerRequest = (($avgInputTokens / 1000) * 0.000150) + (($avgOutputTokens / 1000) * 0.000600); } $costSavedByCache = $total * $avgCostPerRequest; return [ 'total_cached' => $total, 'impossible_cached' => $impossible, 'possible_cached' => $possible, 'cache_file' => self::$cacheFile, 'cache_size_kb' => file_exists(self::$cacheFile) ? round(filesize(self::$cacheFile) / 1024, 2) : 0, 'rate_limit_hits' => self::$rateLimitHits, 'daily_cost' => self::$dailyCost, 'cost_saved_by_cache' => $costSavedByCache, 'cost_limit' => defined('MAX_DAILY_COST') ? MAX_DAILY_COST : 0 ]; } } ?>