This commit is contained in:
root
2025-12-14 21:15:10 +05:00
parent 56ff3b7e53
commit dc8b9d261a
@@ -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<MarketingAnalysis> 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<String, Object> 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<MarketingAnalysis> 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<String, Object> jsonMap = objectMapper.readValue(cleanedJson, new TypeReference<Map<String, Object>>() {
});
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<String, Object> jsonMap = objectMapper.readValue(cleanedJson, new TypeReference<Map<String, Object>>() {
});
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<String, Object> jsonMap = objectMapper.readValue(cleanedJson, new TypeReference<Map<String, Object>>() {
});
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<String, Object> 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<String, Object> 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<String, Object> parseChartDataFromJson(Map<String, Object> analysisJson) {
logger.info("parseChartDataFromJson: Starting chart data parsing");
Map<String, Object> 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<String, Object> generateMarketingReport(MarketingAnalysisRequest request) {
logger.info("generateMarketingReport: Starting report generation for product: {}", request.getProduct());
Map<String, Object> report = new HashMap<>();
// Get all analysis types and detail level
List<AnalysisType> 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<String> 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<String, Object> 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<String, Object> 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<String> channels = parseChannels(channelsStr);
logger.info("generateMarketingReport: Channels parsed. Count: {}", channels.size());
Map<String, Object> 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<String> 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<String, Object> 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<String, Object> 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<String, Object> 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;
}