find($this->ticketId); if ($ticket === null) { return; } $ticket->update(['status' => 'processing']); $logger->log($ticket, 'start', 'info', 'Ticket processing gestart.', [ 'original_question' => $ticket->message, ]); try { $logger->log($ticket, 'normalize_question', 'info', 'Vraag normaliseren en PII redacteren.'); $normalized = $normalizer->normalize($ticket->message); $ticket->normalized_message = $normalized['normalized_message']; $ticket->redaction_report = $normalized['redaction_report'] ?? null; $ticket->save(); $logger->log($ticket, 'normalize_question', 'success', 'Vraag genormaliseerd.', [ 'normalized_message' => $ticket->normalized_message, 'redaction_report' => $ticket->redaction_report, ]); if ($ticket->embedding === null) { $logger->log($ticket, 'embedding', 'info', 'Embedding genereren.'); $ticket->embedding = $embeddingService->embed($ticket->normalized_message ?: $ticket->message); $embeddingContext = $embeddingService->context(); $ticket->embedding_provider_instance_id = $embeddingContext['provider_instance_id']; $ticket->embedding_model = $embeddingContext['embedding_model']; $ticket->embedded_at = now(); $ticket->save(); } $embeddingVector = $ticket->embedding ?? []; $logger->log($ticket, 'embedding', 'success', 'Embedding beschikbaar.', [ 'vector_dimensions' => count($embeddingVector), 'vector_preview' => array_slice($embeddingVector, 0, 8), ]); $logger->log($ticket, 'retrieval_ranking', 'info', 'Semantic retrieval en AI ranking uitvoeren.'); $result = $semanticSearchService->findBestArticle($ticket); $logger->log($ticket, 'retrieval', 'success', 'Top kandidaten uit vector search bepaald.', [ 'candidates' => $result['top_5_candidates'] ?? [], 'retrieval_meta' => $result['retrieval_meta'] ?? null, ]); $logger->log($ticket, 'ranking', 'success', 'Classificatie afgerond.', [ 'selected_article_id' => $result['best_article']?->id, 'confidence' => $result['confidence'], 'explanation' => $result['explanation'], 'classifier_raw_response' => $result['classifier_raw_response'] ?? null, ]); $isKnowledgeGap = $knowledgeGapService->shouldCreateDraft($ticket, $result); $draftSuggestion = null; $supportReply = null; $toolCallRecord = null; $quickReply = null; if ($isKnowledgeGap) { $logger->log($ticket, 'knowledge_gap', 'warning', 'Onvoldoende match gevonden; geen passend artikel in kennisbank.'); $draftSuggestion = $knowledgeGapService->suggestArticleDraft($ticket, $result); $logger->log($ticket, 'knowledge_gap', 'success', 'Voorstel voor nieuw kennisbankartikel gegenereerd (niet opgeslagen).', [ 'suggested_title' => $draftSuggestion['title'] ?? null, ]); } else { $quickReply = $quickReplyResolver->resolveForArticle($result['best_article'] ?? null); if ($quickReply !== null) { $supportReply = $quickReply->content; $logger->log($ticket, 'quick_reply', 'success', 'Snelantwoord gebruikt; AI antwoordgeneratie overgeslagen.', [ 'quick_reply_id' => $quickReply->id, 'quick_reply_title' => $quickReply->title, 'article_id' => $result['best_article']?->id, ]); } else { $toolCallRecord = $toolCallService->executeRequestedTool( $ticket, $result['best_article'] ?? null, $result['requested_tool_call'] ?? null ); $logger->log($ticket, 'quick_reply', 'info', 'Geen actief snelantwoord gekoppeld; AI maakt conceptantwoord.', [ 'article_id' => $result['best_article']?->id, ]); $supportReply = $supportReplyService->build( $ticket, $result['best_article'] ?? null, (string) $result['explanation'], $toolCallRecord?->toArray() ); } } $ticket->update([ 'status' => 'completed', 'best_article_id' => $result['best_article']?->id, 'confidence' => $result['confidence'], 'explanation' => $result['explanation'], 'support_reply' => $supportReply, 'needs_article_draft' => $isKnowledgeGap, 'draft_article_id' => null, 'result_payload' => $payloadBuilder->build($result, $toolCallRecord, $quickReply, $isKnowledgeGap, $draftSuggestion), 'error_message' => null, 'processed_at' => now(), ]); if ($supportReply !== null) { $logger->log($ticket, 'support_reply', 'success', 'Concept supportantwoord opgebouwd.', [ 'support_reply_preview' => Str::limit($supportReply, 220), ]); } else { $logger->log($ticket, 'support_reply', 'info', 'Geen klantreactie gegenereerd wegens knowledge gap; eerst artikelreview nodig.'); } $logger->log($ticket, 'completed', 'success', 'Ticket processing afgerond.', [ 'best_article_id' => $result['best_article']?->id, 'confidence' => $result['confidence'], ]); } catch (\Throwable $e) { $ticket->update([ 'status' => 'failed', 'error_message' => $e->getMessage(), 'processed_at' => now(), ]); $logger->log($ticket, 'failed', 'error', 'Ticket processing gefaald.', [ 'error' => $e->getMessage(), ]); throw $e; } } }