diff --git a/src/main/java/kz/konturai/parser/service/MarketingAnalysisV3Service.java b/src/main/java/kz/konturai/parser/service/MarketingAnalysisV3Service.java index d872f68..c814b97 100644 --- a/src/main/java/kz/konturai/parser/service/MarketingAnalysisV3Service.java +++ b/src/main/java/kz/konturai/parser/service/MarketingAnalysisV3Service.java @@ -1,6 +1,7 @@ package kz.konturai.parser.service; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import kz.konturai.parser.dto.*; import kz.konturai.parser.model.MarketingAnalysisV3Document; @@ -29,6 +30,8 @@ public class MarketingAnalysisV3Service { private String highIntelligenceModel; public String createAndStartAnalysis(MarketingAnalysisV3Request request, String userId) { + log.info("Creating new Analysis V3 for user: {}", userId); + MarketingAnalysisV3Document doc = new MarketingAnalysisV3Document(); doc.setUserId(userId); doc.setRequestData(request); @@ -37,6 +40,7 @@ public class MarketingAnalysisV3Service { doc.setUpdatedAt(LocalDateTime.now()); doc = repository.save(doc); + log.info("Analysis document saved with ID: {}", doc.getId()); processAnalysisAsync(doc.getId(), request); return doc.getId(); @@ -45,21 +49,28 @@ public class MarketingAnalysisV3Service { @Async("reportGenerationExecutor") public void processAnalysisAsync(String docId, MarketingAnalysisV3Request request) { try { + log.info("[Analysis ID: {}] Started async processing", docId); updateStatus(docId, "PROCESSING"); + log.info("[Analysis ID: {}] Step 1/3: Executing Deep Research", docId); Map researchPack = executeDeepResearch(request); saveResearchMetaData(docId, researchPack); + log.info("[Analysis ID: {}] Step 2/3: Building Prompts", docId); String systemPrompt = buildKazakhstanSystemPrompt(); String userPrompt = buildDataDrivenUserPrompt(request, researchPack); - String jsonResponse = generateAiResponseWithRetry(userPrompt, systemPrompt); + log.info("[Analysis ID: {}] Step 3/3: Calling AI Model ({})", docId, highIntelligenceModel); + String jsonResponse = generateAiResponseWithRetry(docId, userPrompt, systemPrompt); + + log.info("[Analysis ID: {}] Parsing and validating AI result", docId); MarketingAnalysisV3Result result = parseAndValidateResult(jsonResponse); completeAnalysis(docId, result); + log.info("[Analysis ID: {}] Analysis successfully completed!", docId); } catch (Exception e) { - log.error("Analysis V3 Failed for ID {}: {}", docId, e.getMessage(), e); + log.error("[Analysis ID: {}] FAILED with error: {}", docId, e.getMessage(), e); failAnalysis(docId, e.getMessage()); } } @@ -91,10 +102,16 @@ public class MarketingAnalysisV3Service { queries.add(String.format("кейс продвижение SMM %s казахстан", niche)); queries.parallelStream().forEach(q -> { + // КРИТИЧЕСКИ ВАЖНО: MongoDB не поддерживает точки (.) в названиях ключей! + // Поэтому заменяем точки на нижнее подчеркивание перед сохранением в Map. + String safeMongoKey = q.replace(".", "_").replace("$", ""); + try { - pack.put(q, searchService.search(q)); + log.debug("Executing search query: {}", q); + pack.put(safeMongoKey, searchService.search(q)); } catch (Exception e) { - pack.put(q, Map.of("error", e.getMessage(), "status", "ERROR")); + log.warn("Search failed for query: {}. Error: {}", q, e.getMessage()); + pack.put(safeMongoKey, Map.of("error", e.getMessage(), "status", "ERROR")); } }); @@ -102,13 +119,14 @@ public class MarketingAnalysisV3Service { return pack; } - private String generateAiResponseWithRetry(String userPrompt, String systemPrompt) { + private String generateAiResponseWithRetry(String docId, String userPrompt, String systemPrompt) { int attempts = 0; int maxAttempts = 3; String lastError = ""; while (attempts < maxAttempts) { try { + log.info("[Analysis ID: {}] AI Generation attempt {}/{}", docId, attempts + 1, maxAttempts); String response = aiService.generateWithInstructionWithModel( "{}", userPrompt, @@ -121,24 +139,31 @@ public class MarketingAnalysisV3Service { String cleaned = cleanJson(response); if (cleaned != null && cleaned.startsWith("{") && cleaned.endsWith("}")) { + // Проверка валидности JSON objectMapper.readTree(cleaned); return cleaned; } else { - lastError = "Response is not a valid JSON"; + lastError = "Response is not a valid JSON structure"; + log.warn("[Analysis ID: {}] Invalid JSON received from AI", docId); } } catch (JsonProcessingException e) { lastError = "JSON Parse Error: " + e.getMessage(); + log.warn("[Analysis ID: {}] Failed to parse AI JSON response", docId); } catch (Exception e) { lastError = "API Error: " + e.getMessage(); + log.warn("[Analysis ID: {}] AI API Call failed: {}", docId, e.getMessage()); } attempts++; - try { Thread.sleep(2500L * attempts); } catch (InterruptedException ignored) {} + try { Thread.sleep(3000L * attempts); } catch (InterruptedException ignored) {} } throw new RuntimeException("Failed to generate valid JSON after " + maxAttempts + " attempts. Last error: " + lastError); } private MarketingAnalysisV3Result parseAndValidateResult(String json) throws Exception { - return objectMapper.readValue(json, MarketingAnalysisV3Result.class); + // Создаем безопасную копию маппера, чтобы он не падал, если ИИ придумает новые поля + ObjectMapper safeMapper = this.objectMapper.copy(); + safeMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + return safeMapper.readValue(json, MarketingAnalysisV3Result.class); } private String cleanJson(String response) { @@ -163,10 +188,16 @@ public class MarketingAnalysisV3Service { } private void saveResearchMetaData(String id, Map researchPack) { - repository.findById(id).ifPresent(doc -> { - doc.setResearchMetaData(researchPack); - repository.save(doc); - }); + try { + repository.findById(id).ifPresent(doc -> { + doc.setResearchMetaData(researchPack); + repository.save(doc); + log.debug("[Analysis ID: {}] Research metadata successfully saved to DB", id); + }); + } catch (Exception e) { + log.error("[Analysis ID: {}] CRITICAL: Failed to save research metadata to MongoDB. Check for invalid characters in keys. Error: {}", id, e.getMessage()); + throw e; // Пробрасываем дальше, чтобы уйти в статус FAILED + } } private void completeAnalysis(String id, MarketingAnalysisV3Result result) { @@ -308,28 +339,6 @@ public class MarketingAnalysisV3Service { "reviews": 0, "strengths": ["string"], "weaknesses": ["string"] - }, - { - "name": "РЕАЛЬНОЕ НАЗВАНИЕ БРЕНДА 2", - "platform": "string", - "followers": 0, - "postsPerMonth": 0, - "er": 0.0, - "rating": 0.0, - "reviews": 0, - "strengths": ["string"], - "weaknesses": ["string"] - }, - { - "name": "РЕАЛЬНОЕ НАЗВАНИЕ БРЕНДА 3", - "platform": "string", - "followers": 0, - "postsPerMonth": 0, - "er": 0.0, - "rating": 0.0, - "reviews": 0, - "strengths": ["string"], - "weaknesses": ["string"] } ], "4_content_profile": {