From 0a46c45c9a7b2c1fe1855332d2200819d65e4081 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 20 Dec 2025 00:42:49 +0500 Subject: [PATCH] . --- .../service/MarketingAnalysisService.java | 68 ++++++++++++------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java index 36494a7..1668a36 100644 --- a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java +++ b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java @@ -27,6 +27,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; @Service public class MarketingAnalysisService { @@ -34,6 +35,7 @@ public class MarketingAnalysisService { private static final Logger logger = LoggerFactory.getLogger(MarketingAnalysisService.class); private static final int MAX_RETRY_ATTEMPTS = 3; private static final long RETRY_DELAY_MS = 1000; + private static final AtomicInteger SECTION_THREAD_COUNTER = new AtomicInteger(0); private final MarketingAnalysisRepository repository; private final OpenAIAnalyticsService openAIAnalyticsService; @@ -48,7 +50,7 @@ public class MarketingAnalysisService { */ private final ExecutorService sectionGenerationExecutor = Executors.newFixedThreadPool(2, r -> { Thread t = new Thread(r); - t.setName("report-sections-" + t.getId()); + t.setName("report-sections-" + SECTION_THREAD_COUNTER.incrementAndGet()); t.setDaemon(true); return t; }); @@ -1690,6 +1692,10 @@ public class MarketingAnalysisService { context.put("swot", strategyAnalysis.get("globalSwot")); } } + // Даже если swot отсутствует/пустой — даём опорный контекст (для гипотез) + if (fullJson.containsKey("strategyAnalysis")) { + context.put("strategyAnalysis", fullJson.get("strategyAnalysis")); + } break; case "channels": // Новая структура: strategyAnalysis.channelsPotential (List) @@ -1710,6 +1716,11 @@ public class MarketingAnalysisService { } } } + // Даже если channelsPotential отсутствует/пустой — даём опорный контекст (для + // гипотез) + if (fullJson.containsKey("strategyAnalysis")) { + context.put("strategyAnalysis", fullJson.get("strategyAnalysis")); + } break; case "funnel": // Новая структура: strategyAnalysis.funnel (Map со stages) @@ -1725,6 +1736,10 @@ public class MarketingAnalysisService { if (fullJson.containsKey("conversionFunnel")) { context.put("conversionFunnel", fullJson.get("conversionFunnel")); } + // Даже если funnel отсутствует/пустой — даём опорный контекст (для гипотез) + if (fullJson.containsKey("strategyAnalysis")) { + context.put("strategyAnalysis", fullJson.get("strategyAnalysis")); + } break; case "positioning": if (fullJson.containsKey("positioning")) { @@ -2130,11 +2145,6 @@ public class MarketingAnalysisService { */ private String genSectionSWOT(Map contextJson) { try { - if (!hasNonEmptyValue(contextJson, "swot")) { - return "VI. SWOT-анализ\n\n" - + "Раздел пропущен: отсутствуют данные SWOT в JSON (swot / strategyAnalysis.globalSwot)."; - } - String jsonString = objectMapper.writeValueAsString(contextJson); StringBuilder promptBuilder = new StringBuilder(); promptBuilder.append("Ты — высокооплачиваемый бизнес-консультант и маркетинговый аналитик.\n\n"); @@ -2143,7 +2153,14 @@ public class MarketingAnalysisService { promptBuilder.append("1. Объем: минимум 500-700 слов\n"); promptBuilder.append("2. Стиль: деловой, убедительный для владельца бизнеса\n"); promptBuilder.append("3. Глубина: аналитические инсайты с практическими выводами\n"); - promptBuilder.append("4. Данные: используй ТОЛЬКО данные из JSON, не выдумывай цифры\n\n"); + promptBuilder.append("4. Данные: используй ТОЛЬКО данные из JSON, не выдумывай цифры\n"); + promptBuilder.append("5. РАЗДЕЛ НЕЛЬЗЯ ПРОПУСКАТЬ:\n"); + promptBuilder.append( + " - Если в JSON нет поля swot или оно неполное: сформируй SWOT как ГИПОТЕЗУ на основе product/businessNiche/goal/market/audienceAnalysis/competitorAnalysis/strategyAnalysis.\n"); + promptBuilder.append( + " - Каждый пункт, который не подтвержден напрямую JSON-данными, помечай префиксом \"Гипотеза:\".\n"); + promptBuilder.append( + " - Для каждого блока (S/W/O/T) добавь подпункт \"Какие данные собрать для подтверждения\" (3-5 пунктов).\n\n"); promptBuilder.append("СТРУКТУРА РАЗДЕЛА:\n"); promptBuilder.append("VI. SWOT-анализ\n\n"); promptBuilder.append("Детально проанализируй:\n"); @@ -2166,7 +2183,7 @@ public class MarketingAnalysisService { promptBuilder.append("ВАЖНО:\n"); promptBuilder.append("- Начинай сразу с заголовка \"VI. SWOT-анализ\"\n"); promptBuilder.append("- Не добавляй вступления или заключения\n"); - promptBuilder.append("- Используй данные из поля swot\n"); + promptBuilder.append("- Если поле swot присутствует в JSON — используй его как основу.\n"); promptBuilder.append("- Обязательно проведи перекрестный анализ пересечений\n\n"); promptBuilder.append("JSON ДАННЫЕ:\n"); promptBuilder.append(jsonString); @@ -2192,14 +2209,6 @@ public class MarketingAnalysisService { */ private String genSectionChannels(Map contextJson) { try { - boolean hasChannels = hasNonEmptyValue(contextJson, "channels") - || hasNonEmptyValue(contextJson, "channelsPotential"); - if (!hasChannels) { - return "VII. Каналы продвижения и их потенциал\n\n" - + "Раздел пропущен: отсутствуют данные по каналам в JSON (channels/channelsPotential/strategyAnalysis.channelsPotential). " - + "Нужно сгенерировать strategyAnalysis.channelsPotential."; - } - String jsonString = objectMapper.writeValueAsString(contextJson); StringBuilder promptBuilder = new StringBuilder(); promptBuilder.append("Ты — высокооплачиваемый бизнес-консультант и маркетинговый аналитик.\n\n"); @@ -2209,10 +2218,17 @@ public class MarketingAnalysisService { promptBuilder.append("1. Объем: минимум 500-700 слов\n"); promptBuilder.append("2. Стиль: деловой, убедительный для владельца бизнеса\n"); promptBuilder.append("3. Глубина: аналитические инсайты с практическими выводами\n"); - promptBuilder.append("4. Данные: используй ТОЛЬКО данные из JSON, не выдумывай цифры\n\n"); + promptBuilder.append("4. Данные: используй ТОЛЬКО данные из JSON, не выдумывай цифры\n"); + promptBuilder.append("5. РАЗДЕЛ НЕЛЬЗЯ ПРОПУСКАТЬ:\n"); + promptBuilder.append( + " - Если в JSON отсутствуют channels/channelsPotential: составь список каналов как ГИПОТЕЗЫ, опираясь на нишу/регион/цель, аудиторию (audienceAnalysis), сезонность и конкурентов.\n"); + promptBuilder.append( + " - Если по каналу нет чисел: явно укажи \"нет данных в JSON\" и перечисли, какие метрики нужно собрать (CPC/CPM/CTR/CR/CPL/CPA/LTV).\n"); + promptBuilder.append( + " - Если channelsPotential присутствует: обязательно используй potentialIndex/priority/обоснование.\n\n"); promptBuilder.append("СТРУКТУРА РАЗДЕЛА:\n"); promptBuilder.append("VII. Каналы продвижения и их потенциал\n\n"); - promptBuilder.append("Детально проанализируй КАЖДЫЙ канал из channels:\n"); + promptBuilder.append("Детально проанализируй КАЖДЫЙ канал из channels/channelsPotential (если есть):\n"); promptBuilder.append("- Потенциал канала для данной ниши (potentialIndex)\n"); promptBuilder.append("- Приоритетность канала (priorityLevel) и обоснование\n"); promptBuilder.append("- Роль канала в общей стратегии (roleInStrategy)\n"); @@ -2256,13 +2272,6 @@ public class MarketingAnalysisService { */ private String genSectionFunnel(Map contextJson) { try { - boolean hasFunnel = hasNonEmptyValue(contextJson, "funnel"); - boolean hasConversionFunnel = hasNonEmptyValue(contextJson, "conversionFunnel"); - if (!hasFunnel && !hasConversionFunnel) { - return "VIII. Воронка спроса\n\n" - + "Раздел пропущен: отсутствуют данные воронки в JSON (funnel / strategyAnalysis.funnel / conversionFunnel)."; - } - String jsonString = objectMapper.writeValueAsString(contextJson); StringBuilder promptBuilder = new StringBuilder(); promptBuilder.append("Ты — высокооплачиваемый бизнес-консультант и маркетинговый аналитик.\n\n"); @@ -2272,7 +2281,14 @@ public class MarketingAnalysisService { promptBuilder.append("1. Объем: минимум 500-700 слов\n"); promptBuilder.append("2. Стиль: деловой, убедительный для владельца бизнеса\n"); promptBuilder.append("3. Глубина: аналитические инсайты с практическими выводами\n"); - promptBuilder.append("4. Данные: используй ТОЛЬКО данные из JSON, не выдумывай цифры\n\n"); + promptBuilder.append("4. Данные: используй ТОЛЬКО данные из JSON, не выдумывай цифры\n"); + promptBuilder.append("5. РАЗДЕЛ НЕЛЬЗЯ ПРОПУСКАТЬ:\n"); + promptBuilder.append( + " - Если в JSON нет funnel/conversionFunnel: построй воронку как ГИПОТЕЗУ (этапы + логика), но все числа замени на переменные (X1, X2...) и подпиши \"нет данных в JSON\".\n"); + promptBuilder.append( + " - Обязательно добавь блок: какие данные нужно собрать для расчета (трафик, лиды, заявки, оплаты, CR по этапам, CPL/CPA).\n"); + promptBuilder.append( + " - Если funnel/conversionFunnel присутствуют: делай выводы строго по цифрам из JSON.\n\n"); promptBuilder.append("СТРУКТУРА РАЗДЕЛА:\n"); promptBuilder.append("VIII. Воронка спроса\n\n"); promptBuilder.append("Детально проанализируй:\n");