diff --git a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java index 6aabd03..a6cd08e 100644 --- a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java +++ b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java @@ -57,6 +57,20 @@ public class MarketingAnalysisService { return token.replaceAll("\\s+", "").toUpperCase(Locale.ROOT); } + private boolean containsChartOrTablePlaceholder(String text) { + if (text == null || text.isBlank()) { + return false; + } + // Fast path (exact canonical token) + if (text.contains("[[CHART_") || text.contains("[[TABLE_")) { + return true; + } + // Flexible path (ignore extra spaces, tolerate model formatting) + return java.util.regex.Pattern.compile("\\[\\[\\s*(CHART|TABLE)_", java.util.regex.Pattern.CASE_INSENSITIVE) + .matcher(text) + .find(); + } + private String removePlaceholderWithSurroundingNewlines(String text, String placeholderVariant) { if (text == null || placeholderVariant == null || placeholderVariant.isBlank()) { return text; @@ -1714,6 +1728,8 @@ public class MarketingAnalysisService { promptBuilder.append("ЕСЛИ В JSON НЕТ ТОЧНЫХ ЦИФР (РАЗРЕШЕННАЯ АППРОКСИМАЦИЯ):\n"); promptBuilder.append( "- Если audienceAnalysis.ageGroups или audienceAnalysis.genderDistribution отсутствуют/пустые: составь реалистичное распределение по нескольким категориям (НЕ 100% в одну).\n"); + promptBuilder.append( + "- Если audienceAnalysis.ageGroups присутствует, но все значения кроме одного равны 0 (вырожденное распределение): придумай реалистичное распределение по shown нескольким возрастным группам (например, 25–34 — 40%, 35–44 — 30%, 18–24 — 15%, 45–54 — 10%, 55+ — 5%), чтобы график был наглядным. Обязательно пометь это как \"Экспертная аппроксимация\".\n"); promptBuilder.append( "- Сумма долей должна быть 100%. Явно пометь это как \"Экспертная аппроксимация\".\n\n"); @@ -3512,13 +3528,16 @@ public class MarketingAnalysisService { } // Find all placeholders actually present in the text (incl. variants with - // spaces) + // spaces). Use a fast contains() check first, but fall back to regex to + // tolerate extra spaces like: [[ CHART_SEASONALITY ]]. java.util.regex.Pattern presentPattern = java.util.regex.Pattern .compile("\\[\\[\\s*(CHART|TABLE)_[A-Za-z0-9_]+\\s*\\]\\]"); - java.util.regex.Matcher presentMatcher = presentPattern.matcher(result); Set presentPlaceholders = new LinkedHashSet<>(); - while (presentMatcher.find()) { - presentPlaceholders.add(presentMatcher.group()); + if (containsChartOrTablePlaceholder(result)) { + java.util.regex.Matcher presentMatcher = presentPattern.matcher(result); + while (presentMatcher.find()) { + presentPlaceholders.add(presentMatcher.group()); + } } for (String placeholderVariant : presentPlaceholders) { @@ -3678,6 +3697,14 @@ public class MarketingAnalysisService { continue; } + // Если график уже был обработан (заменен на ```json блок в другой части + // текста), + // то НИ В КОЕМ СЛУЧАЕ не добавляем его повторно в конец секции. + String normalizedPlaceholder = normalizePlaceholderToken(placeholder); + if (processedPlaceholders != null && processedPlaceholders.contains(normalizedPlaceholder)) { + continue; + } + // Если плейсхолдер уже есть в тексте секции (даже с пробелами/регистром) — не // добавляем if (sectionContainsPlaceholder(sectionText, placeholder)) { @@ -3707,7 +3734,7 @@ public class MarketingAnalysisService { // Не считаем как processed (пусть final pass заменит его на JSON-блок) // но помечаем, чтобы ниже не пытаться добавлять повторно в рамках этой итерации - processedPlaceholders.add(normalizePlaceholderToken(placeholder)); + processedPlaceholders.add(normalizedPlaceholder); } if (!willAppendAnything) {