diff --git a/src/main/java/kz/konturai/parser/controller/MarketingController.java b/src/main/java/kz/konturai/parser/controller/MarketingController.java index 2d33fd4..40de561 100644 --- a/src/main/java/kz/konturai/parser/controller/MarketingController.java +++ b/src/main/java/kz/konturai/parser/controller/MarketingController.java @@ -85,45 +85,29 @@ public class MarketingController { return unauthorizedResponse(); } - // Create analysis records (one for each analysis type) + // Create unified analysis with all analysis types List analyses = marketingAnalysisService.startAnalysis(request, userId); + MarketingAnalysis analysis = analyses.get(0); - // Start async processing for each analysis - for (MarketingAnalysis analysis : analyses) { - marketingAnalysisService.processAnalysis(analysis.getId(), request); - } + // Start async processing + marketingAnalysisService.processAnalysis(analysis.getId(), request); // Calculate estimated completion time (5-10 minutes) LocalDateTime estimatedCompletion = LocalDateTime.now().plusMinutes(8); - // If multiple analyses, return list of responses - if (analyses.size() > 1) { - List responses = analyses.stream() - .map(analysis -> new MarketingAnalysisResponse( - analysis.getId(), - "processing", - estimatedCompletion, - "Анализ типа '" + - (analysis.getAnalysisType() != null && !analysis - .getAnalysisType().isEmpty() - ? analysis.getAnalysisType() - .get(0) - : "неизвестно") - + - "' запущен успешно. Результаты будут готовы в течение 5-10 минут.")) - .collect(Collectors.toList()); - return ResponseEntity.ok(ApiResponse.success( - "Создано анализов: " + analyses.size() + ". Все анализы запущены успешно.", - responses)); - } else { - MarketingAnalysis analysis = analyses.get(0); - MarketingAnalysisResponse response = new MarketingAnalysisResponse( - analysis.getId(), - "processing", - estimatedCompletion, - "Анализ запущен успешно. Результаты будут готовы в течение 5-10 минут."); - return ResponseEntity.ok(ApiResponse.success("Анализ запущен успешно", response)); + // Build analysis types string for message + String analysisTypesStr = "неизвестно"; + if (analysis.getAnalysisType() != null && !analysis.getAnalysisType().isEmpty()) { + analysisTypesStr = String.join(", ", analysis.getAnalysisType()); } + + MarketingAnalysisResponse response = new MarketingAnalysisResponse( + analysis.getId(), + "processing", + estimatedCompletion, + "Комплексный анализ (" + analysisTypesStr + + ") запущен успешно. Результаты будут готовы в течение 5-10 минут."); + return ResponseEntity.ok(ApiResponse.success("Анализ запущен успешно", response)); } @GetMapping("/{analysisId}") diff --git a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java index d020fca..860bd33 100644 --- a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java +++ b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java @@ -43,27 +43,26 @@ public class MarketingAnalysisService { throw new IllegalArgumentException("Необходимо выбрать хотя бы один тип анализа"); } - List analyses = new ArrayList<>(); - for (String analysisType : analysisTypes) { - MarketingAnalysis analysis = new MarketingAnalysis( - request.getBusinessNiche(), - request.getProduct(), - request.getTargetAudience(), - request.getRegion(), - request.getGoal(), - request.getDetailLevel(), - request.getStrongSide(), - request.getWeakSide(), - List.of(analysisType)); // Каждый анализ имеет один тип - analysis.setUserId(userId); - analysis.setStatus("queued"); - addStatusHistoryEntry(analysis, "queued", "Анализ создан и добавлен в очередь"); - analysis = repository.save(analysis); - analyses.add(analysis); - logger.info("Marketing analysis created with ID: {} for user: {} with type: {}", - analysis.getId(), userId, analysisType); - } - return analyses; + // Create a single unified analysis with all analysis types + MarketingAnalysis analysis = new MarketingAnalysis( + request.getBusinessNiche(), + request.getProduct(), + request.getTargetAudience(), + request.getRegion(), + request.getGoal(), + request.getDetailLevel(), + request.getStrongSide(), + request.getWeakSide(), + analysisTypes); // Все типы анализа в одном анализе + analysis.setUserId(userId); + analysis.setStatus("queued"); + addStatusHistoryEntry(analysis, "queued", "Комплексный анализ создан и добавлен в очередь"); + analysis = repository.save(analysis); + + logger.info("Unified marketing analysis created with ID: {} for user: {} with types: {}", + analysis.getId(), userId, analysisTypes); + + return List.of(analysis); } private void addStatusHistoryEntry(MarketingAnalysis analysis, String status, String message) { @@ -90,19 +89,15 @@ public class MarketingAnalysisService { logger.info("Starting marketing analysis processing for ID: {}", analysisId); - // Create a modified request with single analysis type for this analysis - MarketingAnalysisRequest singleTypeRequest = createSingleTypeRequest(request, - analysis.getAnalysisType().get(0)); - - // Generate marketing report - Map reportData = generateMarketingReport(singleTypeRequest); + // Generate marketing report with all analysis types + Map reportData = generateMarketingReport(request); // Generate markdown content for PDF - String markdownContent = buildMarkdownFromReport(reportData, singleTypeRequest); + String markdownContent = buildMarkdownFromReport(reportData, request); // Generate PDF byte[] pdfBytes = researchPdfService.generatePdfReport( - "Маркетинговый анализ: " + singleTypeRequest.getProduct(), + "Маркетинговый анализ: " + request.getProduct(), markdownContent, new ArrayList<>()); @@ -137,30 +132,21 @@ public class MarketingAnalysisService { } } - private MarketingAnalysisRequest createSingleTypeRequest(MarketingAnalysisRequest originalRequest, - String analysisType) { - MarketingAnalysisRequest singleTypeRequest = new MarketingAnalysisRequest(); - singleTypeRequest.setBusinessNiche(originalRequest.getBusinessNiche()); - singleTypeRequest.setProduct(originalRequest.getProduct()); - singleTypeRequest.setTargetAudience(originalRequest.getTargetAudience()); - singleTypeRequest.setRegion(originalRequest.getRegion()); - singleTypeRequest.setGoal(originalRequest.getGoal()); - singleTypeRequest.setDetailLevel(originalRequest.getDetailLevel()); - singleTypeRequest.setStrongSide(originalRequest.getStrongSide()); - singleTypeRequest.setWeakSide(originalRequest.getWeakSide()); - singleTypeRequest.setAnalysisType(List.of(analysisType)); - return singleTypeRequest; - } - private Map generateMarketingReport(MarketingAnalysisRequest request) { Map report = new HashMap<>(); - // Get analysis type and detail level - теперь всегда один тип + // Get all analysis types and detail level List analysisTypes = request.getAnalysisTypeEnums(); - AnalysisType analysisType = analysisTypes != null && !analysisTypes.isEmpty() ? analysisTypes.get(0) : null; - String analysisTypeName = analysisType != null ? analysisType.getDisplayName() : "Общий"; + if (analysisTypes == null || analysisTypes.isEmpty()) { + analysisTypes = List.of(AnalysisType.РЫНОК); // Default + } DetailLevel detailLevel = request.getDetailLevelEnum(); String detailLevelName = detailLevel != null ? detailLevel.getDisplayName() : "Стандартно"; + + // Build list of analysis type names for display + List analysisTypeNames = analysisTypes.stream() + .map(AnalysisType::getDisplayName) + .toList(); // Build context for AI with all new fields StringBuilder contextBuilder = new StringBuilder(); @@ -208,14 +194,16 @@ public class MarketingAnalysisService { if (request.getWeakSide() != null && !request.getWeakSide().trim().isEmpty()) { contextBuilder.append("Слабая сторона: ").append(request.getWeakSide()).append("\n"); } - contextBuilder.append("Тип анализа: ").append(analysisTypeName).append("\n"); + contextBuilder.append("Типы анализа: ").append(String.join(", ", analysisTypeNames)).append("\n"); String context = contextBuilder.toString(); - // Generate analysis based on type and detail level - String analysisContent = generateAnalysisByType(context, analysisType, detailLevel); - report.put("analysisType", analysisTypeName); + // Generate unified analysis with all types + String unifiedAnalysisContent = generateUnifiedAnalysis(context, analysisTypes, detailLevel); + + // Store analysis data + report.put("analysisTypes", analysisTypeNames); report.put("detailLevel", detailLevelName); - report.put("summary", analysisContent != null ? analysisContent : "Анализ не удалось сгенерировать."); + report.put("summary", unifiedAnalysisContent != null ? unifiedAnalysisContent : "Анализ не удалось сгенерировать."); // Generate target audience description (adjusted by detail level) String audiencePrompt; @@ -302,6 +290,79 @@ public class MarketingAnalysisService { return report; } + private String generateUnifiedAnalysis(String context, List analysisTypes, DetailLevel detailLevel) { + if (analysisTypes == null || analysisTypes.isEmpty()) { + analysisTypes = List.of(AnalysisType.РЫНОК); // Default + } + if (detailLevel == null) { + detailLevel = DetailLevel.СТАНДАРТНО; // Default + } + + String lengthInstruction; + // Adjust length based on detail level + if (detailLevel == DetailLevel.КРАТКО) { + lengthInstruction = "Каждый раздел должен быть кратким (1-2 абзаца)."; + } else if (detailLevel == DetailLevel.ПОДРОБНО) { + lengthInstruction = "Каждый раздел должен быть подробным (5-8 абзацев) с глубоким анализом."; + } else { + lengthInstruction = "Каждый раздел должен быть структурированным (3-5 абзацев)."; + } + + StringBuilder promptBuilder = new StringBuilder(); + promptBuilder.append("Проведи комплексный маркетинговый анализ на основе предоставленной информации о бизнесе. "); + promptBuilder.append("Анализ должен быть качественным, прикладным к конкретной тематике и задаче.\n\n"); + + promptBuilder.append("Включи следующие разделы анализа:\n\n"); + + // Add section for each analysis type + int sectionNumber = 1; + for (AnalysisType analysisType : analysisTypes) { + promptBuilder.append(sectionNumber).append(". "); + + switch (analysisType) { + case РЫНОК: + promptBuilder.append("Анализ рынка: "); + promptBuilder.append("Включи размер рынка, динамику роста, основные сегменты, тренды и перспективы развития.\n"); + break; + case КОНКУРЕНТЫ: + promptBuilder.append("Анализ конкурентов: "); + promptBuilder.append("Включи основных конкурентов, их сильные и слабые стороны, позиционирование, "); + promptBuilder.append("ценовую политику и маркетинговые стратегии.\n"); + break; + case ЦА: + promptBuilder.append("Анализ целевой аудитории: "); + promptBuilder.append("Включи демографические характеристики, психографический профиль, потребности и боли, "); + promptBuilder.append("поведенческие паттерны и предпочтения.\n"); + break; + case КАНАЛЫ: + promptBuilder.append("Анализ маркетинговых каналов: "); + promptBuilder.append("Включи оценку эффективности различных каналов коммуникации, рекомендации по выбору каналов, "); + promptBuilder.append("особенности использования каждого канала и бюджетные рекомендации.\n"); + break; + case SWOT: + promptBuilder.append("SWOT-анализ: "); + promptBuilder.append("Включи анализ Сильных сторон (Strengths), Слабых сторон (Weaknesses), "); + promptBuilder.append("Возможностей (Opportunities) и Угроз (Threats). "); + promptBuilder.append("Ответ должен быть структурированным с четким разделением по каждому разделу SWOT.\n"); + break; + } + sectionNumber++; + } + + promptBuilder.append("\n"); + promptBuilder.append(sectionNumber).append(". Общий вывод: "); + promptBuilder.append("Объедини все разделы анализа в единый комплексный вывод, "); + promptBuilder.append("выдели ключевые возможности и особенности бизнеса, "); + promptBuilder.append("сформулируй прикладные рекомендации, применимые к конкретной тематике и задаче.\n\n"); + + promptBuilder.append(lengthInstruction).append(" "); + promptBuilder.append("Ответ должен быть на русском языке.\n\n"); + promptBuilder.append(context); + + String prompt = promptBuilder.toString(); + return openAIAnalyticsService.generateWithInstruction(context, prompt, "ru"); + } + private String generateAnalysisByType(String context, AnalysisType analysisType, DetailLevel detailLevel) { if (analysisType == null) { analysisType = AnalysisType.РЫНОК; // Default @@ -505,15 +566,19 @@ public class MarketingAnalysisService { markdown.append("- **Слабая сторона:** ").append(request.getWeakSide()).append("\n"); } - String analysisTypeName = (String) reportData.get("analysisType"); - if (analysisTypeName != null) { - markdown.append("- **Тип анализа:** ").append(analysisTypeName).append("\n"); + @SuppressWarnings("unchecked") + List analysisTypeNames = (List) reportData.get("analysisTypes"); + if (analysisTypeNames != null && !analysisTypeNames.isEmpty()) { + markdown.append("- **Типы анализа:** ").append(String.join(", ", analysisTypeNames)).append("\n"); } markdown.append("\n"); - String analysisTitle = analysisTypeName != null ? analysisTypeName : "Резюме анализа"; - markdown.append("## ").append(analysisTitle).append("\n\n"); - markdown.append(reportData.get("summary")).append("\n\n"); + // Display unified analysis + String summary = (String) reportData.get("summary"); + if (summary != null && !summary.trim().isEmpty()) { + markdown.append("## Комплексный маркетинговый анализ\n\n"); + markdown.append(summary).append("\n\n"); + } @SuppressWarnings("unchecked") Map targetAudience = (Map) reportData.get("targetAudience");