.
This commit is contained in:
@@ -13,6 +13,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParseException;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -26,6 +27,8 @@ import java.util.concurrent.TimeUnit;
|
||||
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 final MarketingAnalysisRepository repository;
|
||||
private final OpenAIAnalyticsService openAIAnalyticsService;
|
||||
@@ -635,107 +638,137 @@ public class MarketingAnalysisService {
|
||||
* Генерация анализа сезонности и рынка
|
||||
*/
|
||||
private Map<String, Object> getSeasonalityAndMarket(MarketingAnalysisRequest request) {
|
||||
try {
|
||||
String context = buildContextString(request);
|
||||
String context = buildContextString(request);
|
||||
|
||||
StringBuilder promptBuilder = new StringBuilder();
|
||||
promptBuilder.append(
|
||||
"Ты — профессиональный маркетинговый аналитик, работающий с малым и средним бизнесом в Казахстане.\n\n");
|
||||
promptBuilder.append(
|
||||
"Твоя задача — на основе входных данных о бизнесе выполнить глубокий анализ сезонности и рынка и выдать результат ТОЛЬКО в виде корректного JSON-объекта строго по указанной структуре.\n\n");
|
||||
promptBuilder.append(
|
||||
"КРИТИЧЕСКИ ВАЖНО: Предоставь детальный анализ сезонных колебаний спроса с конкретными объяснениями. "
|
||||
+ "Опиши рыночные тренды, возможности и угрозы с конкретными инсайтами, а не общими фразами.\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");
|
||||
StringBuilder promptBuilder = new StringBuilder();
|
||||
promptBuilder.append(
|
||||
"Ты — профессиональный маркетинговый аналитик, работающий с малым и средним бизнесом в Казахстане.\n\n");
|
||||
promptBuilder.append(
|
||||
"Твоя задача — на основе входных данных о бизнесе выполнить глубокий анализ сезонности и рынка и выдать результат ТОЛЬКО в виде корректного JSON-объекта строго по указанной структуре.\n\n");
|
||||
promptBuilder.append(
|
||||
"КРИТИЧЕСКИ ВАЖНО: Предоставь детальный анализ сезонных колебаний спроса с конкретными объяснениями. "
|
||||
+ "Опиши рыночные тренды, возможности и угрозы с конкретными инсайтами, а не общими фразами.\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");
|
||||
|
||||
List<String> regions = request.getRegion();
|
||||
String regionStr = (regions != null && !regions.isEmpty())
|
||||
? String.join(", ", regions)
|
||||
: "Не указан";
|
||||
promptBuilder.append("- Регион/город: ").append(regionStr).append("\n");
|
||||
promptBuilder.append("- Цель продвижения: ").append(request.getGoal()).append("\n\n");
|
||||
List<String> regions = request.getRegion();
|
||||
String regionStr = (regions != null && !regions.isEmpty())
|
||||
? String.join(", ", regions)
|
||||
: "Не указан";
|
||||
promptBuilder.append("- Регион/город: ").append(regionStr).append("\n");
|
||||
promptBuilder.append("- Цель продвижения: ").append(request.getGoal()).append("\n\n");
|
||||
|
||||
promptBuilder.append("СФОРМИРУЙ JSON СЛЕДУЮЩЕЙ СТРУКТУРЫ:\n\n");
|
||||
promptBuilder.append("{\n");
|
||||
promptBuilder.append(" \"seasonality\": {\n");
|
||||
promptBuilder.append(
|
||||
" \"months\": [ \"Январь\", \"Февраль\", \"Март\", \"Апрель\", \"Май\", \"Июнь\", \"Июль\", \"Август\", \"Сентябрь\", \"Октябрь\", \"Ноябрь\", \"Декабрь\" ],\n");
|
||||
promptBuilder.append(" \"demandIndex\": [12 чисел от 0 до 100], // Индекс спроса по месяцам\n");
|
||||
promptBuilder.append(
|
||||
" \"description\": \"Детальное описание сезонности (3-4 предложения). Опиши конкретные причины сезонных колебаний: "
|
||||
+ "праздники, климатические условия, культурные особенности, жизненные циклы целевой аудитории. "
|
||||
+ "Укажи, какие месяцы являются пиковыми и почему, какие - низкими и что это означает для бизнеса.\"\n");
|
||||
promptBuilder.append(" },\n");
|
||||
promptBuilder.append(" \"market\": {\n");
|
||||
promptBuilder.append(
|
||||
" \"marketSize\": \"Оценка объема рынка (например, 'Оценка объема рынка составляет X млн тенге в год' или 'Средний рынок с потенциалом роста'). "
|
||||
+ "Дай конкретную оценку или описание масштаба рынка в Казахстане для данной ниши.\",\n");
|
||||
promptBuilder.append(
|
||||
" \"growthRate\": \"Темп роста рынка (например, '+15% в год' или 'Стабильный рост 10-12% ежегодно')\",\n");
|
||||
promptBuilder.append(
|
||||
" \"trends\": [\"Конкретный тренд 1 (1-2 предложения с деталями)\", \"Конкретный тренд 2 (1-2 предложения с деталями)\", \"Конкретный тренд 3 (1-2 предложения с деталями)\"],\n");
|
||||
promptBuilder.append(
|
||||
" \"opportunities\": [\"Конкретная возможность 1 (1-2 предложения с деталями)\", \"Конкретная возможность 2 (1-2 предложения с деталями)\"],\n");
|
||||
promptBuilder.append(
|
||||
" \"threats\": [\"Конкретная угроза 1 (1-2 предложения с деталями)\", \"Конкретная угроза 2 (1-2 предложения с деталями)\"]\n");
|
||||
promptBuilder.append(" }\n");
|
||||
promptBuilder.append("}\n\n");
|
||||
promptBuilder.append("КРИТИЧЕСКИ ВАЖНЫЕ ТРЕБОВАНИЯ:\n\n");
|
||||
promptBuilder.append("1. Верни ТОЛЬКО JSON, без пояснений, комментариев, markdown-разметки.\n");
|
||||
promptBuilder.append("2. JSON должен быть синтаксически корректным и валидным.\n");
|
||||
promptBuilder
|
||||
.append("3. Значения \"demandIndex\" должны быть от 0 до 100 (12 чисел для каждого месяца).\n");
|
||||
promptBuilder.append(
|
||||
"4. Поле \"description\" в seasonality должно содержать ДЕТАЛЬНОЕ описание (3-4 предложения) с конкретными причинами сезонных колебаний.\n");
|
||||
promptBuilder.append(
|
||||
"5. Поле \"marketSize\" должно содержать конкретную оценку объема рынка, а не просто слова 'средний' или 'крупный'.\n");
|
||||
promptBuilder.append(
|
||||
"6. Массивы trends, opportunities, threats должны содержать КОНКРЕТНЫЕ инсайты (по 1-2 предложения каждый), а не общие фразы.\n");
|
||||
promptBuilder.append(
|
||||
"7. Каждый элемент в trends должен описывать реальный тренд с конкретными деталями (например, 'Рост популярности онлайн-заказов на 30% за последний год').\n");
|
||||
promptBuilder.append(
|
||||
"8. Каждый элемент в opportunities должен описывать конкретную возможность с практическими деталями.\n");
|
||||
promptBuilder.append(
|
||||
"9. Каждый элемент в threats должен описывать конкретную угрозу с практическими деталями.\n");
|
||||
promptBuilder.append("10. Все строки — на русском языке.\n");
|
||||
promptBuilder.append(
|
||||
"11. НЕ включай поле \"estimated\" ни в seasonality, ни в market.\n");
|
||||
promptBuilder.append("СФОРМИРУЙ JSON СЛЕДУЮЩЕЙ СТРУКТУРЫ:\n\n");
|
||||
promptBuilder.append("{\n");
|
||||
promptBuilder.append(" \"seasonality\": {\n");
|
||||
promptBuilder.append(
|
||||
" \"months\": [ \"Январь\", \"Февраль\", \"Март\", \"Апрель\", \"Май\", \"Июнь\", \"Июль\", \"Август\", \"Сентябрь\", \"Октябрь\", \"Ноябрь\", \"Декабрь\" ],\n");
|
||||
promptBuilder.append(" \"demandIndex\": [12 чисел от 0 до 100], // Индекс спроса по месяцам\n");
|
||||
promptBuilder.append(
|
||||
" \"description\": \"Детальное описание сезонности (3-4 предложения). Опиши конкретные причины сезонных колебаний: "
|
||||
+ "праздники, климатические условия, культурные особенности, жизненные циклы целевой аудитории. "
|
||||
+ "Укажи, какие месяцы являются пиковыми и почему, какие - низкими и что это означает для бизнеса.\"\n");
|
||||
promptBuilder.append(" },\n");
|
||||
promptBuilder.append(" \"market\": {\n");
|
||||
promptBuilder.append(
|
||||
" \"marketSize\": \"Оценка объема рынка (например, 'Оценка объема рынка составляет X млн тенге в год' или 'Средний рынок с потенциалом роста'). "
|
||||
+ "Дай конкретную оценку или описание масштаба рынка в Казахстане для данной ниши.\",\n");
|
||||
promptBuilder.append(
|
||||
" \"growthRate\": \"Темп роста рынка (например, '+15% в год' или 'Стабильный рост 10-12% ежегодно')\",\n");
|
||||
promptBuilder.append(
|
||||
" \"trends\": [\"Конкретный тренд 1 (1-2 предложения с деталями)\", \"Конкретный тренд 2 (1-2 предложения с деталями)\", \"Конкретный тренд 3 (1-2 предложения с деталями)\"],\n");
|
||||
promptBuilder.append(
|
||||
" \"opportunities\": [\"Конкретная возможность 1 (1-2 предложения с деталями)\", \"Конкретная возможность 2 (1-2 предложения с деталями)\"],\n");
|
||||
promptBuilder.append(
|
||||
" \"threats\": [\"Конкретная угроза 1 (1-2 предложения с деталями)\", \"Конкретная угроза 2 (1-2 предложения с деталями)\"]\n");
|
||||
promptBuilder.append(" }\n");
|
||||
promptBuilder.append("}\n\n");
|
||||
promptBuilder.append("КРИТИЧЕСКИ ВАЖНЫЕ ТРЕБОВАНИЯ:\n\n");
|
||||
promptBuilder.append("1. Верни ТОЛЬКО JSON, без пояснений, комментариев, markdown-разметки.\n");
|
||||
promptBuilder.append("2. JSON должен быть синтаксически корректным и валидным.\n");
|
||||
promptBuilder
|
||||
.append("3. Значения \"demandIndex\" должны быть от 0 до 100 (12 чисел для каждого месяца).\n");
|
||||
promptBuilder.append(
|
||||
"4. Поле \"description\" в seasonality должно содержать ДЕТАЛЬНОЕ описание (3-4 предложения) с конкретными причинами сезонных колебаний.\n");
|
||||
promptBuilder.append(
|
||||
"5. Поле \"marketSize\" должно содержать конкретную оценку объема рынка, а не просто слова 'средний' или 'крупный'.\n");
|
||||
promptBuilder.append(
|
||||
"6. Массивы trends, opportunities, threats должны содержать КОНКРЕТНЫЕ инсайты (по 1-2 предложения каждый), а не общие фразы.\n");
|
||||
promptBuilder.append(
|
||||
"7. Каждый элемент в trends должен описывать реальный тренд с конкретными деталями (например, 'Рост популярности онлайн-заказов на 30% за последний год').\n");
|
||||
promptBuilder.append(
|
||||
"8. Каждый элемент в opportunities должен описывать конкретную возможность с практическими деталями.\n");
|
||||
promptBuilder.append(
|
||||
"9. Каждый элемент в threats должен описывать конкретную угрозу с практическими деталями.\n");
|
||||
promptBuilder.append("10. Все строки — на русском языке.\n");
|
||||
promptBuilder.append(
|
||||
"11. НЕ включай поле \"estimated\" ни в seasonality, ни в market.\n");
|
||||
|
||||
String prompt = promptBuilder.toString();
|
||||
logger.debug(
|
||||
"getSeasonalityAndMarket: Generating seasonality and market analysis with detailed prompt (length: {} chars)",
|
||||
prompt.length());
|
||||
logger.info("getSeasonalityAndMarket: Calling OpenAI API for seasonality and market analysis");
|
||||
String jsonResponse = openAIAnalyticsService.generateWithInstruction(context, prompt, "ru");
|
||||
logger.debug("getSeasonalityAndMarket: OpenAI response received. Length: {} chars",
|
||||
jsonResponse != null ? jsonResponse.length() : 0);
|
||||
String prompt = promptBuilder.toString();
|
||||
logger.debug(
|
||||
"getSeasonalityAndMarket: Generating seasonality and market analysis with detailed prompt (length: {} chars)",
|
||||
prompt.length());
|
||||
|
||||
if (jsonResponse == null || jsonResponse.trim().isEmpty()) {
|
||||
logger.warn("getSeasonalityAndMarket: Empty JSON response from AI for seasonality and market analysis");
|
||||
// Retry logic for JsonParseException
|
||||
int attempt = 0;
|
||||
while (attempt < MAX_RETRY_ATTEMPTS) {
|
||||
try {
|
||||
attempt++;
|
||||
logger.info(
|
||||
"getSeasonalityAndMarket: Calling OpenAI API for seasonality and market analysis (attempt {}/{})",
|
||||
attempt, MAX_RETRY_ATTEMPTS);
|
||||
String jsonResponse = openAIAnalyticsService.generateWithInstruction(context, prompt, "ru");
|
||||
logger.debug("getSeasonalityAndMarket: OpenAI response received. Length: {} chars",
|
||||
jsonResponse != null ? jsonResponse.length() : 0);
|
||||
|
||||
if (jsonResponse == null || jsonResponse.trim().isEmpty()) {
|
||||
logger.warn(
|
||||
"getSeasonalityAndMarket: Empty JSON response from AI for seasonality and market analysis");
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
logger.debug("getSeasonalityAndMarket: Cleaning JSON from markdown");
|
||||
String cleanedJson = cleanJsonFromMarkdown(jsonResponse);
|
||||
logger.debug("getSeasonalityAndMarket: Parsing JSON response");
|
||||
Map<String, Object> jsonMap = objectMapper.readValue(cleanedJson,
|
||||
new TypeReference<Map<String, Object>>() {
|
||||
});
|
||||
logger.debug("getSeasonalityAndMarket: JSON parsed successfully. Keys: {}", jsonMap.keySet());
|
||||
|
||||
logger.info("getSeasonalityAndMarket: Seasonality and market analysis generated successfully");
|
||||
return jsonMap;
|
||||
} catch (JsonParseException e) {
|
||||
logger.warn("getSeasonalityAndMarket: JsonParseException on attempt {}/{}: {}. Will retry...",
|
||||
attempt, MAX_RETRY_ATTEMPTS, e.getMessage());
|
||||
if (attempt < MAX_RETRY_ATTEMPTS) {
|
||||
try {
|
||||
Thread.sleep(RETRY_DELAY_MS);
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
logger.error("getSeasonalityAndMarket: Retry delay interrupted", ie);
|
||||
return new HashMap<>();
|
||||
}
|
||||
} else {
|
||||
logger.error("getSeasonalityAndMarket: JsonParseException after {} attempts: {}",
|
||||
MAX_RETRY_ATTEMPTS, e.getMessage(), e);
|
||||
logger.error("getSeasonalityAndMarket: Stack trace: ", e);
|
||||
return new HashMap<>();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("getSeasonalityAndMarket: Error generating seasonality and market analysis: {}",
|
||||
e.getMessage(), e);
|
||||
logger.error("getSeasonalityAndMarket: Stack trace: ", e);
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
logger.debug("getSeasonalityAndMarket: Cleaning JSON from markdown");
|
||||
String cleanedJson = cleanJsonFromMarkdown(jsonResponse);
|
||||
logger.debug("getSeasonalityAndMarket: Parsing JSON response");
|
||||
Map<String, Object> jsonMap = objectMapper.readValue(cleanedJson, new TypeReference<Map<String, Object>>() {
|
||||
});
|
||||
logger.debug("getSeasonalityAndMarket: JSON parsed successfully. Keys: {}", jsonMap.keySet());
|
||||
|
||||
logger.info("getSeasonalityAndMarket: Seasonality and market analysis generated successfully");
|
||||
return jsonMap;
|
||||
} catch (Exception e) {
|
||||
logger.error("getSeasonalityAndMarket: Error generating seasonality and market analysis: {}",
|
||||
e.getMessage(), e);
|
||||
logger.error("getSeasonalityAndMarket: Stack trace: ", e);
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
logger.error("getSeasonalityAndMarket: Failed after {} attempts", MAX_RETRY_ATTEMPTS);
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user