diff --git a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java index 3382138..1cf2e31 100644 --- a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java +++ b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java @@ -1351,7 +1351,8 @@ public class MarketingAnalysisService { * Жесткая санитизация: убирает запрещённые фразы про отсутствие данных и, при * необходимости, хвостовые плейсхолдеры [[CHART_*]]/[[TABLE_*]]. * - * Цель: в финальном отчёте не должно быть ни одной фразы "данные отсутствуют"/"нет данных" + * Цель: в финальном отчёте не должно быть ни одной фразы "данные + * отсутствуют"/"нет данных" * и не должно оставаться токенов [[CHART_...]]. */ private String hardSanitizeReportText(String text, boolean removePlaceholders) { @@ -1361,7 +1362,8 @@ public class MarketingAnalysisService { String cleaned = text; - // 1) Replace "no data" sentences with a neutral analytical extrapolation statement. + // 1) Replace "no data" sentences with a neutral analytical extrapolation + // statement. String[] noDataSentencePatterns = new String[] { "(?i)\\bк\\s*сожалению\\b[^\\n\\.\\!\\?]*\\bданн[а-яё]*\\b[^\\n\\.\\!\\?]*(нет|не\\s*представлен[а-яё]*)[^\\n\\.\\!\\?]*[\\.\\!\\?]?", "(?i)\\bданные\\s*(отсутствуют|не\\s*представлен[а-яё]*|не\\s*доступн[а-яё]*)\\b[^\\n\\.\\!\\?]*[\\.\\!\\?]?", @@ -1372,7 +1374,8 @@ public class MarketingAnalysisService { cleaned = cleaned.replaceAll(p, "Экстраполяция: применяем консервативные допущения и методику расчета."); } - // 2) Remove lingering short phrases (in case model wrote it without punctuation) + // 2) Remove lingering short phrases (in case model wrote it without + // punctuation) cleaned = cleaned.replaceAll("(?i)\\b(нет\\s*данных|данные\\s*отсутствуют|нет\\s*информации)\\b", ""); // 3) Optionally remove any leftover placeholders @@ -2255,12 +2258,13 @@ public class MarketingAnalysisService { promptBuilder.append( "- Вставь [[CHART_CHANNEL_MATRIX]] рядом с абзацем, где ты объясняешь поведение сегментов в каналах.\n"); promptBuilder.append( - "- Объясни на основе матрицы, почему для сегмента \"Молодежь\" приоритет Instagram выше, чем Facebook (с привязкой к значениям matrix).\n\n"); + "- ОБЯЗАТЕЛЬНО проанализируй матрицу: объясни, почему для конкретного сегмента (например, \"Молодежь\") один канал имеет приоритет High, а другой Low. Делай выводы ТОЛЬКО с привязкой к конкретным ячейкам segmentsChannelMatrix (значения High/Medium/Low).\n\n"); promptBuilder.append("ВАЖНО:\n"); promptBuilder.append("- Начинай сразу с заголовка \"VII. Каналы продвижения и их потенциал\"\n"); promptBuilder.append("- Не добавляй вступления или заключения\n"); promptBuilder.append("- Используй данные из полей channels, channelsPotential\n"); - promptBuilder.append("- Используй audienceAnalysis.segmentsChannelMatrix (если есть) для обоснования приоритетов.\n"); + promptBuilder.append( + "- Используй audienceAnalysis.segmentsChannelMatrix (если есть) для обоснования приоритетов.\n"); promptBuilder.append("- Для каждого канала дай конкретные рекомендации\n\n"); promptBuilder.append("JSON ДАННЫЕ:\n"); promptBuilder.append(jsonString); @@ -3629,7 +3633,8 @@ public class MarketingAnalysisService { .replace("[[CHART_CHANNEL_MATRIX]]", "") .replace("[[CHART_FUNNEL]]", "") .replace("[[CHART_SWOT]]", ""); - processedTextReport = processedTextReport.replaceAll("(?i)\\[\\[\\s*(CHART|TABLE)_[A-Za-z0-9_]+\\s*\\]\\]", + processedTextReport = processedTextReport.replaceAll( + "(?i)\\[\\[\\s*(CHART|TABLE)_[A-Za-z0-9_]+\\s*\\]\\]", ""); } @@ -3640,7 +3645,8 @@ public class MarketingAnalysisService { // Очищаем текст: ошибки/отказы + "пустые" фразы про графики без реального блока String cleanedTextReport = processedTextReport != null ? removeErrorMessages(processedTextReport) : null; cleanedTextReport = cleanedTextReport != null ? removeDanglingChartNarration(cleanedTextReport) : null; - // Final hard sanitize: remove forbidden "no data" wording and any leftover placeholders + // Final hard sanitize: remove forbidden "no data" wording and any leftover + // placeholders cleanedTextReport = cleanedTextReport != null ? hardSanitizeReportText(cleanedTextReport, true) : null; if (cleanedTextReport != null && cleanedTextReport.trim().isEmpty()) { cleanedTextReport = "Анализ не удалось сгенерировать."; @@ -3828,7 +3834,8 @@ public class MarketingAnalysisService { placeholderMap.put("[[CHART_AGE_DISTRIBUTION]]", new ChartMapping("audienceAge", "audienceAge")); placeholderMap.put("[[CHART_GENDER_DISTRIBUTION]]", new ChartMapping("audienceGender", "audienceGender")); placeholderMap.put("[[CHART_CHANNELS]]", new ChartMapping("channelsPotential", "channelsPotential")); - placeholderMap.put("[[CHART_CHANNEL_MATRIX]]", new ChartMapping("segmentsChannelMatrix", "segmentsChannelMatrix")); + placeholderMap.put("[[CHART_CHANNEL_MATRIX]]", + new ChartMapping("segmentsChannelMatrix", "segmentsChannelMatrix")); placeholderMap.put("[[CHART_FUNNEL]]", new ChartMapping("conversionFunnel", "funnel")); placeholderMap.put("[[CHART_SWOT]]", new ChartMapping("swot", "swot")); diff --git a/src/test/java/kz/konturai/parser/service/MarketingAnalysisServiceChartPlacementTest.java b/src/test/java/kz/konturai/parser/service/MarketingAnalysisServiceChartPlacementTest.java index 0060efe..c39ad01 100644 --- a/src/test/java/kz/konturai/parser/service/MarketingAnalysisServiceChartPlacementTest.java +++ b/src/test/java/kz/konturai/parser/service/MarketingAnalysisServiceChartPlacementTest.java @@ -33,7 +33,8 @@ class MarketingAnalysisServiceChartPlacementTest { placeholderMap.put("[[TABLE_COMPARISON]]", ctor.newInstance("comparisonTable", "comparisonTable")); placeholderMap.put("[[CHART_SWOT]]", ctor.newInstance("swot", "swot")); placeholderMap.put("[[CHART_CHANNELS]]", ctor.newInstance("channelsPotential", "channelsPotential")); - placeholderMap.put("[[CHART_CHANNEL_MATRIX]]", ctor.newInstance("segmentsChannelMatrix", "segmentsChannelMatrix")); + placeholderMap.put("[[CHART_CHANNEL_MATRIX]]", + ctor.newInstance("segmentsChannelMatrix", "segmentsChannelMatrix")); placeholderMap.put("[[CHART_FUNNEL]]", ctor.newInstance("conversionFunnel", "funnel")); return placeholderMap; } @@ -149,6 +150,7 @@ class MarketingAnalysisServiceChartPlacementTest { assertTrue(s7Start >= 0 && s8Start > s7Start, "Sanity check: VII before VIII"); String section7Body = out.substring(s7Start, s8Start); assertTrue(section7Body.contains("[[CHART_CHANNELS]]"), "Must insert CHART_CHANNELS into section VII"); - assertTrue(section7Body.contains("[[CHART_CHANNEL_MATRIX]]"), "Must insert CHART_CHANNEL_MATRIX into section VII"); + assertTrue(section7Body.contains("[[CHART_CHANNEL_MATRIX]]"), + "Must insert CHART_CHANNEL_MATRIX into section VII"); } }