This commit is contained in:
root
2025-12-20 00:42:49 +05:00
parent 17ba04552c
commit 0a46c45c9a
@@ -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<String, Object> 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<String, Object> 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<String, Object> 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");