This commit is contained in:
root
2025-12-20 20:02:36 +05:00
parent 9aac9020c2
commit dc2e25d13d
@@ -112,7 +112,7 @@ public class MarketingAnalysisService {
// Match lines like: "I. Title" or "## I. Title" // Match lines like: "I. Title" or "## I. Title"
java.util.regex.Pattern p = java.util.regex.Pattern.compile( java.util.regex.Pattern p = java.util.regex.Pattern.compile(
"^(\\s*)(#{0,6}\\s*)?(" + java.util.regex.Pattern.quote(roman) "^(\\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.Pattern.MULTILINE);
java.util.regex.Matcher m = p.matcher(result); java.util.regex.Matcher m = p.matcher(result);
if (!m.find()) { if (!m.find()) {
@@ -142,8 +142,8 @@ public class MarketingAnalysisService {
} }
for (String roman : SECTION_ROMANS) { for (String roman : SECTION_ROMANS) {
java.util.regex.Pattern p = java.util.regex.Pattern.compile( java.util.regex.Pattern p = java.util.regex.Pattern.compile(
"^(?:\\s*<a\\s+id=\"[^\"]+\"\\s*></a>\\s*\\n)?\\s*##\\s*(" + java.util.regex.Pattern.quote(roman) "^(?:\\s*<a\\s+id=\"[^\"]+\"\\s*></a>\\s*\\n)?\\s*(?:#{1,6}\\s*)?("
+ ")\\.(\\s+[^\\n\\r]+)\\s*$", + java.util.regex.Pattern.quote(roman) + ")\\.(\\s*\\S[^\\n\\r]*)\\s*$",
java.util.regex.Pattern.MULTILINE); java.util.regex.Pattern.MULTILINE);
java.util.regex.Matcher m = p.matcher(fullAnalysis); java.util.regex.Matcher m = p.matcher(fullAnalysis);
if (m.find()) { if (m.find()) {
@@ -2038,6 +2038,10 @@ public class MarketingAnalysisService {
promptBuilder.append("2. Стиль: деловой, убедительный для владельца бизнеса\n"); promptBuilder.append("2. Стиль: деловой, убедительный для владельца бизнеса\n");
promptBuilder.append("3. Глубина: аналитические инсайты с практическими выводами\n"); promptBuilder.append("3. Глубина: аналитические инсайты с практическими выводами\n");
promptBuilder.append("4. Данные: используй ТОЛЬКО данные из JSON, не выдумывай цифры\n"); promptBuilder.append("4. Данные: используй ТОЛЬКО данные из JSON, не выдумывай цифры\n");
promptBuilder.append(
"ВНИМАНИЕ: Проверяй математическую логику. Количество покупок НЕ МОЖЕТ быть больше количества заявок. "
+ "Конверсия не может превышать 100%. Если данные в JSON противоречивы, используй логические значения "
+ "(например, конверсия 10%) и сделай пометку 'Требуется уточнение данных'.\n");
promptBuilder.append("5. РАЗДЕЛ НЕЛЬЗЯ ПРОПУСКАТЬ:\n"); promptBuilder.append("5. РАЗДЕЛ НЕЛЬЗЯ ПРОПУСКАТЬ:\n");
promptBuilder.append( promptBuilder.append(
" - Если в JSON нет funnel/conversionFunnel: построй воронку как ГИПОТЕЗУ (этапы + логика), но все числа замени на переменные (X1, X2...) и подпиши \"нет данных в JSON\".\n"); " - Если в JSON нет funnel/conversionFunnel: построй воронку как ГИПОТЕЗУ (этапы + логика), но все числа замени на переменные (X1, X2...) и подпиши \"нет данных в JSON\".\n");
@@ -2233,6 +2237,10 @@ public class MarketingAnalysisService {
promptBuilder.append("КРИТИЧНО - ПРОГНОЗ И РАСЧЕТЫ НА ОСНОВЕ ВОРОНКИ:\n"); promptBuilder.append("КРИТИЧНО - ПРОГНОЗ И РАСЧЕТЫ НА ОСНОВЕ ВОРОНКИ:\n");
promptBuilder.append( promptBuilder.append(
"1. Возьми воронку из strategyAnalysis.funnel (или из funnel, если strategyAnalysis отсутствует) и выполни расчеты.\n"); "1. Возьми воронку из strategyAnalysis.funnel (или из funnel, если strategyAnalysis отсутствует) и выполни расчеты.\n");
promptBuilder.append(
"ВНИМАНИЕ: Проверяй математическую логику. Количество покупок НЕ МОЖЕТ быть больше количества заявок. "
+ "Конверсия не может превышать 100%. Если данные в JSON противоречивы, используй логические значения "
+ "(например, конверсия 10%) и сделай пометку 'Требуется уточнение данных'.\n");
promptBuilder.append( promptBuilder.append(
"2. Дай минимум 3 прогнозных показателя с формулами и подстановкой чисел из JSON (например: конверсия между этапами, ожидаемое число покупок, требуемый объем верхнего этапа для достижения цели).\n"); "2. Дай минимум 3 прогнозных показателя с формулами и подстановкой чисел из JSON (например: конверсия между этапами, ожидаемое число покупок, требуемый объем верхнего этапа для достижения цели).\n");
promptBuilder.append( promptBuilder.append(
@@ -3378,6 +3386,9 @@ public class MarketingAnalysisService {
for (String roman : SECTION_ROMANS) { for (String roman : SECTION_ROMANS) {
String id = ROMAN_TO_ANCHOR_ID.get(roman); String id = ROMAN_TO_ANCHOR_ID.get(roman);
String label = sectionTitles.getOrDefault(roman, "Раздел " + 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("- [").append(label).append("](#").append(id).append(")\n");
} }
markdown.append("\n"); markdown.append("\n");
@@ -3711,6 +3722,15 @@ public class MarketingAnalysisService {
continue; continue;
} }
// Если JSON-блок для этого графика уже присутствует в секции — значит он уже
// вставлен/обработан; не добавляем повторно в конец секции
if (mapping.chartKey != null && sectionContainsJsonBlock(sectionText, mapping.chartKey)) {
if (processedPlaceholders != null) {
processedPlaceholders.add(normalizedPlaceholder);
}
continue;
}
// Если данные отсутствуют — не добавляем тег (он будет удален final pass-ом) // Если данные отсутствуют — не добавляем тег (он будет удален final pass-ом)
if (chartsData == null || !chartsData.containsKey(mapping.dataKey)) { if (chartsData == null || !chartsData.containsKey(mapping.dataKey)) {
logger.warn( logger.warn(
@@ -3764,6 +3784,19 @@ public class MarketingAnalysisService {
.find(); .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) { public MarketingAnalysisResult getAnalysisResult(String analysisId) {
Optional<MarketingAnalysis> optAnalysis = repository.findById(analysisId); Optional<MarketingAnalysis> optAnalysis = repository.findById(analysisId);
if (optAnalysis.isEmpty()) { if (optAnalysis.isEmpty()) {