.
This commit is contained in:
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user