diff --git a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java index cf1ee5a..4f33d14 100644 --- a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java +++ b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java @@ -83,60 +83,89 @@ public class MarketingAnalysisService { @Async("reportGenerationExecutor") public void processAnalysis(String analysisId, MarketingAnalysisRequest request) { try { + logger.info("processAnalysis: Starting for analysisId: {}", analysisId); Optional optAnalysis = repository.findById(analysisId); if (optAnalysis.isEmpty()) { - logger.error("Marketing analysis not found: {}", analysisId); + logger.error("processAnalysis: Marketing analysis not found: {}", analysisId); return; } MarketingAnalysis analysis = optAnalysis.get(); analysis.setStatus("processing"); addStatusHistoryEntry(analysis, "processing", "Начата обработка анализа"); + logger.debug("processAnalysis: Saving analysis with 'processing' status to MongoDB for ID: {}", analysisId); repository.save(analysis); + logger.debug("processAnalysis: Analysis status set to 'processing' and saved for ID: {}", analysisId); - logger.info("Starting marketing analysis processing for ID: {}", analysisId); + logger.info("processAnalysis: Starting marketing analysis processing for ID: {}", analysisId); // Generate marketing report with all analysis types + logger.info("processAnalysis: Starting generateMarketingReport for ID: {}", analysisId); Map reportData = generateMarketingReport(request); + logger.info("processAnalysis: generateMarketingReport completed for ID: {}. Report keys: {}", + analysisId, reportData != null ? reportData.keySet() : "null"); // Generate markdown content for PDF + logger.info("processAnalysis: Starting buildMarkdownFromReport for ID: {}", analysisId); String markdownContent = buildMarkdownFromReport(reportData, request); + logger.info("processAnalysis: buildMarkdownFromReport completed for ID: {}. Markdown length: {} chars", + analysisId, markdownContent != null ? markdownContent.length() : 0); // Save markdown content into report data so frontend gets JSON code blocks reportData.put("fullAnalysis", markdownContent); // Generate PDF + logger.info("processAnalysis: Starting PDF generation for ID: {}", analysisId); byte[] pdfBytes = researchPdfService.generatePdfReport( "Маркетинговый анализ: " + request.getProduct(), markdownContent, new ArrayList<>()); + logger.info("processAnalysis: PDF generation completed for ID: {}. PDF size: {} bytes", + analysisId, pdfBytes != null ? pdfBytes.length : 0); // Save PDF to MinIO String filename = "marketing_analysis_" + analysisId + "_" + System.currentTimeMillis() + ".pdf"; + logger.info("processAnalysis: Starting MinIO upload for ID: {}. Filename: {}", analysisId, filename); minIOService.uploadFile(filename, pdfBytes, MediaType.APPLICATION_PDF.toString()); + logger.info("processAnalysis: MinIO upload completed for ID: {}", analysisId); // Update analysis with results + logger.info("processAnalysis: Updating analysis status to 'completed' for ID: {}", analysisId); analysis.setStatus("completed"); analysis.setCompletedAt(LocalDateTime.now()); + logger.debug("processAnalysis: Setting reportData. ReportData keys: {}", + reportData != null ? reportData.keySet() : "null"); analysis.setReportData(reportData); analysis.setPdfFilename(filename); analysis.setPdfFilePath(filename); addStatusHistoryEntry(analysis, "completed", "Анализ успешно завершен"); + logger.info("processAnalysis: Saving analysis to MongoDB for ID: {}", analysisId); repository.save(analysis); + logger.info("processAnalysis: Analysis saved successfully to MongoDB for ID: {}", analysisId); - logger.info("Marketing analysis completed successfully for ID: {}", analysisId); + logger.info("processAnalysis: Marketing analysis completed successfully for ID: {}", analysisId); } catch (Exception e) { - logger.error("Error processing marketing analysis {}: {}", analysisId, e.getMessage(), e); + logger.error("processAnalysis: Error processing marketing analysis {}: {}", analysisId, e.getMessage(), e); + logger.error("processAnalysis: Stack trace for analysisId {}: ", analysisId, e); try { + logger.info("processAnalysis: Attempting to update analysis status to 'failed' for ID: {}", analysisId); Optional optAnalysis = repository.findById(analysisId); if (optAnalysis.isPresent()) { MarketingAnalysis analysis = optAnalysis.get(); analysis.setStatus("failed"); addStatusHistoryEntry(analysis, "failed", "Ошибка при обработке: " + e.getMessage()); + logger.debug("processAnalysis: Saving analysis with 'failed' status to MongoDB for ID: {}", + analysisId); repository.save(analysis); + logger.info("processAnalysis: Analysis status updated to 'failed' and saved for ID: {}", + analysisId); + } else { + logger.error("processAnalysis: Cannot update status - analysis not found: {}", analysisId); } } catch (Exception saveError) { - logger.error("Failed to update analysis status to failed: {}", saveError.getMessage()); + logger.error("processAnalysis: Failed to update analysis status to failed for ID {}: {}", + analysisId, saveError.getMessage(), saveError); + logger.error("processAnalysis: Stack trace for saveError: ", saveError); } } } @@ -400,22 +429,30 @@ public class MarketingAnalysisService { + "High = сегмент активно использует канал, Medium = умеренное использование, Low = редкое использование.\n"); String prompt = promptBuilder.toString(); - logger.debug("Generating audience analysis with detailed prompt (length: {} chars)", prompt.length()); + logger.debug("getAudienceAnalysis: Generating audience analysis with detailed prompt (length: {} chars)", + prompt.length()); + logger.info("getAudienceAnalysis: Calling OpenAI API for audience analysis"); String jsonResponse = openAIAnalyticsService.generateWithInstruction(context, prompt, "ru"); + logger.debug("getAudienceAnalysis: OpenAI response received. Length: {} chars", + jsonResponse != null ? jsonResponse.length() : 0); if (jsonResponse == null || jsonResponse.trim().isEmpty()) { - logger.warn("Empty JSON response from AI for audience analysis"); + logger.warn("getAudienceAnalysis: Empty JSON response from AI for audience analysis"); return new HashMap<>(); } + logger.debug("getAudienceAnalysis: Cleaning JSON from markdown"); String cleanedJson = cleanJsonFromMarkdown(jsonResponse); + logger.debug("getAudienceAnalysis: Parsing JSON response"); Map jsonMap = objectMapper.readValue(cleanedJson, new TypeReference>() { }); + logger.debug("getAudienceAnalysis: JSON parsed successfully. Keys: {}", jsonMap.keySet()); - logger.info("Audience analysis generated successfully"); + logger.info("getAudienceAnalysis: Audience analysis generated successfully"); return jsonMap; } catch (Exception e) { - logger.error("Error generating audience analysis: {}", e.getMessage(), e); + logger.error("getAudienceAnalysis: Error generating audience analysis: {}", e.getMessage(), e); + logger.error("getAudienceAnalysis: Stack trace: ", e); return new HashMap<>(); } } @@ -524,22 +561,31 @@ public class MarketingAnalysisService { "12. channelsHeatmap должна содержать оценки (0-100) для всех основных каналов: Instagram, TikTok, Telegram, YouTube, Facebook.\n"); String prompt = promptBuilder.toString(); - logger.debug("Generating competitor analysis with detailed prompt (length: {} chars)", prompt.length()); + logger.debug( + "getCompetitorAnalysis: Generating competitor analysis with detailed prompt (length: {} chars)", + prompt.length()); + logger.info("getCompetitorAnalysis: Calling OpenAI API for competitor analysis"); String jsonResponse = openAIAnalyticsService.generateWithInstruction(context, prompt, "ru"); + logger.debug("getCompetitorAnalysis: OpenAI response received. Length: {} chars", + jsonResponse != null ? jsonResponse.length() : 0); if (jsonResponse == null || jsonResponse.trim().isEmpty()) { - logger.warn("Empty JSON response from AI for competitor analysis"); + logger.warn("getCompetitorAnalysis: Empty JSON response from AI for competitor analysis"); return new HashMap<>(); } + logger.debug("getCompetitorAnalysis: Cleaning JSON from markdown"); String cleanedJson = cleanJsonFromMarkdown(jsonResponse); + logger.debug("getCompetitorAnalysis: Parsing JSON response"); Map jsonMap = objectMapper.readValue(cleanedJson, new TypeReference>() { }); + logger.debug("getCompetitorAnalysis: JSON parsed successfully. Keys: {}", jsonMap.keySet()); - logger.info("Competitor analysis generated successfully"); + logger.info("getCompetitorAnalysis: Competitor analysis generated successfully"); return jsonMap; } catch (Exception e) { - logger.error("Error generating competitor analysis: {}", e.getMessage(), e); + logger.error("getCompetitorAnalysis: Error generating competitor analysis: {}", e.getMessage(), e); + logger.error("getCompetitorAnalysis: Stack trace: ", e); return new HashMap<>(); } } @@ -621,23 +667,32 @@ public class MarketingAnalysisService { "11. НЕ включай поле \"estimated\" ни в seasonality, ни в market.\n"); String prompt = promptBuilder.toString(); - logger.debug("Generating seasonality and market analysis with detailed prompt (length: {} chars)", + logger.debug( + "getSeasonalityAndMarket: Generating seasonality and market analysis with detailed prompt (length: {} chars)", prompt.length()); + logger.info("getSeasonalityAndMarket: Calling OpenAI API for seasonality and market analysis"); String jsonResponse = openAIAnalyticsService.generateWithInstruction(context, prompt, "ru"); + logger.debug("getSeasonalityAndMarket: OpenAI response received. Length: {} chars", + jsonResponse != null ? jsonResponse.length() : 0); if (jsonResponse == null || jsonResponse.trim().isEmpty()) { - logger.warn("Empty JSON response from AI for seasonality and market analysis"); + logger.warn("getSeasonalityAndMarket: Empty JSON response from AI for seasonality and market analysis"); return new HashMap<>(); } + logger.debug("getSeasonalityAndMarket: Cleaning JSON from markdown"); String cleanedJson = cleanJsonFromMarkdown(jsonResponse); + logger.debug("getSeasonalityAndMarket: Parsing JSON response"); Map jsonMap = objectMapper.readValue(cleanedJson, new TypeReference>() { }); + logger.debug("getSeasonalityAndMarket: JSON parsed successfully. Keys: {}", jsonMap.keySet()); - logger.info("Seasonality and market analysis generated successfully"); + logger.info("getSeasonalityAndMarket: Seasonality and market analysis generated successfully"); return jsonMap; } catch (Exception e) { - logger.error("Error generating seasonality and market analysis: {}", e.getMessage(), e); + logger.error("getSeasonalityAndMarket: Error generating seasonality and market analysis: {}", + e.getMessage(), e); + logger.error("getSeasonalityAndMarket: Stack trace: ", e); return new HashMap<>(); } } @@ -646,14 +701,18 @@ public class MarketingAnalysisService { * Промпт №2: Генерация текстового отчёта на основе JSON */ private String generateTextReportFromJson(Map analysisJson) { + logger.info("generateTextReportFromJson: Starting text report generation from JSON"); if (analysisJson == null || analysisJson.isEmpty()) { - logger.warn("Empty analysisJson provided for text report generation"); + logger.warn("generateTextReportFromJson: Empty analysisJson provided for text report generation"); return "Не удалось сгенерировать текстовый отчёт: отсутствуют данные JSON-анализа."; } + logger.debug("generateTextReportFromJson: AnalysisJson keys: {}", analysisJson.keySet()); try { // Преобразование JSON в строку для промпта + logger.debug("generateTextReportFromJson: Converting JSON to string"); String jsonString = objectMapper.writeValueAsString(analysisJson); + logger.debug("generateTextReportFromJson: JSON string length: {} chars", jsonString.length()); // Построение промпта №2 StringBuilder promptBuilder = new StringBuilder(); @@ -702,10 +761,14 @@ public class MarketingAnalysisService { String prompt = promptBuilder.toString(); String context = jsonString; // Используем JSON как контекст + logger.info("generateTextReportFromJson: Calling OpenAI API for text report generation"); String textReport = openAIAnalyticsService.generateWithInstruction(context, prompt, "ru"); + logger.info("generateTextReportFromJson: Text report generated. Length: {} chars", + textReport != null ? textReport.length() : 0); return textReport != null ? textReport : "Не удалось сгенерировать текстовый отчёт."; } catch (Exception e) { - logger.error("Error generating text report from JSON: {}", e.getMessage(), e); + logger.error("generateTextReportFromJson: Error generating text report from JSON: {}", e.getMessage(), e); + logger.error("generateTextReportFromJson: Stack trace: ", e); return "Ошибка при генерации текстового отчёта: " + e.getMessage(); } } @@ -715,10 +778,12 @@ public class MarketingAnalysisService { * AI-суммирования) */ private String generateDeepTextReport(Map analysisJson) { + logger.info("generateDeepTextReport: Starting deep text report generation"); if (analysisJson == null || analysisJson.isEmpty()) { - logger.warn("Empty analysisJson provided for deep text report generation"); + logger.warn("generateDeepTextReport: Empty analysisJson provided for deep text report generation"); return "Не удалось сгенерировать текстовый отчёт: отсутствуют данные JSON-анализа."; } + logger.debug("generateDeepTextReport: AnalysisJson keys: {}", analysisJson.keySet()); try { StringBuilder fullReport = new StringBuilder(); @@ -1427,11 +1492,14 @@ public class MarketingAnalysisService { * competitorAnalysis, seasonality, market */ private Map parseChartDataFromJson(Map analysisJson) { + logger.info("parseChartDataFromJson: Starting chart data parsing"); Map chartsData = new HashMap<>(); if (analysisJson == null || analysisJson.isEmpty()) { + logger.warn("parseChartDataFromJson: analysisJson is null or empty"); return chartsData; } + logger.debug("parseChartDataFromJson: AnalysisJson keys: {}", analysisJson.keySet()); try { // 1. Сезонность (структура не изменилась) @@ -1640,22 +1708,27 @@ public class MarketingAnalysisService { } } catch (Exception e) { - logger.error("Error parsing chart data from JSON: {}", e.getMessage(), e); + logger.error("parseChartDataFromJson: Error parsing chart data from JSON: {}", e.getMessage(), e); + logger.error("parseChartDataFromJson: Stack trace: ", e); } + logger.info("parseChartDataFromJson: Chart data parsing completed. Extracted keys: {}", chartsData.keySet()); return chartsData; } private Map generateMarketingReport(MarketingAnalysisRequest request) { + logger.info("generateMarketingReport: Starting report generation for product: {}", request.getProduct()); Map report = new HashMap<>(); // Get all analysis types and detail level List analysisTypes = request.getAnalysisTypeEnums(); if (analysisTypes == null || analysisTypes.isEmpty()) { + logger.warn("generateMarketingReport: No analysis types provided, using default: РЫНОК"); analysisTypes = List.of(AnalysisType.РЫНОК); // Default } DetailLevel detailLevel = request.getDetailLevelEnum(); String detailLevelName = detailLevel != null ? detailLevel.getDisplayName() : "Стандартно"; + logger.debug("generateMarketingReport: Analysis types: {}, Detail level: {}", analysisTypes, detailLevelName); // Build list of analysis type names for display List analysisTypeNames = analysisTypes.stream() @@ -1712,25 +1785,35 @@ public class MarketingAnalysisService { String context = contextBuilder.toString(); // Шаг 1: Генерация JSON-анализа (Промпт №1) + logger.info("generateMarketingReport: Starting JSON analysis generation"); Map analysisJson = generateJsonAnalysis(request); if (analysisJson == null || analysisJson.isEmpty()) { - logger.error("Failed to generate JSON analysis"); + logger.error("generateMarketingReport: Failed to generate JSON analysis"); // При ошибке возвращаем базовую структуру с сообщением об ошибке report.put("analysisTypes", analysisTypeNames); report.put("detailLevel", detailLevelName); report.put("fullAnalysis", "Не удалось сгенерировать анализ. Пожалуйста, попробуйте позже."); report.put("summary", "Не удалось сгенерировать анализ. Пожалуйста, попробуйте позже."); report.put("chartsData", new HashMap<>()); + logger.warn("generateMarketingReport: Returning report with error message"); } else { + logger.info("generateMarketingReport: JSON analysis generated successfully. Keys: {}", + analysisJson.keySet()); // Сохраняем JSON в отчёт report.put("analysisJson", analysisJson); // Шаг 2: Глубокая генерация текстового отчёта на основе JSON (4 этапа) + logger.info("generateMarketingReport: Starting deep text report generation"); String textReport = generateDeepTextReport(analysisJson); + logger.info("generateMarketingReport: Deep text report generated. Length: {} chars", + textReport != null ? textReport.length() : 0); // Извлекаем данные для графиков из JSON + logger.info("generateMarketingReport: Starting chart data parsing from JSON"); Map chartsData = parseChartDataFromJson(analysisJson); + logger.info("generateMarketingReport: Chart data parsed. Keys: {}", + chartsData != null ? chartsData.keySet() : "null"); report.put("chartsData", chartsData); // Сохраняем данные анализа @@ -1743,6 +1826,7 @@ public class MarketingAnalysisService { } // Generate target audience description (adjusted by detail level) + logger.info("generateMarketingReport: Starting target audience description generation"); String audiencePrompt; if (detailLevel == DetailLevel.КРАТКО) { audiencePrompt = "На основе информации о бизнесе опиши целевую аудиторию кратко (1 абзац). " + @@ -1759,14 +1843,21 @@ public class MarketingAnalysisService { "Укажи, какие каналы коммуникации наиболее подходят для этой аудитории. " + "Ответ должен быть на русском языке.\n\n" + context; } + logger.debug("generateMarketingReport: Calling OpenAI for audience description"); String audienceDescription = openAIAnalyticsService.generateWithInstruction(context, audiencePrompt, "ru"); + logger.info("generateMarketingReport: Audience description generated. Length: {} chars", + audienceDescription != null ? audienceDescription.length() : 0); // Extract channels from description or generate separately + logger.info("generateMarketingReport: Starting channels generation"); String channelsPrompt = "На основе описания бизнеса перечисли 3-5 наиболее подходящих маркетинговых каналов. " + "Ответ должен быть простым списком через запятую, без нумерации. Пример: Instagram, LinkedIn, Telegram\n\n" + context; + logger.debug("generateMarketingReport: Calling OpenAI for channels"); String channelsStr = openAIAnalyticsService.generateWithInstruction(context, channelsPrompt, "ru"); + logger.debug("generateMarketingReport: Channels response received: {}", channelsStr); List channels = parseChannels(channelsStr); + logger.info("generateMarketingReport: Channels parsed. Count: {}", channels.size()); Map targetAudience = new HashMap<>(); targetAudience.put("description", audienceDescription != null ? audienceDescription @@ -1776,6 +1867,7 @@ public class MarketingAnalysisService { report.put("targetAudience", targetAudience); // Generate recommendations (adjusted by detail level) + logger.info("generateMarketingReport: Starting recommendations generation"); String recommendationsPrompt; if (detailLevel == DetailLevel.КРАТКО) { recommendationsPrompt = "На основе информации о бизнесе сформулируй 3-4 краткие практические рекомендации для маркетинговой стратегии. " @@ -1793,13 +1885,18 @@ public class MarketingAnalysisService { "Каждая рекомендация должна быть конкретной и применимой. " + "Ответ должен быть списком рекомендаций, каждая с новой строки, без нумерации.\n\n" + context; } + logger.debug("generateMarketingReport: Calling OpenAI for recommendations"); String recommendationsStr = openAIAnalyticsService.generateWithInstruction(context, recommendationsPrompt, "ru"); + logger.debug("generateMarketingReport: Recommendations response received. Length: {} chars", + recommendationsStr != null ? recommendationsStr.length() : 0); List recommendations = parseRecommendations(recommendationsStr); + logger.info("generateMarketingReport: Recommendations parsed. Count: {}", recommendations.size()); report.put("recommendations", recommendations.isEmpty() ? Arrays.asList("Рекомендации не удалось сгенерировать.") : recommendations); // Generate strategy (adjusted by detail level) + logger.info("generateMarketingReport: Starting strategy generation"); String strategyPrompt; if (detailLevel == DetailLevel.КРАТКО) { strategyPrompt = "На основе информации о бизнесе создай краткую маркетинговую стратегию. " + @@ -1819,10 +1916,14 @@ public class MarketingAnalysisService { "3-5 каналов коммуникации и типы контента (например, 'посты', 'сторис', 'баннеры'). " + "Ответ должен быть структурированным текстом на русском языке.\n\n" + context; } + logger.debug("generateMarketingReport: Calling OpenAI for strategy"); String strategyText = openAIAnalyticsService.generateWithInstruction(context, strategyPrompt, "ru"); + logger.info("generateMarketingReport: Strategy generated. Length: {} chars", + strategyText != null ? strategyText.length() : 0); Map strategy = parseStrategy(strategyText, channels); report.put("strategy", strategy); + logger.info("generateMarketingReport: Report generation completed. Report keys: {}", report.keySet()); return report; } @@ -1975,6 +2076,8 @@ public class MarketingAnalysisService { } private String buildMarkdownFromReport(Map reportData, MarketingAnalysisRequest request) { + logger.info("buildMarkdownFromReport: Starting markdown generation for product: {}", request.getProduct()); + logger.debug("buildMarkdownFromReport: ReportData keys: {}", reportData != null ? reportData.keySet() : "null"); StringBuilder markdown = new StringBuilder(); markdown.append("# Маркетинговый анализ\n\n"); @@ -2248,11 +2351,18 @@ public class MarketingAnalysisService { * @return текст отчета с замененными плейсхолдерами на JSON-блоки */ private String replaceChartPlaceholders(String fullAnalysis, Map chartsData) { + logger.info("replaceChartPlaceholders: Starting placeholder replacement"); + logger.debug("replaceChartPlaceholders: FullAnalysis length: {}, ChartsData keys: {}", + fullAnalysis != null ? fullAnalysis.length() : 0, + chartsData != null ? chartsData.keySet() : "null"); + if (fullAnalysis == null || fullAnalysis.trim().isEmpty()) { + logger.warn("replaceChartPlaceholders: fullAnalysis is null or empty"); return fullAnalysis; } if (chartsData == null || chartsData.isEmpty()) { + logger.warn("replaceChartPlaceholders: chartsData is null or empty, removing all placeholders"); // Если данных нет, просто удаляем все плейсхолдеры return fullAnalysis .replace("[[CHART_SEASONALITY]]", "") @@ -2303,8 +2413,12 @@ public class MarketingAnalysisService { // Fallback: добавляем графики в конец соответствующих разделов, если // плейсхолдер отсутствовал + logger.debug("replaceChartPlaceholders: Adding missing charts to sections"); result = addMissingChartsToSections(result, chartsData, placeholderMap, processedPlaceholders); + logger.info( + "replaceChartPlaceholders: Placeholder replacement completed. Processed placeholders: {}, Result length: {} chars", + processedPlaceholders.size(), result.length()); return result; }