From e5d7e5fb77a3b4142447dcbff173955aaebd61b4 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 20 Dec 2025 00:25:46 +0500 Subject: [PATCH] . --- .../service/MarketingAnalysisService.java | 116 ++++++++++++++++-- 1 file changed, 107 insertions(+), 9 deletions(-) diff --git a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java index 97862d2..f171d91 100644 --- a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java +++ b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java @@ -1428,6 +1428,17 @@ public class MarketingAnalysisService { String lowerResult = result.toLowerCase().trim(); + // Явные "заглушки" о провале генерации (не считаем валидным ответом модели) + // Важно: это нужно, чтобы такие ответы не считались "успешной генерацией" при + // сборке отчёта. + if (lowerResult.contains("не удалось сгенерировать раздел") || + lowerResult.contains("ошибка при генерации раздела") || + lowerResult.startsWith("ошибка при генерации") || + lowerResult.startsWith("error generating") || + lowerResult.startsWith("error:")) { + return false; + } + // Проверяем на типичные ответы отказа от OpenAI if (lowerResult.contains("i'm sorry") || lowerResult.contains("i cannot") || @@ -1453,6 +1464,51 @@ public class MarketingAnalysisService { return true; } + private boolean hasNonEmptyValue(Map map, String key) { + if (map == null || key == null) { + return false; + } + Object v = map.get(key); + return !isEmptyChartData(v); + } + + private String generateWithRetry(String jsonContext, String prompt, String lang, String primaryModel, + String fallbackModel, int attempts) { + int maxAttempts = Math.max(1, attempts); + String last = null; + + for (int i = 1; i <= maxAttempts; i++) { + String modelToUse = primaryModel; + // На последней попытке можно переключиться на запасную модель (если задана) + if (i == maxAttempts && fallbackModel != null && !fallbackModel.isBlank()) { + modelToUse = fallbackModel; + } + try { + last = openAIAnalyticsService.generateWithInstructionWithModel( + jsonContext, prompt, lang, modelToUse); + } catch (Exception e) { + logger.warn("generateWithRetry: attempt {} failed with exception: {}", i, e.getMessage()); + last = null; + } + + if (isValidAiResponse(last)) { + return last; + } + + // Небольшая пауза между попытками, чтобы сгладить кратковременные сбои + if (i < maxAttempts) { + try { + Thread.sleep(250L); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + break; + } + } + } + + return last; + } + /** * Удаляет сообщения об ошибках и отказах AI из текста отчета * @@ -1654,9 +1710,27 @@ public class MarketingAnalysisService { } break; case "content": + // Если contentRecommendations отсутствует, всё равно можно построить + // контент-стратегию + // на основе аудитории/воронки/каналов/сезонности/конкурентов. if (fullJson.containsKey("contentRecommendations")) { context.put("contentRecommendations", fullJson.get("contentRecommendations")); } + if (fullJson.containsKey("audienceAnalysis")) { + context.put("audienceAnalysis", fullJson.get("audienceAnalysis")); + } + if (fullJson.containsKey("competitorAnalysis")) { + context.put("competitorAnalysis", fullJson.get("competitorAnalysis")); + } + if (fullJson.containsKey("seasonality")) { + context.put("seasonality", fullJson.get("seasonality")); + } + if (fullJson.containsKey("market")) { + context.put("market", fullJson.get("market")); + } + if (fullJson.containsKey("strategyAnalysis")) { + context.put("strategyAnalysis", fullJson.get("strategyAnalysis")); + } break; case "strategy": // Для итоговой стратегии включаем все ключевые данные @@ -1851,6 +1925,15 @@ public class MarketingAnalysisService { */ private String genSectionAudience(Map contextJson) { try { + // Если нет ключевых данных — не дергаем модель, чтобы не получать "короткие" + // ответы + boolean hasAudienceAnalysis = hasNonEmptyValue(contextJson, "audienceAnalysis"); + boolean hasAudienceSegments = hasNonEmptyValue(contextJson, "audienceSegments"); + if (!hasAudienceAnalysis && !hasAudienceSegments) { + return "IV. Анализ целевой аудитории\n\n" + + "Раздел пропущен: отсутствуют данные аудитории в JSON (audienceAnalysis/audienceSegments)."; + } + String jsonString = objectMapper.writeValueAsString(contextJson); StringBuilder promptBuilder = new StringBuilder(); promptBuilder.append("Ты — высокооплачиваемый бизнес-консультант и маркетинговый аналитик.\n\n"); @@ -1901,8 +1984,8 @@ public class MarketingAnalysisService { promptBuilder.append("JSON ДАННЫЕ:\n"); promptBuilder.append(jsonString); - String result = openAIAnalyticsService.generateWithInstructionWithModel( - jsonString, promptBuilder.toString(), "ru", textModelName); + String result = generateWithRetry(jsonString, promptBuilder.toString(), "ru", textModelName, miniModelName, + 2); if (!isValidAiResponse(result)) { logger.warn("genSectionAudience: AI returned invalid or refusal response"); @@ -1997,6 +2080,11 @@ 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"); @@ -2033,8 +2121,8 @@ public class MarketingAnalysisService { promptBuilder.append("JSON ДАННЫЕ:\n"); promptBuilder.append(jsonString); - String result = openAIAnalyticsService.generateWithInstructionWithModel( - jsonString, promptBuilder.toString(), "ru", textModelName); + String result = generateWithRetry(jsonString, promptBuilder.toString(), "ru", textModelName, miniModelName, + 2); if (!isValidAiResponse(result)) { logger.warn("genSectionSWOT: AI returned invalid or refusal response"); @@ -2110,6 +2198,13 @@ 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"); @@ -2145,8 +2240,8 @@ public class MarketingAnalysisService { promptBuilder.append("JSON ДАННЫЕ:\n"); promptBuilder.append(jsonString); - String result = openAIAnalyticsService.generateWithInstructionWithModel( - jsonString, promptBuilder.toString(), "ru", textModelName); + String result = generateWithRetry(jsonString, promptBuilder.toString(), "ru", textModelName, miniModelName, + 2); if (!isValidAiResponse(result)) { logger.warn("genSectionFunnel: AI returned invalid or refusal response"); @@ -2228,7 +2323,10 @@ public class MarketingAnalysisService { promptBuilder.append("СТРУКТУРА РАЗДЕЛА:\n"); promptBuilder.append("X. Рекомендации по контенту\n\n"); promptBuilder.append("Детально проанализируй:\n"); - promptBuilder.append("- Типы контента для разных целей (из contentRecommendations)\n"); + promptBuilder.append( + "- Если в JSON есть contentRecommendations: используй их как основу и доработай/структурируй\n"); + promptBuilder.append( + "- Если contentRecommendations НЕТ: разработай контент-стратегию, опираясь на audienceAnalysis, strategyAnalysis (funnel/channelsPotential), seasonality и competitorAnalysis\n"); promptBuilder.append("- Форматы контента для разных каналов продвижения\n"); promptBuilder.append("- Контент для разных этапов воронки (осведомленность, интерес, решение, действие)\n"); promptBuilder.append("- Контент для разных сегментов целевой аудитории\n"); @@ -2243,8 +2341,8 @@ public class MarketingAnalysisService { promptBuilder.append("JSON ДАННЫЕ:\n"); promptBuilder.append(jsonString); - String result = openAIAnalyticsService.generateWithInstructionWithModel( - jsonString, promptBuilder.toString(), "ru", miniModelName); + String result = generateWithRetry(jsonString, promptBuilder.toString(), "ru", miniModelName, textModelName, + 2); if (!isValidAiResponse(result)) { logger.warn("genSectionContent: AI returned invalid or refusal response");