diff --git a/l.md b/l.md new file mode 100644 index 0000000..35277a0 --- /dev/null +++ b/l.md @@ -0,0 +1,109 @@ +Я хочу изменить логику формирования Markdown-отчета в методе `buildMarkdownFromReport` класса `MarketingAnalysisService`. + +Сейчас метод создает Markdown-таблицы (ASCII tables) для отображения данных (сезонность, конкуренты и т.д.). +Мне нужно изменить это поведение: вместо таблиц я хочу встраивать JSON-данные для графиков прямо в текст отчета, используя Markdown code blocks. + +**Требования:** + +1. Найди метод `buildMarkdownFromReport`. +2. В местах, где сейчас формируются таблицы (например, для "seasonality", "competitors", "audienceSegmentation", "channelsPotential", "conversionFunnel"), замени генерацию таблиц на вставку JSON-блока. +3. Формат блока должен быть таким: + ```json:chart-type + { ... json data ... } + ``` + +```` + +Где `chart-type` — это идентификатор типа графика (например, `json:seasonality`, `json:competitors`, `json:funnel`). Это поможет фронтенду понять, какой компонент графика рисовать. +4\. Используй поле `objectMapper` для сериализации соответствующих карт/списков (chartsData) в JSON-строку перед вставкой. +5\. Убедись, что JSON отформатирован красиво (pretty print), чтобы он был читаемым, если смотреть на raw text. + +**Пример желаемого результата в Markdown:** +Вместо таблицы с месяцами, должно получиться: + +### 1️⃣ Сезонность спроса (12 месяцев) + +```json:seasonality +{ + "Январь": 70, + "Февраль": 60, + ... +} +``` + +Пожалуйста, рефактори метод `buildMarkdownFromReport` соответствующим образом. + +```` + +--- + +### Как это будет работать (для твоего понимания) + +После того как Cursor применит изменения, твой метод `buildMarkdownFromReport` будет выглядеть примерно так (псевдокод): + +````java +// Внутри buildMarkdownFromReport + +// ... заголовки ... + +if (chartsData.containsKey("seasonality")) { + markdown.append("### 1️⃣ Сезонность спроса (12 месяцев)\n\n"); + + // Сериализуем данные в JSON + String jsonStr = objectMapper.writerWithDefaultPrettyPrinter() + .writeValueAsString(chartsData.get("seasonality")); + + // Вставляем как блок кода с уникальным идентификатором языка + markdown.append("```json:seasonality\n"); + markdown.append(jsonStr); + markdown.append("\n```\n\n"); +} +```` + +### Что нужно сделать на Фронтенде? + +Так как мы изменили подход, фронтенд (React/Vue) должен использовать библиотеку для рендеринга Markdown (например, `react-markdown`). + +Тебе нужно будет написать кастомный рендерер для элементов `code`. + +**Пример логики для фронтенда (React + react-markdown):** + +```javascript +import ReactMarkdown from 'react-markdown'; +import { SeasonalityChart, FunnelChart } from './MyCharts'; // Твои компоненты графиков + +const MarkdownRender = ({ content }) => { + return ( + ; + case 'funnel': + return ; + default: + return
{children}
; // Фоллбек + } + } + + return ( + + {children} + + ); + }, + }} + > + {content} +
+ ); +}; +``` diff --git a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java index 669100f..529708e 100644 --- a/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java +++ b/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java @@ -13,6 +13,7 @@ import org.springframework.http.MediaType; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -1084,115 +1085,32 @@ public class MarketingAnalysisService { if (chartsData != null && !chartsData.isEmpty()) { markdown.append("## 📊 Числовые данные для графиков\n\n"); - // Seasonality table + // Seasonality JSON block @SuppressWarnings("unchecked") Map seasonality = (Map) chartsData.get("seasonality"); - if (seasonality != null && !seasonality.isEmpty()) { - markdown.append("### 1️⃣ Сезонность спроса (12 месяцев)\n\n"); - markdown.append("| Месяц | Уровень спроса (0–100) |\n"); - markdown.append("|-------|------------------------|\n"); - for (Map.Entry entry : seasonality.entrySet()) { - markdown.append("| ").append(entry.getKey()).append(" | ").append(entry.getValue()).append(" |\n"); - } - markdown.append("\n"); - } + appendJsonChart(markdown, "### 1️⃣ Сезонность спроса (12 месяцев)", "seasonality", seasonality); - // Competitors table + // Competitors JSON block @SuppressWarnings("unchecked") List> competitors = (List>) chartsData.get("competitors"); - if (competitors != null && !competitors.isEmpty()) { - markdown.append("### 2️⃣ Сравнительный анализ конкурентов\n\n"); - markdown.append( - "| Конкурент | Охват аудитории | Активность в соцсетях (0–100) | Количество отзывов | Сильные стороны | Слабые стороны |\n"); - markdown.append( - "|-----------|-----------------|-------------------------------|-------------------|----------------|----------------|\n"); - for (Map comp : competitors) { - markdown.append("| ").append(comp.get("name")).append(" | ") - .append(comp.get("reach")).append(" | ") - .append(comp.get("activity")).append(" | ") - .append(comp.get("reviews")).append(" | ") - .append(comp.get("strengths")).append(" | ") - .append(comp.get("weaknesses")).append(" |\n"); - } - markdown.append("\n"); - } + appendJsonChart(markdown, "### 2️⃣ Сравнительный анализ конкурентов", "competitors", competitors); - // Audience segmentation tables + // Audience segmentation JSON block @SuppressWarnings("unchecked") Map audienceSegmentation = (Map) chartsData.get("audienceSegmentation"); - if (audienceSegmentation != null && !audienceSegmentation.isEmpty()) { - markdown.append("### 3️⃣ Сегментация целевой аудитории\n\n"); + appendJsonChart(markdown, "### 3️⃣ Сегментация целевой аудитории", "audienceSegmentation", + audienceSegmentation); - @SuppressWarnings("unchecked") - List> ageGroups = (List>) audienceSegmentation.get("ageGroups"); - if (ageGroups != null && !ageGroups.isEmpty()) { - markdown.append("#### Возрастные группы\n\n"); - markdown.append("| Возрастная группа | Процент (%) |\n"); - markdown.append("|-------------------|-------------|\n"); - for (Map group : ageGroups) { - markdown.append("| ").append(group.get("label")).append(" | ").append(group.get("value")) - .append(" |\n"); - } - markdown.append("\n"); - } - - @SuppressWarnings("unchecked") - List> genders = (List>) audienceSegmentation.get("genders"); - if (genders != null && !genders.isEmpty()) { - markdown.append("#### Пол\n\n"); - markdown.append("| Пол | Процент (%) |\n"); - markdown.append("|-----|-------------|\n"); - for (Map gender : genders) { - markdown.append("| ").append(gender.get("label")).append(" | ").append(gender.get("value")) - .append(" |\n"); - } - markdown.append("\n"); - } - - @SuppressWarnings("unchecked") - List> segments = (List>) audienceSegmentation.get("segments"); - if (segments != null && !segments.isEmpty()) { - markdown.append("#### Сегменты ЦА\n\n"); - markdown.append("| Сегмент | Процент (%) |\n"); - markdown.append("|---------|-------------|\n"); - for (Map segment : segments) { - markdown.append("| ").append(segment.get("label")).append(" | ").append(segment.get("value")) - .append(" |\n"); - } - markdown.append("\n"); - } - } - - // Channels potential table + // Channels potential JSON block @SuppressWarnings("unchecked") List> channelsPotential = (List>) chartsData .get("channelsPotential"); - if (channelsPotential != null && !channelsPotential.isEmpty()) { - markdown.append("### 4️⃣ Потенциал каналов продвижения\n\n"); - markdown.append("| Канал | Потенциал (0–100) | Обоснование |\n"); - markdown.append("|-------|-------------------|-------------|\n"); - for (Map channel : channelsPotential) { - markdown.append("| ").append(channel.get("name")).append(" | ") - .append(channel.get("potential")).append(" | ") - .append(channel.get("justification")).append(" |\n"); - } - markdown.append("\n"); - } + appendJsonChart(markdown, "### 4️⃣ Потенциал каналов продвижения", "channelsPotential", channelsPotential); - // Conversion funnel table + // Conversion funnel JSON block @SuppressWarnings("unchecked") List> conversionFunnel = (List>) chartsData.get("conversionFunnel"); - if (conversionFunnel != null && !conversionFunnel.isEmpty()) { - markdown.append("### 5️⃣ Конверсионная воронка\n\n"); - markdown.append("| Этап | Значение | Конверсия (%) |\n"); - markdown.append("|------|----------|---------------|\n"); - for (Map stage : conversionFunnel) { - markdown.append("| ").append(stage.get("stage")).append(" | ") - .append(stage.get("value")).append(" | ") - .append(stage.get("conversion")).append(" |\n"); - } - markdown.append("\n"); - } + appendJsonChart(markdown, "### 5️⃣ Конверсионная воронка", "funnel", conversionFunnel); // SWOT table (if available from JSON) @SuppressWarnings("unchecked") @@ -1404,6 +1322,34 @@ public class MarketingAnalysisService { return markdown.toString(); } + private void appendJsonChart(StringBuilder markdown, String heading, String chartKey, Object data) { + if (isEmptyChartData(data)) { + return; + } + try { + String jsonStr = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(data); + markdown.append(heading).append("\n\n"); + markdown.append("```json:").append(chartKey).append("\n"); + markdown.append(jsonStr); + markdown.append("\n```\n\n"); + } catch (JsonProcessingException e) { + throw new RuntimeException("Failed to serialize chart data for " + chartKey, e); + } + } + + private boolean isEmptyChartData(Object data) { + if (data == null) { + return true; + } + if (data instanceof Map) { + return ((Map) data).isEmpty(); + } + if (data instanceof Collection) { + return ((Collection) data).isEmpty(); + } + return false; + } + public MarketingAnalysisResult getAnalysisResult(String analysisId) { Optional optAnalysis = repository.findById(analysisId); if (optAnalysis.isEmpty()) {