diff --git a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java index a6cd08e..6cae0ce 100644 --- a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java +++ b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java @@ -112,7 +112,7 @@ public class MarketingAnalysisService { // Match lines like: "I. Title" or "## I. Title" java.util.regex.Pattern p = java.util.regex.Pattern.compile( "^(\\s*)(#{0,6}\\s*)?(" + java.util.regex.Pattern.quote(roman) - + ")\\.(\\s+[^\\n\\r]+)\\s*$", + + ")\\.(\\s*\\S[^\\n\\r]*)\\s*$", java.util.regex.Pattern.MULTILINE); java.util.regex.Matcher m = p.matcher(result); if (!m.find()) { @@ -142,8 +142,8 @@ public class MarketingAnalysisService { } for (String roman : SECTION_ROMANS) { java.util.regex.Pattern p = java.util.regex.Pattern.compile( - "^(?:\\s*\\s*\\n)?\\s*##\\s*(" + java.util.regex.Pattern.quote(roman) - + ")\\.(\\s+[^\\n\\r]+)\\s*$", + "^(?:\\s*\\s*\\n)?\\s*(?:#{1,6}\\s*)?(" + + java.util.regex.Pattern.quote(roman) + ")\\.(\\s*\\S[^\\n\\r]*)\\s*$", java.util.regex.Pattern.MULTILINE); java.util.regex.Matcher m = p.matcher(fullAnalysis); if (m.find()) { @@ -2038,6 +2038,10 @@ public class MarketingAnalysisService { promptBuilder.append("2. Стиль: деловой, убедительный для владельца бизнеса\n"); promptBuilder.append("3. Глубина: аналитические инсайты с практическими выводами\n"); promptBuilder.append("4. Данные: используй ТОЛЬКО данные из JSON, не выдумывай цифры\n"); + promptBuilder.append( + "ВНИМАНИЕ: Проверяй математическую логику. Количество покупок НЕ МОЖЕТ быть больше количества заявок. " + + "Конверсия не может превышать 100%. Если данные в JSON противоречивы, используй логические значения " + + "(например, конверсия 10%) и сделай пометку 'Требуется уточнение данных'.\n"); promptBuilder.append("5. РАЗДЕЛ НЕЛЬЗЯ ПРОПУСКАТЬ:\n"); promptBuilder.append( " - Если в JSON нет funnel/conversionFunnel: построй воронку как ГИПОТЕЗУ (этапы + логика), но все числа замени на переменные (X1, X2...) и подпиши \"нет данных в JSON\".\n"); @@ -2233,6 +2237,10 @@ public class MarketingAnalysisService { promptBuilder.append("КРИТИЧНО - ПРОГНОЗ И РАСЧЕТЫ НА ОСНОВЕ ВОРОНКИ:\n"); promptBuilder.append( "1. Возьми воронку из strategyAnalysis.funnel (или из funnel, если strategyAnalysis отсутствует) и выполни расчеты.\n"); + promptBuilder.append( + "ВНИМАНИЕ: Проверяй математическую логику. Количество покупок НЕ МОЖЕТ быть больше количества заявок. " + + "Конверсия не может превышать 100%. Если данные в JSON противоречивы, используй логические значения " + + "(например, конверсия 10%) и сделай пометку 'Требуется уточнение данных'.\n"); promptBuilder.append( "2. Дай минимум 3 прогнозных показателя с формулами и подстановкой чисел из JSON (например: конверсия между этапами, ожидаемое число покупок, требуемый объем верхнего этапа для достижения цели).\n"); promptBuilder.append( @@ -3378,6 +3386,9 @@ public class MarketingAnalysisService { for (String roman : SECTION_ROMANS) { String id = ROMAN_TO_ANCHOR_ID.get(roman); String label = sectionTitles.getOrDefault(roman, "Раздел " + roman); + if ("V".equals(roman) && ("Раздел V".equals(label) || label == null || label.isBlank())) { + label = "V. Анализ конкурентов"; + } markdown.append("- [").append(label).append("](#").append(id).append(")\n"); } markdown.append("\n"); @@ -3711,6 +3722,15 @@ public class MarketingAnalysisService { continue; } + // Если JSON-блок для этого графика уже присутствует в секции — значит он уже + // вставлен/обработан; не добавляем повторно в конец секции + if (mapping.chartKey != null && sectionContainsJsonBlock(sectionText, mapping.chartKey)) { + if (processedPlaceholders != null) { + processedPlaceholders.add(normalizedPlaceholder); + } + continue; + } + // Если данные отсутствуют — не добавляем тег (он будет удален final pass-ом) if (chartsData == null || !chartsData.containsKey(mapping.dataKey)) { logger.warn( @@ -3764,6 +3784,19 @@ public class MarketingAnalysisService { .find(); } + private boolean sectionContainsJsonBlock(String sectionText, String chartKey) { + if (sectionText == null || sectionText.isEmpty() || chartKey == null || chartKey.isBlank()) { + return false; + } + // Matches blocks like: + // ```json:seasonality + // {...} + // ``` + String regex = "```\\s*json\\s*:\\s*" + java.util.regex.Pattern.quote(chartKey.trim()) + "\\b"; + return java.util.regex.Pattern.compile(regex, java.util.regex.Pattern.CASE_INSENSITIVE).matcher(sectionText) + .find(); + } + public MarketingAnalysisResult getAnalysisResult(String analysisId) { Optional optAnalysis = repository.findById(analysisId); if (optAnalysis.isEmpty()) {