This commit is contained in:
root
2025-12-09 22:17:32 +05:00
parent 0807280d5d
commit 7f77f48020
@@ -62,10 +62,10 @@ public class MarketingAnalysisService {
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);
}
@@ -173,25 +173,30 @@ public class MarketingAnalysisService {
// Форматирование региона
List<String> regions = request.getRegion();
String regionStr = (regions != null && !regions.isEmpty())
? String.join(", ", regions)
: "Не указан";
String regionStr = (regions != null && !regions.isEmpty())
? String.join(", ", regions)
: "Не указан";
// Построение промпта №1
StringBuilder promptBuilder = new StringBuilder();
promptBuilder.append("Ты — профессиональный маркетинговый аналитик, работающий с малым и средним бизнесом в Казахстане.\n\n");
promptBuilder.append("Твоя задача — на основе входных данных о бизнесе пользователя выполнить маркетинговый анализ и выдать результат ТОЛЬКО в виде корректного JSON-объекта строго по указанной структуре.\n\n");
promptBuilder.append(
"Ты — профессиональный маркетинговый аналитик, работающий с малым и средним бизнесом в Казахстане.\n\n");
promptBuilder.append(
"Твоя задача — на основе входных данных о бизнесе пользователя выполнить маркетинговый анализ и выдать результат ТОЛЬКО в виде корректного JSON-объекта строго по указанной структуре.\n\n");
promptBuilder.append("Не добавляй пояснений, комментариев, текста до или после JSON.\n");
promptBuilder.append("Ответ должен быть валидным JSON.\n\n");
promptBuilder.append("---\n\n");
promptBuilder.append("ВХОДНЫЕ ДАННЫЕ ПОЛЬЗОВАТЕЛЯ:\n\n");
promptBuilder.append("- Название компании/продукта: ").append(request.getProduct()).append("\n");
promptBuilder.append("- Описание продукта/услуг: ").append(request.getProduct()).append(" (").append(request.getBusinessNiche()).append(")\n");
promptBuilder.append("- Описание продукта/услуг: ").append(request.getProduct()).append(" (")
.append(request.getBusinessNiche()).append(")\n");
promptBuilder.append("- Целевая аудитория (если указана): ").append(targetAudienceStr).append("\n");
promptBuilder.append("- Конкуренты (если указаны): ").append(request.getBusinessNiche()).append(" (уточни основных конкурентов)\n");
promptBuilder.append("- Конкуренты (если указаны): ").append(request.getBusinessNiche())
.append(" (уточни основных конкурентов)\n");
promptBuilder.append("- Цель продвижения: ").append(request.getGoal()).append("\n");
promptBuilder.append("- Регион/город: ").append(regionStr).append("\n\n");
promptBuilder.append("## Если каких-то данных не хватает, ты можешь логично дополнить предположениями, НО в таких полях добавь флаг \"estimated\": true.\n\n");
promptBuilder.append(
"## Если каких-то данных не хватает, ты можешь логично дополнить предположениями, НО в таких полях добавь флаг \"estimated\": true.\n\n");
promptBuilder.append("СФОРМИРУЙ ОДИН JSON СЛЕДУЮЩЕЙ СТРУКТУРЫ:\n\n");
promptBuilder.append("{\n");
promptBuilder.append("\"meta\": {\n");
@@ -233,7 +238,8 @@ public class MarketingAnalysisService {
promptBuilder.append("},\n\n");
promptBuilder.append("\"channels\": [\n");
promptBuilder.append("{\n");
promptBuilder.append("\"name\": string, // \"Instagram\", \"TikTok\", \"Telegram\", \"2GIS\", \"Google Ads\" и т.п.\n");
promptBuilder.append(
"\"name\": string, // \"Instagram\", \"TikTok\", \"Telegram\", \"2GIS\", \"Google Ads\" и т.п.\n");
promptBuilder.append("\"potentialIndex\": integer, // 0100\n");
promptBuilder.append("\"priorityLevel\": string, // \"высокий\", \"средний\", \"низкий\"\n");
promptBuilder.append("\"roleInStrategy\": string, // коротко: для чего этот канал\n");
@@ -245,7 +251,8 @@ public class MarketingAnalysisService {
promptBuilder.append("\"description\": string,\n");
promptBuilder.append("\"stages\": [\n");
promptBuilder.append("{\n");
promptBuilder.append("\"stageName\": string, // \"Охват\", \"Вовлечённость\", \"Клики\", \"Переходы\", \"Заявки\", \"Покупки\"\n");
promptBuilder.append(
"\"stageName\": string, // \"Охват\", \"Вовлечённость\", \"Клики\", \"Переходы\", \"Заявки\", \"Покупки\"\n");
promptBuilder.append("\"value\": integer, // количество (должно логично уменьшаться вниз по воронке)\n");
promptBuilder.append("\"conversionPercent\": number // от предыдущего этапа, с одним знаком после запятой\n");
promptBuilder.append("}\n");
@@ -287,7 +294,7 @@ public class MarketingAnalysisService {
String prompt = promptBuilder.toString();
String context = buildContextString(request);
try {
String jsonResponse = openAIAnalyticsService.generateWithInstruction(context, prompt, "ru");
if (jsonResponse == null || jsonResponse.trim().isEmpty()) {
@@ -309,12 +316,13 @@ public class MarketingAnalysisService {
cleanedJson = cleanedJson.trim();
// Парсинг JSON
Map<String, Object> jsonMap = objectMapper.readValue(cleanedJson, new TypeReference<Map<String, Object>>() {});
Map<String, Object> jsonMap = objectMapper.readValue(cleanedJson, new TypeReference<Map<String, Object>>() {
});
// Валидация JSON
if (validateJsonAnalysis(jsonMap)) {
return jsonMap;
} else {
} else {
logger.warn("JSON validation failed, but returning parsed JSON anyway");
return jsonMap;
}
@@ -342,7 +350,8 @@ public class MarketingAnalysisService {
promptBuilder.append("Ты — профессиональный маркетолог и аналитик.\n\n");
promptBuilder.append("Тебе передан JSON с результатами маркетингового анализа в следующей структуре:\n");
promptBuilder.append(jsonString).append("\n\n");
promptBuilder.append("На основе этих данных подготовь ПОНЯТНЫЙ для предпринимателя малого/среднего бизнеса текстовый отчёт.\n\n");
promptBuilder.append(
"На основе этих данных подготовь ПОНЯТНЫЙ для предпринимателя малого/среднего бизнеса текстовый отчёт.\n\n");
promptBuilder.append("ТРЕБОВАНИЯ К ОТЧЁТУ:\n\n");
promptBuilder.append("1. Структура разделов:\n");
promptBuilder.append(" I. Краткое резюме (что за бизнес, какая цель, общий вывод)\n");
@@ -393,10 +402,10 @@ public class MarketingAnalysisService {
try {
// Проверка обязательных полей
if (!jsonMap.containsKey("meta") || !jsonMap.containsKey("seasonality") ||
!jsonMap.containsKey("competitors") || !jsonMap.containsKey("audienceSegments") ||
!jsonMap.containsKey("channels") || !jsonMap.containsKey("funnel") ||
!jsonMap.containsKey("swot")) {
if (!jsonMap.containsKey("meta") || !jsonMap.containsKey("seasonality") ||
!jsonMap.containsKey("competitors") || !jsonMap.containsKey("audienceSegments") ||
!jsonMap.containsKey("channels") || !jsonMap.containsKey("funnel") ||
!jsonMap.containsKey("swot")) {
logger.warn("Missing required fields in JSON analysis");
return false;
}
@@ -504,7 +513,7 @@ public class MarketingAnalysisService {
*/
private Map<String, Object> parseChartDataFromJson(Map<String, Object> analysisJson) {
Map<String, Object> chartsData = new HashMap<>();
if (analysisJson == null || analysisJson.isEmpty()) {
return chartsData;
}
@@ -518,7 +527,7 @@ public class MarketingAnalysisService {
List<String> months = (List<String>) seasonality.get("months");
@SuppressWarnings("unchecked")
List<Object> demandIndex = (List<Object>) seasonality.get("demandIndex");
if (months != null && demandIndex != null && months.size() == demandIndex.size()) {
Map<String, Integer> seasonalityMap = new LinkedHashMap<>();
for (int i = 0; i < months.size() && i < demandIndex.size(); i++) {
@@ -560,37 +569,37 @@ public class MarketingAnalysisService {
List<Map<String, Object>> segments = (List<Map<String, Object>>) audienceSegments.get("segments");
if (segments != null && !segments.isEmpty()) {
Map<String, Object> segmentationData = new HashMap<>();
// Группировка по возрасту
Map<String, Integer> ageGroups = new HashMap<>();
Map<String, Integer> genders = new HashMap<>();
List<Map<String, String>> segmentsList = new ArrayList<>();
for (Map<String, Object> segment : segments) {
String name = (String) segment.getOrDefault("name", "Сегмент");
Object percentObj = segment.get("sharePercent");
int percent = percentObj instanceof Number ? ((Number) percentObj).intValue() : 0;
String ageRange = (String) segment.getOrDefault("ageRange", "");
String gender = (String) segment.getOrDefault("gender", "");
// Добавляем в общий список сегментов
Map<String, String> segmentData = new HashMap<>();
segmentData.put("label", name);
segmentData.put("value", String.valueOf(percent));
segmentsList.add(segmentData);
// Группируем по возрасту
if (!ageRange.isEmpty()) {
ageGroups.put(ageRange, ageGroups.getOrDefault(ageRange, 0) + percent);
}
// Группируем по полу
if (!gender.isEmpty()) {
genders.put(gender, genders.getOrDefault(gender, 0) + percent);
}
}
// Преобразуем в нужный формат
List<Map<String, String>> ageGroupsList = new ArrayList<>();
for (Map.Entry<String, Integer> entry : ageGroups.entrySet()) {
@@ -599,7 +608,7 @@ public class MarketingAnalysisService {
ageGroup.put("value", String.valueOf(entry.getValue()));
ageGroupsList.add(ageGroup);
}
List<Map<String, String>> gendersList = new ArrayList<>();
for (Map.Entry<String, Integer> entry : genders.entrySet()) {
Map<String, String> genderData = new HashMap<>();
@@ -607,7 +616,7 @@ public class MarketingAnalysisService {
genderData.put("value", String.valueOf(entry.getValue()));
gendersList.add(genderData);
}
if (!ageGroupsList.isEmpty()) {
segmentationData.put("ageGroups", ageGroupsList);
}
@@ -617,7 +626,7 @@ public class MarketingAnalysisService {
if (!segmentsList.isEmpty()) {
segmentationData.put("segments", segmentsList);
}
if (!segmentationData.isEmpty()) {
chartsData.put("audienceSegmentation", segmentationData);
}
@@ -651,11 +660,11 @@ public class MarketingAnalysisService {
Map<String, Object> stageData = new HashMap<>();
stageData.put("stage", stage.getOrDefault("stageName", "Этап"));
stageData.put("value", stage.getOrDefault("value", 0));
Object conversionObj = stage.get("conversionPercent");
if (conversionObj instanceof Number) {
stageData.put("conversion", ((Number) conversionObj).intValue());
} else {
} else {
stageData.put("conversion", 0);
}
funnelList.add(stageData);
@@ -688,7 +697,7 @@ public class MarketingAnalysisService {
}
DetailLevel detailLevel = request.getDetailLevelEnum();
String detailLevelName = detailLevel != null ? detailLevel.getDisplayName() : "Стандартно";
// Build list of analysis type names for display
List<String> analysisTypeNames = analysisTypes.stream()
.map(AnalysisType::getDisplayName)
@@ -726,7 +735,7 @@ public class MarketingAnalysisService {
}
contextBuilder.append("Целевая аудитория: ").append(audienceBuilder.toString().trim()).append("\n");
}
// Format regions
List<String> regions = request.getRegion();
if (regions != null && !regions.isEmpty()) {
@@ -745,7 +754,7 @@ public class MarketingAnalysisService {
// Шаг 1: Генерация JSON-анализа (Промпт №1)
Map<String, Object> analysisJson = generateJsonAnalysis(request);
if (analysisJson == null || analysisJson.isEmpty()) {
logger.error("Failed to generate JSON analysis");
// При ошибке возвращаем базовую структуру с сообщением об ошибке
@@ -757,19 +766,21 @@ public class MarketingAnalysisService {
} else {
// Сохраняем JSON в отчёт
report.put("analysisJson", analysisJson);
// Шаг 2: Генерация текстового отчёта на основе JSON (Промпт №2)
String textReport = generateTextReportFromJson(analysisJson);
// Извлекаем данные для графиков из JSON
Map<String, Object> chartsData = parseChartDataFromJson(analysisJson);
report.put("chartsData", chartsData);
// Сохраняем данные анализа
report.put("analysisTypes", analysisTypeNames);
report.put("detailLevel", detailLevelName);
report.put("fullAnalysis", textReport != null ? textReport : "Анализ не удалось сгенерировать.");
report.put("summary", textReport != null ? textReport : "Анализ не удалось сгенерировать."); // Keep for backward compatibility
report.put("summary", textReport != null ? textReport : "Анализ не удалось сгенерировать."); // Keep for
// backward
// compatibility
}
// Generate target audience description (adjusted by detail level)
@@ -1072,7 +1083,7 @@ public class MarketingAnalysisService {
Map<String, Object> chartsData = (Map<String, Object>) reportData.get("chartsData");
if (chartsData != null && !chartsData.isEmpty()) {
markdown.append("## 📊 Числовые данные для графиков\n\n");
// Seasonality table
@SuppressWarnings("unchecked")
Map<String, Integer> seasonality = (Map<String, Integer>) chartsData.get("seasonality");
@@ -1085,14 +1096,16 @@ public class MarketingAnalysisService {
}
markdown.append("\n");
}
// Competitors table
@SuppressWarnings("unchecked")
List<Map<String, Object>> competitors = (List<Map<String, Object>>) chartsData.get("competitors");
if (competitors != null && !competitors.isEmpty()) {
markdown.append("### 2️⃣ Сравнительный анализ конкурентов\n\n");
markdown.append("| Конкурент | Охват аудитории | Активность в соцсетях (0–100) | Количество отзывов | Сильные стороны | Слабые стороны |\n");
markdown.append("|-----------|-----------------|-------------------------------|-------------------|----------------|----------------|\n");
markdown.append(
"| Конкурент | Охват аудитории | Активность в соцсетях (0–100) | Количество отзывов | Сильные стороны | Слабые стороны |\n");
markdown.append(
"|-----------|-----------------|-------------------------------|-------------------|----------------|----------------|\n");
for (Map<String, Object> comp : competitors) {
markdown.append("| ").append(comp.get("name")).append(" | ")
.append(comp.get("reach")).append(" | ")
@@ -1103,13 +1116,13 @@ public class MarketingAnalysisService {
}
markdown.append("\n");
}
// Audience segmentation tables
@SuppressWarnings("unchecked")
Map<String, Object> audienceSegmentation = (Map<String, Object>) chartsData.get("audienceSegmentation");
if (audienceSegmentation != null && !audienceSegmentation.isEmpty()) {
markdown.append("### 3️⃣ Сегментация целевой аудитории\n\n");
@SuppressWarnings("unchecked")
List<Map<String, String>> ageGroups = (List<Map<String, String>>) audienceSegmentation.get("ageGroups");
if (ageGroups != null && !ageGroups.isEmpty()) {
@@ -1117,11 +1130,12 @@ public class MarketingAnalysisService {
markdown.append("| Возрастная группа | Процент (%) |\n");
markdown.append("|-------------------|-------------|\n");
for (Map<String, String> group : ageGroups) {
markdown.append("| ").append(group.get("label")).append(" | ").append(group.get("value")).append(" |\n");
markdown.append("| ").append(group.get("label")).append(" | ").append(group.get("value"))
.append(" |\n");
}
markdown.append("\n");
}
@SuppressWarnings("unchecked")
List<Map<String, String>> genders = (List<Map<String, String>>) audienceSegmentation.get("genders");
if (genders != null && !genders.isEmpty()) {
@@ -1129,11 +1143,12 @@ public class MarketingAnalysisService {
markdown.append("| Пол | Процент (%) |\n");
markdown.append("|-----|-------------|\n");
for (Map<String, String> gender : genders) {
markdown.append("| ").append(gender.get("label")).append(" | ").append(gender.get("value")).append(" |\n");
markdown.append("| ").append(gender.get("label")).append(" | ").append(gender.get("value"))
.append(" |\n");
}
markdown.append("\n");
}
@SuppressWarnings("unchecked")
List<Map<String, String>> segments = (List<Map<String, String>>) audienceSegmentation.get("segments");
if (segments != null && !segments.isEmpty()) {
@@ -1141,15 +1156,17 @@ public class MarketingAnalysisService {
markdown.append("| Сегмент | Процент (%) |\n");
markdown.append("|---------|-------------|\n");
for (Map<String, String> segment : segments) {
markdown.append("| ").append(segment.get("label")).append(" | ").append(segment.get("value")).append(" |\n");
markdown.append("| ").append(segment.get("label")).append(" | ").append(segment.get("value"))
.append(" |\n");
}
markdown.append("\n");
}
}
// Channels potential table
@SuppressWarnings("unchecked")
List<Map<String, Object>> channelsPotential = (List<Map<String, Object>>) chartsData.get("channelsPotential");
List<Map<String, Object>> channelsPotential = (List<Map<String, Object>>) chartsData
.get("channelsPotential");
if (channelsPotential != null && !channelsPotential.isEmpty()) {
markdown.append("### 4️⃣ Потенциал каналов продвижения\n\n");
markdown.append("| Канал | Потенциал (0–100) | Обоснование |\n");
@@ -1161,7 +1178,7 @@ public class MarketingAnalysisService {
}
markdown.append("\n");
}
// Conversion funnel table
@SuppressWarnings("unchecked")
List<Map<String, Object>> conversionFunnel = (List<Map<String, Object>>) chartsData.get("conversionFunnel");
@@ -1273,7 +1290,8 @@ public class MarketingAnalysisService {
// Content Recommendations section
@SuppressWarnings("unchecked")
Map<String, Object> contentRecommendations = (Map<String, Object>) analysisJson.get("contentRecommendations");
Map<String, Object> contentRecommendations = (Map<String, Object>) analysisJson
.get("contentRecommendations");
if (contentRecommendations != null) {
markdown.append("## Рекомендации по контенту\n\n");
@SuppressWarnings("unchecked")
@@ -1322,10 +1340,10 @@ public class MarketingAnalysisService {
markdown.append(fullAnalysis).append("\n\n");
} else {
// Fallback to summary if fullAnalysis is not available
String summary = (String) reportData.get("summary");
if (summary != null && !summary.trim().isEmpty()) {
markdown.append("## Комплексный маркетинговый анализ\n\n");
markdown.append(summary).append("\n\n");
String summary = (String) reportData.get("summary");
if (summary != null && !summary.trim().isEmpty()) {
markdown.append("## Комплексный маркетинговый анализ\n\n");
markdown.append(summary).append("\n\n");
}
}
@@ -1429,7 +1447,7 @@ public class MarketingAnalysisService {
// Set summary (backward compatibility)
report.setSummary((String) reportData.get("summary"));
// Set full analysis (new field)
String fullAnalysis = (String) reportData.get("fullAnalysis");
if (fullAnalysis != null) {