This commit is contained in:
root
2025-12-14 21:59:19 +05:00
parent 13bd7859c7
commit d344fecc94
@@ -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<>();
}
/**