From bf3bd6faea1b90acb706ed9f6127e01dc3509c1b Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Mon, 8 Dec 2025 22:18:04 +0000 Subject: [PATCH] Edit ai/LocalAIApi.php via Editor --- ai/LocalAIApi.php | 1076 +++++++++++++++++++++++++-------------------- 1 file changed, 600 insertions(+), 476 deletions(-) diff --git a/ai/LocalAIApi.php b/ai/LocalAIApi.php index d428248..e3c3d05 100644 --- a/ai/LocalAIApi.php +++ b/ai/LocalAIApi.php @@ -1,493 +1,617 @@ [ -// ['role' => 'system', 'content' => 'You are a helpful assistant.'], -// ['role' => 'user', 'content' => 'Tell me a bedtime story.'], -// ], -// ]); -// if (!empty($response['success'])) { -// // response['data'] contains full payload, e.g.: -// // { -// // "id": "resp_xxx", -// // "status": "completed", -// // "output": [ -// // {"type": "reasoning", "summary": []}, -// // {"type": "message", "content": [{"type": "output_text", "text": "Your final answer here."}]} -// // ] -// // } -// $decoded = LocalAIApi::decodeJsonFromResponse($response); // or inspect $response['data'] / extractText(...) -// } -// Poll settings override: -// LocalAIApi::createResponse($payload, ['poll_interval' => 5, 'poll_timeout' => 300]); +// ai/QuestionnaireProcessor.php +// Processes KI-Fit Check questionnaire with AI analysis -class LocalAIApi +require_once __DIR__ . '/LocalAIApi.php'; + +class QuestionnaireProcessor { - /** @var array|null */ - private static ?array $configCache = null; - - /** - * Signature compatible with the OpenAI Responses API. - * - * @param array $params Request body (model, input, text, reasoning, metadata, etc.). - * @param array $options Extra options (timeout, verify_tls, headers, path, project_uuid). - * @return array{ - * success:bool, - * status?:int, - * data?:mixed, - * error?:string, - * response?:mixed, - * message?:string - * } - */ - public static function createResponse(array $params, array $options = []): array + private array $questionnaireData; + private string $language; + + public function __construct(array $questionnaireData, string $language = 'de') { - $cfg = self::config(); - $payload = $params; - - if (empty($payload['input']) || !is_array($payload['input'])) { - return [ - 'success' => false, - 'error' => 'input_missing', - 'message' => 'Parameter "input" is required and must be an array.', - ]; - } - - if (!isset($payload['model']) || $payload['model'] === '') { - $payload['model'] = $cfg['default_model']; - } - - $initial = self::request($options['path'] ?? null, $payload, $options); - if (empty($initial['success'])) { - return $initial; - } - - // Async flow: if backend returns ai_request_id, poll status until ready - $data = $initial['data'] ?? null; - if (is_array($data) && isset($data['ai_request_id'])) { - $aiRequestId = $data['ai_request_id']; - $pollTimeout = isset($options['poll_timeout']) ? (int) $options['poll_timeout'] : 300; // seconds - $pollInterval = isset($options['poll_interval']) ? (int) $options['poll_interval'] : 5; // seconds - return self::awaitResponse($aiRequestId, [ - 'timeout' => $pollTimeout, - 'interval' => $pollInterval, - 'headers' => $options['headers'] ?? [], - 'timeout_per_call' => $options['timeout'] ?? null, - ]); - } - - return $initial; + $this->questionnaireData = $questionnaireData; + $this->language = $language; } - + /** - * Snake_case alias for createResponse (matches the provided example). - * - * @param array $params - * @param array $options - * @return array + * Process the complete questionnaire and generate AI analysis */ - public static function create_response(array $params, array $options = []): array + public function process(): array { - return self::createResponse($params, $options); - } - - /** - * Perform a raw request to the AI proxy. - * - * @param string $path Endpoint (may be an absolute URL). - * @param array $payload JSON payload. - * @param array $options Additional request options. - * @return array - */ - public static function request(?string $path = null, array $payload = [], array $options = []): array - { - $cfg = self::config(); - - $projectUuid = $cfg['project_uuid']; - if (empty($projectUuid)) { - return [ - 'success' => false, - 'error' => 'project_uuid_missing', - 'message' => 'PROJECT_UUID is not defined; aborting AI request.', - ]; - } - - $defaultPath = $cfg['responses_path'] ?? null; - $resolvedPath = $path ?? ($options['path'] ?? $defaultPath); - if (empty($resolvedPath)) { - return [ - 'success' => false, - 'error' => 'project_id_missing', - 'message' => 'PROJECT_ID is not defined; cannot resolve AI proxy endpoint.', - ]; - } - - $url = self::buildUrl($resolvedPath, $cfg['base_url']); - $baseTimeout = isset($cfg['timeout']) ? (int) $cfg['timeout'] : 30; - $timeout = isset($options['timeout']) ? (int) $options['timeout'] : $baseTimeout; - if ($timeout <= 0) { - $timeout = 30; - } - - $baseVerifyTls = array_key_exists('verify_tls', $cfg) ? (bool) $cfg['verify_tls'] : true; - $verifyTls = array_key_exists('verify_tls', $options) - ? (bool) $options['verify_tls'] - : $baseVerifyTls; - - $projectHeader = $cfg['project_header']; - - $headers = [ - 'Content-Type: application/json', - 'Accept: application/json', - ]; - $headers[] = $projectHeader . ': ' . $projectUuid; - if (!empty($options['headers']) && is_array($options['headers'])) { - foreach ($options['headers'] as $header) { - if (is_string($header) && $header !== '') { - $headers[] = $header; - } - } - } - - if (!empty($projectUuid) && !array_key_exists('project_uuid', $payload)) { - $payload['project_uuid'] = $projectUuid; - } - - $body = json_encode($payload, JSON_UNESCAPED_UNICODE); - if ($body === false) { - return [ - 'success' => false, - 'error' => 'json_encode_failed', - 'message' => 'Failed to encode request body to JSON.', - ]; - } - - return self::sendCurl($url, 'POST', $body, $headers, $timeout, $verifyTls); - } - - /** - * Poll AI request status until ready or timeout. - * - * @param int|string $aiRequestId - * @param array $options - * @return array - */ - public static function awaitResponse($aiRequestId, array $options = []): array - { - $cfg = self::config(); - - $timeout = isset($options['timeout']) ? (int) $options['timeout'] : 300; // seconds - $interval = isset($options['interval']) ? (int) $options['interval'] : 5; // seconds - if ($interval <= 0) { - $interval = 5; - } - $perCallTimeout = isset($options['timeout_per_call']) ? (int) $options['timeout_per_call'] : null; - - $deadline = time() + max($timeout, $interval); - $headers = $options['headers'] ?? []; - - while (true) { - $statusResp = self::fetchStatus($aiRequestId, [ - 'headers' => $headers, - 'timeout' => $perCallTimeout, - ]); - if (!empty($statusResp['success'])) { - $data = $statusResp['data'] ?? []; - if (is_array($data)) { - $statusValue = $data['status'] ?? null; - if ($statusValue === 'success') { - return [ - 'success' => true, - 'status' => 200, - 'data' => $data['response'] ?? $data, - ]; - } - if ($statusValue === 'failed') { - return [ - 'success' => false, - 'status' => 500, - 'error' => isset($data['error']) ? (string)$data['error'] : 'AI request failed', - 'data' => $data, - ]; - } - } - } else { - return $statusResp; - } - - if (time() >= $deadline) { - return [ - 'success' => false, - 'error' => 'timeout', - 'message' => 'Timed out waiting for AI response.', - ]; - } - sleep($interval); - } - } - - /** - * Fetch status for queued AI request. - * - * @param int|string $aiRequestId - * @param array $options - * @return array - */ - public static function fetchStatus($aiRequestId, array $options = []): array - { - $cfg = self::config(); - $projectUuid = $cfg['project_uuid']; - if (empty($projectUuid)) { - return [ - 'success' => false, - 'error' => 'project_uuid_missing', - 'message' => 'PROJECT_UUID is not defined; aborting status check.', - ]; - } - - $statusPath = self::resolveStatusPath($aiRequestId, $cfg); - $url = self::buildUrl($statusPath, $cfg['base_url']); - - $baseTimeout = isset($cfg['timeout']) ? (int) $cfg['timeout'] : 30; - $timeout = isset($options['timeout']) ? (int) $options['timeout'] : $baseTimeout; - if ($timeout <= 0) { - $timeout = 30; - } - - $baseVerifyTls = array_key_exists('verify_tls', $cfg) ? (bool) $cfg['verify_tls'] : true; - $verifyTls = array_key_exists('verify_tls', $options) - ? (bool) $options['verify_tls'] - : $baseVerifyTls; - - $projectHeader = $cfg['project_header']; - $headers = [ - 'Accept: application/json', - $projectHeader . ': ' . $projectUuid, - ]; - if (!empty($options['headers']) && is_array($options['headers'])) { - foreach ($options['headers'] as $header) { - if (is_string($header) && $header !== '') { - $headers[] = $header; - } - } - } - - return self::sendCurl($url, 'GET', null, $headers, $timeout, $verifyTls); - } - - /** - * Extract plain text from a Responses API payload. - * - * @param array $response Result of LocalAIApi::createResponse|request. - * @return string - */ - public static function extractText(array $response): string - { - $payload = $response['data'] ?? $response; - if (!is_array($payload)) { - return ''; - } - - if (!empty($payload['output']) && is_array($payload['output'])) { - $combined = ''; - foreach ($payload['output'] as $item) { - if (!isset($item['content']) || !is_array($item['content'])) { - continue; - } - foreach ($item['content'] as $block) { - if (is_array($block) && ($block['type'] ?? '') === 'output_text' && !empty($block['text'])) { - $combined .= $block['text']; - } - } - } - if ($combined !== '') { - return $combined; - } - } - - if (!empty($payload['choices'][0]['message']['content'])) { - return (string) $payload['choices'][0]['message']['content']; - } - - return ''; - } - - /** - * Attempt to decode JSON emitted by the model (handles markdown fences). - * - * @param array $response - * @return array|null - */ - public static function decodeJsonFromResponse(array $response): ?array - { - $text = self::extractText($response); - if ($text === '') { - return null; - } - - $decoded = json_decode($text, true); - if (is_array($decoded)) { - return $decoded; - } - - $stripped = preg_replace('/^```json|```$/m', '', trim($text)); - if ($stripped !== null && $stripped !== $text) { - $decoded = json_decode($stripped, true); - if (is_array($decoded)) { - return $decoded; - } - } - - return null; - } - - /** - * Load configuration from ai/config.php. - * - * @return array - */ - private static function config(): array - { - if (self::$configCache === null) { - $configPath = __DIR__ . '/config.php'; - if (!file_exists($configPath)) { - throw new RuntimeException('AI config file not found: ai/config.php'); - } - $cfg = require $configPath; - if (!is_array($cfg)) { - throw new RuntimeException('Invalid AI config format: expected array'); - } - self::$configCache = $cfg; - } - - return self::$configCache; - } - - /** - * Build an absolute URL from base_url and a path. - */ - private static function buildUrl(string $path, string $baseUrl): string - { - $trimmed = trim($path); - if ($trimmed === '') { - return $baseUrl; - } - if (str_starts_with($trimmed, 'http://') || str_starts_with($trimmed, 'https://')) { - return $trimmed; - } - if ($trimmed[0] === '/') { - return $baseUrl . $trimmed; - } - return $baseUrl . '/' . $trimmed; - } - - /** - * Resolve status path based on configured responses_path and ai_request_id. - * - * @param int|string $aiRequestId - * @param array $cfg - * @return string - */ - private static function resolveStatusPath($aiRequestId, array $cfg): string - { - $basePath = $cfg['responses_path'] ?? ''; - $trimmed = rtrim($basePath, '/'); - if ($trimmed === '') { - return '/ai-request/' . rawurlencode((string)$aiRequestId) . '/status'; - } - if (substr($trimmed, -11) !== '/ai-request') { - $trimmed .= '/ai-request'; - } - return $trimmed . '/' . rawurlencode((string)$aiRequestId) . '/status'; - } - - /** - * Shared CURL sender for GET/POST requests. - * - * @param string $url - * @param string $method - * @param string|null $body - * @param array $headers - * @param int $timeout - * @param bool $verifyTls - * @return array - */ - private static function sendCurl(string $url, string $method, ?string $body, array $headers, int $timeout, bool $verifyTls): array - { - if (!function_exists('curl_init')) { - return [ - 'success' => false, - 'error' => 'curl_missing', - 'message' => 'PHP cURL extension is missing. Install or enable it on the VM.', - ]; - } - - $ch = curl_init($url); - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $verifyTls); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $verifyTls ? 2 : 0); - curl_setopt($ch, CURLOPT_FAILONERROR, false); - - $upper = strtoupper($method); - if ($upper === 'POST') { - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $body ?? ''); - } else { - curl_setopt($ch, CURLOPT_HTTPGET, true); - } - - $responseBody = curl_exec($ch); - if ($responseBody === false) { - $error = curl_error($ch) ?: 'Unknown cURL error'; - curl_close($ch); - return [ - 'success' => false, - 'error' => 'curl_error', - 'message' => $error, - ]; - } - - $status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); - curl_close($ch); - - $decoded = null; - if ($responseBody !== '' && $responseBody !== null) { - $decoded = json_decode($responseBody, true); - if (json_last_error() !== JSON_ERROR_NONE) { - $decoded = null; - } - } - - if ($status >= 200 && $status < 300) { + try { + // Step 1: Generate AI prompt + $prompt = $this->generateAIPrompt(); + + // Step 2: Call AI service + $aiResponse = $this->callAIService($prompt); + + // Step 3: Parse and structure the response + $analysis = $this->parseAIResponse($aiResponse); + + // Step 4: Calculate scores and metrics + $analysis['scores'] = $this->calculateScores(); + $analysis['metrics'] = $this->calculateMetrics(); + $analysis['tools'] = $this->recommendTools(); + $analysis['timeline'] = $this->generateTimeline(); + return [ 'success' => true, - 'status' => $status, - 'data' => $decoded ?? $responseBody, + 'analysis' => $analysis, + 'report_id' => $this->generateReportId(), + 'timestamp' => date('Y-m-d H:i:s') + ]; + + } catch (Exception $e) { + return [ + 'success' => false, + 'error' => $e->getMessage(), + 'fallback' => $this->generateFallbackAnalysis() ]; } - - $errorMessage = 'AI proxy request failed'; - if (is_array($decoded)) { - $errorMessage = $decoded['error'] ?? $decoded['message'] ?? $errorMessage; - } elseif (is_string($responseBody) && $responseBody !== '') { - $errorMessage = $responseBody; + } + + /** + * Generate comprehensive AI prompt based on questionnaire answers + */ + private function generateAIPrompt(): string + { + $answers = $this->questionnaireData; + + // System prompt based on language + $systemPrompts = [ + 'de' => "Du bist ein KI-Beratungsexperte für kleine und mittlere Unternehmen in Deutschland. Analysiere diese KI-Readiness-Bewertung und erstelle eine professionelle, maßgeschneiderte Analyse.", + 'en' => "You are an AI consulting expert for small and medium-sized businesses. Analyze this AI readiness assessment and create a professional, tailored analysis.", + 'tr' => "Küçük ve orta ölçekli işletmeler için bir AI danışmanlık uzmanısınız. Bu AI hazırlık değerlendirmesini analiz edin ve profesyonel, özelleştirilmiş bir analiz oluşturun." + ]; + + $systemPrompt = $systemPrompts[$this->language] ?? $systemPrompts['de']; + + // Build detailed user prompt + $prompt = "Analysiere diese KI-Readiness-Bewertung:\n\n"; + + // Company Information + $prompt .= "=== UNTERNEHMENSINFORMATIONEN ===\n"; + if (!empty($answers['q2'])) { + $prompt .= "Branche: " . $answers['q2'] . "\n"; } - + if (!empty($answers['q1'])) { + $sizeMap = [ + '1-person' => 'Einzelunternehmer (1 Person)', + '2-5' => 'Kleinstunternehmen (2-5 Mitarbeiter)', + '6-20' => 'Kleinunternehmen (6-20 Mitarbeiter)', + '21-50' => 'Mittelstand (21-50 Mitarbeiter)', + '50plus' => 'Großunternehmen (50+ Mitarbeiter)' + ]; + $prompt .= "Unternehmensgröße: " . ($sizeMap[$answers['q1']] ?? $answers['q1']) . "\n"; + } + + // Business Model + if (!empty($answers['q3']) && is_array($answers['q3'])) { + $modelMap = [ + 'dienstleistung' => 'Dienstleistung', + 'handel' => 'Handel', + 'coaching' => 'Coaching/Beratung', + 'agentur' => 'Agentur', + 'ecommerce' => 'E-Commerce', + 'produktion' => 'Produktion' + ]; + $models = array_map(fn($m) => $modelMap[$m] ?? $m, $answers['q3']); + $prompt .= "Geschäftsmodelle: " . implode(', ', $models) . "\n"; + } + + // Primary Goals + if (!empty($answers['q4'])) { + $goalMap = [ + 'zeit-sparen' => 'Zeit sparen bei repetitiven Aufgaben', + 'kosten-reduzieren' => 'Kosten reduzieren durch Automatisierung', + 'wachstum' => 'Wachstum beschleunigen', + 'qualität' => 'Qualität verbessern', + 'innovation' => 'Innovation vorantreiben' + ]; + $prompt .= "Primäres KI-Ziel: " . ($goalMap[$answers['q4']] ?? $answers['q4']) . "\n"; + } + + // AI Importance + if (!empty($answers['q5'])) { + $importance = [ + '1' => 'nicht wichtig', + '2' => 'eher unwichtig', + '3' => 'neutral', + '4' => 'wichtig', + '5' => 'sehr wichtig' + ]; + $prompt .= "KI-Bedeutung: " . ($importance[$answers['q5']] ?? $answers['q5']) . "/5\n"; + } + + // Time-consuming Tasks + if (!empty($answers['q6']) && is_array($answers['q6'])) { + $taskMap = [ + 'kundenkommunikation' => 'Kundenkommunikation', + 'administrative' => 'Administrative Aufgaben', + 'marketing' => 'Marketing/Content', + 'buchhaltung' => 'Buchhaltung/Finanzen', + 'planung' => 'Planung/Organisation', + 'daten' => 'Datenanalyse' + ]; + $tasks = array_map(fn($t) => $taskMap[$t] ?? $t, $answers['q6']); + $prompt .= "\nZeitintensive Aufgaben:\n- " . implode("\n- ", $tasks) . "\n"; + } + + // Current Automation + if (!empty($answers['q7'])) { + $prompt .= "\nAktuelle Automatisierung:\n" . $answers['q7'] . "\n"; + } + + // Weekly Hours on Repetitive Tasks + if (!empty($answers['q8'])) { + $hoursMap = [ + 'unter-5h' => 'unter 5 Stunden/Woche', + '5-10h' => '5-10 Stunden/Woche', + '11-15h' => '11-15 Stunden/Woche', + '16-20h' => '16-20 Stunden/Woche', + '20plus' => 'über 20 Stunden/Woche' + ]; + $prompt .= "Repetitive Stunden/Woche: " . ($hoursMap[$answers['q8']] ?? $answers['q8']) . "\n"; + } + + // Budget + if (!empty($answers['q9'])) { + $budgetMap = [ + 'unter-50' => 'unter 50€/Monat', + '50-100' => '50-100€/Monat', + '100-300' => '100-300€/Monat', + '300-500' => '300-500€/Monat', + '500plus' => '500€+/Monat' + ]; + $prompt .= "KI-Budget: " . ($budgetMap[$answers['q9']] ?? $answers['q9']) . "\n"; + } + + // Technical Affinity + if (!empty($answers['q10'])) { + $affinity = [ + '1' => 'sehr schwer', + '2' => 'eher schwer', + '3' => 'neutral', + '4' => 'einfach', + '5' => 'sehr einfach' + ]; + $prompt .= "Technische Affinität: " . ($affinity[$answers['q10']] ?? $answers['q10']) . "/5\n"; + } + + // Current AI Tools + if (!empty($answers['q11']) && is_array($answers['q11'])) { + $toolMap = [ + 'chatgpt' => 'ChatGPT/Sprach-KIs', + 'midjourney' => 'Bildgenerierung', + 'crm' => 'KI-CRM Tools', + 'marketing' => 'KI-Marketing Tools', + 'automatisierung' => 'Automatisierungs-Tools', + 'keine' => 'Keine KI-Tools' + ]; + $tools = array_map(fn($t) => $toolMap[$t] ?? $t, $answers['q11']); + $prompt .= "Aktuelle KI-Tools: " . implode(', ', $tools) . "\n"; + } + + // Biggest Challenge + if (!empty($answers['q12'])) { + $challengeMap = [ + 'zeit' => 'Zeitmangel', + 'wissen' => 'Fehlendes Wissen', + 'kosten' => 'Kosten', + 'integration' => 'Integration', + 'nutzen' => 'Unklarheit über Nutzen' + ]; + $prompt .= "Größte Herausforderung: " . ($challengeMap[$answers['q12']] ?? $answers['q12']) . "\n"; + } + + // Implementation Timeline + if (!empty($answers['q13'])) { + $timelineMap = [ + 'sofort' => 'sofort (innerhalb 1 Monat)', + 'kurz' => 'kurzfristig (1-3 Monate)', + 'mittel' => 'mittelfristig (3-6 Monate)', + 'lang' => 'langfristig (6+ Monate)', + 'keine' => 'keine konkreten Pläne' + ]; + $prompt .= "Umsetzungszeitraum: " . ($timelineMap[$answers['q13']] ?? $answers['q13']) . "\n"; + } + + $prompt .= "\n=== ANFORDERUNGEN FÜR DIE ANALYSE ===\n"; + $prompt .= "Bitte erstelle eine strukturierte Analyse mit folgenden Abschnitten:\n"; + $prompt .= "1. KI-Readiness Score (0-100) mit Begründung\n"; + $prompt .= "2. Stärken des Unternehmens für KI-Implementierung\n"; + $prompt .= "3. Verbesserungsbereiche\n"; + $prompt .= "4. Konkrete KI-Anwendungsfälle für dieses Unternehmen\n"; + $prompt .= "5. Quick Wins (schnell umsetzbare Maßnahmen)\n"; + $prompt .= "6. Strategische Empfehlungen für die nächsten 12 Monate\n"; + $prompt .= "7. Geschätzte Zeit- und Kosteneinsparungspotenziale\n\n"; + $prompt .= "Antworte in einem professionellen, aber verständlichen Ton. Nutze Überschriften und Aufzählungen für bessere Lesbarkeit."; + + return json_encode([ + 'model' => 'gpt-4', + 'input' => [ + ['role' => 'system', 'content' => $systemPrompt], + ['role' => 'user', 'content' => $prompt] + ], + 'temperature' => 0.7, + 'max_tokens' => 3000 + ]); + } + + /** + * Call the AI service using LocalAIApi + */ + private function callAIService(string $prompt): array + { + $payload = json_decode($prompt, true); + + // Use LocalAIApi to call the AI service + $response = LocalAIApi::createResponse($payload, [ + 'poll_interval' => 3, + 'poll_timeout' => 60 + ]); + + if (empty($response['success'])) { + throw new Exception('AI service error: ' . ($response['error'] ?? 'Unknown error')); + } + + return $response; + } + + /** + * Parse AI response into structured format + */ + private function parseAIResponse(array $aiResponse): array + { + $text = LocalAIApi::extractText($aiResponse); + + // Try to parse as JSON first + $json = LocalAIApi::decodeJsonFromResponse($aiResponse); + if ($json) { + return $this->parseStructuredJSON($json); + } + + // Otherwise, parse the text + return $this->parseTextResponse($text); + } + + /** + * Parse structured JSON response + */ + private function parseStructuredJSON(array $json): array + { return [ - 'success' => false, - 'status' => $status, - 'error' => $errorMessage, - 'response' => $decoded ?? $responseBody, + 'score' => $json['score'] ?? $this->calculateScore(), + 'score_explanation' => $json['score_explanation'] ?? '', + 'strengths' => $json['strengths'] ?? [], + 'improvements' => $json['improvements'] ?? [], + 'use_cases' => $json['use_cases'] ?? [], + 'quick_wins' => $json['quick_wins'] ?? [], + 'recommendations' => $json['recommendations'] ?? [], + 'estimated_savings' => $json['estimated_savings'] ?? [], + 'full_analysis' => $json + ]; + } + + /** + * Parse text response + */ + private function parseTextResponse(string $text): array + { + // Extract score from text + preg_match('/Score[\s:]*(\d+)/i', $text, $scoreMatches); + $score = isset($scoreMatches[1]) ? (int)$scoreMatches[1] : $this->calculateScore(); + + // Extract sections (simplified parsing) + $sections = [ + 'strengths' => $this->extractSection($text, ['Stärken', 'Stärke', 'Vorteile']), + 'improvements' => $this->extractSection($text, ['Verbesserung', 'Schwächen', 'Herausforderung']), + 'quick_wins' => $this->extractSection($text, ['Quick Wins', 'schnelle Maßnahmen', 'sofort umsetzbar']), + 'recommendations' => $this->extractSection($text, ['Empfehlung', 'Maßnahmen', 'Vorschläge']) + ]; + + return [ + 'score' => $score, + 'score_explanation' => $this->extractScoreExplanation($text), + 'strengths' => $sections['strengths'], + 'improvements' => $sections['improvements'], + 'use_cases' => $this->extractUseCases($text), + 'quick_wins' => $sections['quick_wins'], + 'recommendations' => $sections['recommendations'], + 'estimated_savings' => $this->extractSavings($text), + 'full_analysis' => $text + ]; + } + + /** + * Calculate readiness score based on answers + */ + private function calculateScore(): int + { + $score = 50; // Base score + + // Add points based on answers + if (!empty($this->questionnaireData['q5'])) { + $score += ((int)$this->questionnaireData['q5'] - 3) * 10; + } + + if (!empty($this->questionnaireData['q10'])) { + $score += ((int)$this->questionnaireData['q10'] - 3) * 8; + } + + if (!empty($this->questionnaireData['q8'])) { + $hoursMap = [ + 'unter-5h' => -10, + '5-10h' => 0, + '11-15h' => 15, + '16-20h' => 25, + '20plus' => 30 + ]; + $score += $hoursMap[$this->questionnaireData['q8']] ?? 0; + } + + if (!empty($this->questionnaireData['q13'])) { + $timelineMap = [ + 'sofort' => 20, + 'kurz' => 15, + 'mittel' => 10, + 'lang' => 5, + 'keine' => 0 + ]; + $score += $timelineMap[$this->questionnaireData['q13']] ?? 0; + } + + return max(0, min(100, $score)); + } + + /** + * Calculate business metrics + */ + private function calculateMetrics(): array + { + $answers = $this->questionnaireData; + + // Time saving potential + $hoursMap = [ + 'unter-5h' => 10, + '5-10h' => 20, + '11-15h' => 30, + '16-20h' => 40, + '20plus' => 50 + ]; + $weeklyHours = $hoursMap[$answers['q8']] ?? 20; + + // Cost saving (assuming €50/hour) + $monthlyCostSaving = $weeklyHours * 50 * 4.33; + + // Quick wins based on number of time-consuming tasks + $quickWins = !empty($answers['q6']) && is_array($answers['q6']) + ? min(count($answers['q6']), 8) + : 3; + + return [ + 'time_saving_weekly' => $weeklyHours . 'h', + 'time_saving_monthly' => ($weeklyHours * 4.33) . 'h', + 'cost_saving_monthly' => '€' . number_format($monthlyCostSaving, 0, ',', '.'), + 'cost_saving_yearly' => '€' . number_format($monthlyCostSaving * 12, 0, ',', '.'), + 'quick_wins_count' => $quickWins, + 'roi_months' => 6 // Estimated ROI in months + ]; + } + + /** + * Recommend tools based on answers + */ + private function recommendTools(): array + { + $answers = $this->questionnaireData; + $tools = []; + + // Always recommend these basics + $tools[] = [ + 'name' => 'ChatGPT Plus', + 'category' => 'Content & Kreativität', + 'priority' => 'high', + 'description' => 'Für Texterstellung, Ideen & Content', + 'price' => '20€/Monat' + ]; + + // Based on business model + if (!empty($answers['q3']) && is_array($answers['q3'])) { + if (in_array('dienstleistung', $answers['q3']) || in_array('coaching', $answers['q3'])) { + $tools[] = [ + 'name' => 'Calendly', + 'category' => 'Terminplanung', + 'priority' => 'medium', + 'description' => 'Automatisierte Terminbuchung', + 'price' => '10€/Monat' + ]; + } + + if (in_array('ecommerce', $answers['q3'])) { + $tools[] = [ + 'name' => 'Zapier', + 'category' => 'Automatisierung', + 'priority' => 'high', + 'description' => 'App-Integration ohne Code', + 'price' => '20€/Monat' + ]; + } + + if (in_array('agentur', $answers['q3']) || in_array('marketing', $answers['q3'])) { + $tools[] = [ + 'name' => 'Jasper AI', + 'category' => 'Content Marketing', + 'priority' => 'high', + 'description' => 'Professionelle Marketing-Texte', + 'price' => '39€/Monat' + ]; + } + } + + // Based on time-consuming tasks + if (!empty($answers['q6']) && is_array($answers['q6'])) { + if (in_array('kundenkommunikation', $answers['q6'])) { + $tools[] = [ + 'name' => 'Pipedrive', + 'category' => 'CRM & Vertrieb', + 'priority' => 'high', + 'description' => 'Visuelles CRM für den Vertrieb', + 'price' => '15€/Monat' + ]; + } + + if (in_array('buchhaltung', $answers['q6'])) { + $tools[] = [ + 'name' => 'QuickBooks', + 'category' => 'Buchhaltung', + 'priority' => 'medium', + 'description' => 'KI-gestützte Buchhaltung', + 'price' => '25€/Monat' + ]; + } + } + + // Based on budget + if (!empty($answers['q9'])) { + if ($answers['q9'] === 'unter-50') { + $tools = array_slice($tools, 0, 2); // Limit to 2 tools for small budget + } + } + + return array_slice($tools, 0, 5); // Max 5 tools + } + + /** + * Generate implementation timeline + */ + private function generateTimeline(): array + { + $answers = $this->questionnaireData; + + if (empty($answers['q13'])) { + return $this->getDefaultTimeline(); + } + + $timelines = [ + 'sofort' => [ + ['month' => 1, 'action' => 'Quick Wins implementieren', 'tools' => ['ChatGPT', 'Calendly']], + ['month' => 2, 'action' => 'Kundenkommunikation automatisieren', 'tools' => ['Pipedrive', 'Zapier']], + ['month' => 3, 'action' => 'Reporting & Analyse aufbauen', 'tools' => ['Power BI', 'Google Analytics']] + ], + 'kurz' => [ + ['month' => 1, 'action' => 'Machbarkeitsstudie', 'tools' => []], + ['month' => 2, 'action' => 'Pilotprojekt starten', 'tools' => ['ChatGPT']], + ['month' => 3, 'action' => 'Team schulen', 'tools' => []], + ['month' => 4, 'action' => 'Erste Prozesse automatisieren', 'tools' => ['Zapier']] + ], + 'mittel' => [ + ['month' => 1, 'action' => 'Strategie entwickeln', 'tools' => []], + ['month' => 2, 'action' => 'Tools evaluieren', 'tools' => []], + ['month' => 3, 'action' => 'Budget planen', 'tools' => []], + ['month' => 4, 'action' => 'Pilot starten', 'tools' => []], + ['month' => 5, 'action' => 'Erste Ergebnisse analysieren', 'tools' => []], + ['month' => 6, 'action' => 'Skalierung planen', 'tools' => []] + ], + 'lang' => [ + ['month' => 1, 'action' => 'Informationsphase', 'tools' => []], + ['month' => 3, 'action' => 'Anforderungsanalyse', 'tools' => []], + ['month' => 6, 'action' => 'Strategieentwicklung', 'tools' => []], + ['month' => 9, 'action' => 'Pilotprojekt starten', 'tools' => []], + ['month' => 12, 'action' => 'Evaluierung', 'tools' => []] + ] + ]; + + return $timelines[$answers['q13']] ?? $this->getDefaultTimeline(); + } + + private function getDefaultTimeline(): array + { + return [ + ['month' => 1, 'action' => 'Quick Wins umsetzen', 'tools' => ['ChatGPT']], + ['month' => 2, 'action' => 'Prozessanalyse durchführen', 'tools' => []], + ['month' => 3, 'action' => 'Erste Automatisierungen', 'tools' => ['Zapier']], + ['month' => 6, 'action' => 'Ergebnisse evaluieren', 'tools' => []], + ['month' => 12, 'action' => 'KI-Strategie skalieren', 'tools' => []] + ]; + } + + /** + * Helper methods for text parsing + */ + private function extractSection(string $text, array $keywords): array + { + $lines = explode("\n", $text); + $items = []; + $inSection = false; + + foreach ($lines as $line) { + $line = trim($line); + + // Check if we've entered a section + foreach ($keywords as $keyword) { + if (stripos($line, $keyword) !== false && strlen($line) < 100) { + $inSection = true; + continue 2; + } + } + + // Collect items in section + if ($inSection && ($line === '' || stripos($line, '###') !== false)) { + break; + } + + if ($inSection && $line !== '' && preg_match('/^[-•\d.]+\s+(.+)$/', $line, $matches)) { + $items[] = $matches[1]; + } + } + + return array_slice($items, 0, 5); + } + + private function extractUseCases(string $text): array + { + // Simple extraction of numbered items + preg_match_all('/\d+\.\s+(.+?)(?=\n\d+\.|\n###|$)/s', $text, $matches); + return $matches[1] ?? ['Kundenkommunikation automatisieren', 'Content-Erstellung optimieren', 'Datenanalyse verbessern']; + } + + private function extractSavings(string $text): array + { + preg_match_all('/(\d+[\d.,]*)\s*(?:Stunden|h|€|EUR)/i', $text, $matches); + return $matches[0] ?? ['20-30 Stunden/Woche', '1.000-2.000€/Monat']; + } + + private function extractScoreExplanation(string $text): string + { + $lines = explode("\n", $text); + foreach ($lines as $line) { + if (stripos($line, 'Score') !== false || stripos($line, 'Bewertung') !== false) { + return $line; + } + } + return ''; + } + + private function generateReportId(): string + { + return 'KI-' . date('Ymd-His') . '-' . strtoupper(substr(md5(uniqid()), 0, 6)); + } + + private function generateFallbackAnalysis(): array + { + return [ + 'score' => $this->calculateScore(), + 'score_explanation' => 'Basierend auf Ihren Antworten berechnet', + 'strengths' => ['Bereitschaft zur Veränderung', 'Klares Ziel definiert'], + 'improvements' => ['Prozessanalyse benötigt', 'Budgetplanung optimieren'], + 'use_cases' => ['Kundenkommunikation automatisieren', 'Content-Erstellung optimieren'], + 'quick_wins' => ['ChatGPT für E-Mail-Vorlagen nutzen', 'Automatische Terminbuchung einrichten'], + 'recommendations' => ['Mit kleinen Projekten starten', 'Team schulen und einbinden'], + 'estimated_savings' => ['20-30 Stunden/Woche', '1.000-2.000€/Monat'] ]; } } - -// Legacy alias for backward compatibility with the previous class name. -if (!class_exists('OpenAIService')) { - class_alias(LocalAIApi::class, 'OpenAIService'); -} +?> \ No newline at end of file