From b140fe35f532160e36056c9ee3ae50e20cad5877 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 18 Dec 2025 23:55:49 +0500 Subject: [PATCH] . --- .../service/MarketingAnalysisService.java | 128 ++++++++++++++++-- .../service/OpenAIAnalyticsService.java | 2 +- 2 files changed, 114 insertions(+), 16 deletions(-) diff --git a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java index f0f4541..43456bf 100644 --- a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java +++ b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java @@ -1441,16 +1441,32 @@ public class MarketingAnalysisService { "делай глубокие сравнения нашего бренда с конкурентами (используй данные из competitorAnalysis и comparisonTable). "); promptBuilder.append("Каждое утверждение должно быть подкреплено конкретными данными из JSON.\n\n"); + promptBuilder.append("ТРЕБОВАНИЯ К ОБЪЕМУ ОТЧЕТА:\n"); + promptBuilder.append("1. Напиши подробный отчет объемом не менее 2500-3000 слов\n"); + promptBuilder.append( + "2. КРИТИЧНО: Каждый из 11 разделов (I-XI) должен содержать минимум 5-6 развернутых абзацев с глубокой аналитикой\n"); + promptBuilder + .append("3. Каждый абзац должен быть содержательным и развернутым, не менее 4-5 предложений\n\n"); + promptBuilder.append("ТРЕБОВАНИЯ К ОТЧЕТУ:\n"); promptBuilder.append("1. Используй деловой стиль письма, убедительный для владельца бизнеса\n"); promptBuilder.append("2. Добавляй аналитические инсайты и практические выводы\n"); promptBuilder.append("3. Структурируй текст по разделам с римскими цифрами (I, II, III и т.д.)\n"); + promptBuilder.append("4. Используй данные ТОЛЬКО из переданного JSON, не выдумывай цифры\n"); + promptBuilder.append("5. Пиши конкретно и практично, избегай общих фраз\n"); promptBuilder.append( - "4. КРИТИЧНО: Каждый раздел (I-XI) должен содержать минимум 3-4 детальных абзаца с глубокой аналитикой\n"); - promptBuilder.append("5. Используй данные ТОЛЬКО из переданного JSON, не выдумывай цифры\n"); - promptBuilder.append("6. Пиши конкретно и практично, избегай общих фраз\n"); + "6. Обязательно используй данные из полей product, businessNiche, goal, regions для контекста анализа\n\n"); + + promptBuilder.append("АНАЛИТИЧЕСКАЯ ГЛУБИНА:\n"); + promptBuilder.append("1. Используй \"Метод Case Study\" и \"Бенчмаркинг\" для глубокого анализа\n"); + promptBuilder + .append("2. НЕ просто перечисляй данные из JSON, а ИНТЕРПРЕТИРУЙ их с практическими выводами\n"); promptBuilder.append( - "7. Обязательно используй данные из полей product, businessNiche, goal, regions для контекста анализа\n\n"); + "3. Приводи конкретные примеры: если сезонность падает в июле, объясни, как именно это влияет на кэшфлоу бизнеса и предложи конкретные акции для перекрытия кассового разрыва\n"); + promptBuilder.append( + "4. Каждое утверждение должно быть подкреплено конкретными данными из JSON с объяснением их бизнес-значения\n"); + promptBuilder.append( + "5. Делай глубокие сравнения нашего бренда с конкурентами (используй данные из competitorAnalysis и comparisonTable)\n\n"); promptBuilder.append("ОБЯЗАТЕЛЬНАЯ СТРУКТУРА ОТЧЕТА:\n"); promptBuilder.append("I. Краткое резюме (общий обзор бизнеса и ключевые выводы)\n"); @@ -1471,19 +1487,37 @@ public class MarketingAnalysisService { promptBuilder.append("X. Рекомендации по контенту (типы контента для разных целей)\n"); promptBuilder.append("XI. Итоговая стратегия: что делать в первую очередь (конкретный план действий)\n\n"); - promptBuilder.append("КРИТИЧНО - ПЛЕЙСХОЛДЕРЫ ГРАФИКОВ:\n"); + promptBuilder.append("КРИТИЧНО - ПРИНУДИТЕЛЬНАЯ ВСТАВКА ГРАФИКОВ:\n"); promptBuilder.append( - "Обязательно расставь ВСЕ плейсхолдеры графиков: [[CHART_SEASONALITY]], [[CHART_AUDIENCE]], "); - promptBuilder.append("[[CHART_COMPETITORS]], [[CHART_SWOT]], [[CHART_CHANNELS]], [[CHART_FUNNEL]]. "); - promptBuilder.append("Каждый плейсхолдер должен быть на отдельной строке в соответствующем разделе, "); - promptBuilder.append("как указано в структуре выше. НЕ пропускай ни одного плейсхолдера!\n\n"); + "1. Ты ОБЯЗАН вставить каждый плейсхолдер [[CHART_...]] в контекст обсуждения соответствующего раздела\n"); + promptBuilder.append( + "2. Обязательно расставь ВСЕ плейсхолдеры графиков: [[CHART_SEASONALITY]], [[CHART_AUDIENCE]], "); + promptBuilder.append("[[CHART_COMPETITORS]], [[CHART_SWOT]], [[CHART_CHANNELS]], [[CHART_FUNNEL]]\n"); + promptBuilder.append( + "3. Каждый плейсхолдер должен быть на отдельной строке в соответствующем разделе, как указано в структуре выше\n"); + promptBuilder.append( + "4. ПЕРЕД каждым графиком напиши вводный абзац (2-3 предложения), начинающийся с: \"Анализ данных на графике ниже показывает...\"\n"); + promptBuilder.append( + "5. ПОСЛЕ каждого графика напиши интерпретацию данных (2-3 предложения), начинающуюся с: \"Из этих показателей следует, что...\"\n"); + promptBuilder.append( + "6. НЕ пропускай ни одного плейсхолдера! Каждый график должен быть встроен в повествование раздела\n\n"); + + promptBuilder.append("ТЕМЫ ДЛЯ РАСШИРЕНИЯ В РАЗДЕЛАХ:\n"); + promptBuilder.append( + "1. В разделе VI (SWOT-анализ): обязательно проведи анализ пересечений - объясни, как сильные стороны помогут использовать возможности, и как слабые стороны могут помешать противостоять угрозам\n"); + promptBuilder.append( + "2. В разделе IV (Анализ целевой аудитории): для каждого сегмента опиши \"путь клиента\" (Customer Journey Map) - этапы от первого контакта до покупки и лояльности\n"); + promptBuilder.append( + "3. В разделе V (Анализ конкурентов): проведи детальный разбор их контент-воронок - какие типы контента они используют на каждом этапе воронки, какие каналы задействуют, какие месседжи доносят\n\n"); promptBuilder.append("ВАЖНО:\n"); promptBuilder.append("- Плейсхолдеры [[CHART_...]] вставляй ТОЧНО как указано выше, на отдельной строке\n"); promptBuilder.append("- Начинай сразу с 'I. Краткое резюме', без вступлений\n"); promptBuilder.append( "- НЕ добавляй секции 'Целевая аудитория', 'Рекомендации', 'Стратегия' отдельно в конце — они уже включены в структуру\n"); - promptBuilder.append("- Пиши так, чтобы отчет был полезен для принятия бизнес-решений\n\n"); + promptBuilder.append("- Пиши так, чтобы отчет был полезен для принятия бизнес-решений\n"); + promptBuilder.append( + "- Цель: получить полноценное маркетинговое исследование на 15+ страниц с глубокой аналитикой и визуализациями\n\n"); promptBuilder.append("JSON ДАННЫЕ:\n"); promptBuilder.append(jsonString); @@ -2860,6 +2894,36 @@ public class MarketingAnalysisService { logger.debug("replaceChartPlaceholders: Adding missing charts to sections"); result = addMissingChartsToSections(result, chartsData, placeholderMap, processedPlaceholders); + // Финальная очистка: удаляем все оставшиеся не замененные плейсхолдеры + // (если AI вернул их, но данных в chartsData нет) + java.util.regex.Pattern remainingPlaceholderPattern = java.util.regex.Pattern.compile("\\[\\[CHART_\\w+\\]\\]"); + java.util.regex.Matcher matcher = remainingPlaceholderPattern.matcher(result); + Set remainingPlaceholders = new HashSet<>(); + while (matcher.find()) { + String placeholder = matcher.group(); + remainingPlaceholders.add(placeholder); + } + + for (String placeholder : remainingPlaceholders) { + // Проверяем, есть ли данные для этого плейсхолдера + boolean hasData = false; + ChartMapping mapping = placeholderMap.get(placeholder); + if (mapping != null) { + Object chartData = chartsData.get(mapping.dataKey); + if (!isEmptyChartData(chartData)) { + hasData = true; + } + } + + if (!hasData) { + logger.warn("replaceChartPlaceholders: Removing unprocessed placeholder without data: {}", placeholder); + // Удаляем плейсхолдер с окружающими пустыми строками + String placeholderWithNewlines = "(\\r?\\n)?\\s*" + java.util.regex.Pattern.quote(placeholder) + + "\\s*(\\r?\\n)?"; + result = result.replaceAll(placeholderWithNewlines, "\n"); + } + } + // Финальная очистка: удаляем множественные пустые строки (более 2 подряд) result = result.replaceAll("(\\r?\\n){3,}", "\n\n"); @@ -2918,21 +2982,38 @@ public class MarketingAnalysisService { continue; } - // Ищем раздел в тексте + // Ищем раздел в тексте: сначала по markdown заголовкам, затем по римским цифрам java.util.regex.Pattern pattern = java.util.regex.Pattern .compile("(##+\\s*" + sectionPattern + "[^#]*)", java.util.regex.Pattern.CASE_INSENSITIVE); java.util.regex.Matcher matcher = pattern.matcher(result); + boolean found = matcher.find(); - if (matcher.find()) { + // Если не нашли по markdown, ищем по римским цифрам (I., II., III. и т.д.) + if (!found) { + // Паттерн для поиска раздела по римским цифрам: I. Название, II. Название и + // т.д. + pattern = java.util.regex.Pattern + .compile("(^|\\n)\\s*" + sectionPattern + "[^\\n]*(\\n|$)", + java.util.regex.Pattern.CASE_INSENSITIVE | java.util.regex.Pattern.MULTILINE); + matcher = pattern.matcher(result); + found = matcher.find(); + } + + if (found) { try { String jsonStr = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(chartData); String jsonBlock = "\n\n```json:" + mapping.chartKey + "\n" + jsonStr + "\n```\n"; // Вставляем после найденного раздела, перед следующим разделом или концом // текста int insertPos = matcher.end(); + // Ищем конец текущего раздела (начало следующего раздела или конец текста) String remainingText = result.substring(insertPos); - java.util.regex.Pattern nextSectionPattern = java.util.regex.Pattern.compile("^##+\\s*[IVX]+\\."); + + // Паттерн для поиска следующего раздела: может быть markdown заголовок или + // римская цифра + java.util.regex.Pattern nextSectionPattern = java.util.regex.Pattern + .compile("^(##+\\s*[IVX]+\\.|\\s*[IVX]+\\.\\s+[А-Я])", java.util.regex.Pattern.MULTILINE); java.util.regex.Matcher nextSectionMatcher = nextSectionPattern.matcher(remainingText); if (nextSectionMatcher.find()) { @@ -2940,12 +3021,29 @@ public class MarketingAnalysisService { int nextSectionPos = insertPos + nextSectionMatcher.start(); result = result.substring(0, nextSectionPos) + jsonBlock + result.substring(nextSectionPos); } else { - // Вставляем в конец текста - result = result + jsonBlock; + // Ищем конец текущего раздела по содержимому (до следующего раздела с римской + // цифрой) + // Ищем следующий раздел по паттерну: начало строки, римская цифра, точка, + // пробел, заглавная буква + java.util.regex.Pattern romanNumeralPattern = java.util.regex.Pattern + .compile("\\n\\s*[IVX]+\\.\\s+[А-Я]", java.util.regex.Pattern.MULTILINE); + java.util.regex.Matcher romanMatcher = romanNumeralPattern.matcher(remainingText); + + if (romanMatcher.find()) { + int nextSectionPos = insertPos + romanMatcher.start(); + result = result.substring(0, nextSectionPos) + jsonBlock + result.substring(nextSectionPos); + } else { + // Вставляем в конец текста + result = result + jsonBlock; + } } + logger.debug("addMissingChartsToSections: Added chart {} to section matching pattern: {}", + mapping.dataKey, sectionPattern); } catch (JsonProcessingException e) { logger.warn("Failed to serialize chart data for fallback {}: {}", mapping.dataKey, e.getMessage()); } + } else { + logger.debug("addMissingChartsToSections: Section not found for pattern: {}", sectionPattern); } } diff --git a/src/main/java/kz/konturai/parser/service/OpenAIAnalyticsService.java b/src/main/java/kz/konturai/parser/service/OpenAIAnalyticsService.java index 4581bfe..618687a 100644 --- a/src/main/java/kz/konturai/parser/service/OpenAIAnalyticsService.java +++ b/src/main/java/kz/konturai/parser/service/OpenAIAnalyticsService.java @@ -264,7 +264,7 @@ public class OpenAIAnalyticsService { Map requestBody = new HashMap<>(); requestBody.put("model", modelName); requestBody.put("messages", List.of(message)); - requestBody.put("max_tokens", 2000); + requestBody.put("max_tokens", 12000); // Увеличено для поддержки отчетов объемом 2500-3000 слов requestBody.put("temperature", 0.3); try {