.
This commit is contained in:
@@ -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 (
|
||||
<ReactMarkdown
|
||||
components={{
|
||||
code({ node, inline, className, children, ...props }) {
|
||||
const match = /language-(\w+):(\w+)/.exec(className || '');
|
||||
// match[1] будет "json", match[2] будет тип графика (seasonality, funnel и т.д.)
|
||||
|
||||
if (!inline && match && match[1] === 'json') {
|
||||
const chartType = match[2];
|
||||
const data = JSON.parse(String(children).replace(/\n$/, ''));
|
||||
|
||||
switch (chartType) {
|
||||
case 'seasonality':
|
||||
return <SeasonalityChart data={data} />;
|
||||
case 'funnel':
|
||||
return <FunnelChart data={data} />;
|
||||
default:
|
||||
return <pre>{children}</pre>; // Фоллбек
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<code className={className} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
},
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</ReactMarkdown>
|
||||
);
|
||||
};
|
||||
```
|
||||
@@ -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<String, Integer> seasonality = (Map<String, Integer>) 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<String, Integer> 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<Map<String, Object>> competitors = (List<Map<String, Object>>) chartsData.get("competitors");
|
||||
if (competitors != null && !competitors.isEmpty()) {
|
||||
markdown.append("### 2️⃣ Сравнительный анализ конкурентов\n\n");
|
||||
markdown.append(
|
||||
"| Конкурент | Охват аудитории | Активность в соцсетях (0–100) | Количество отзывов | Сильные стороны | Слабые стороны |\n");
|
||||
markdown.append(
|
||||
"|-----------|-----------------|-------------------------------|-------------------|----------------|----------------|\n");
|
||||
for (Map<String, Object> 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<String, Object> audienceSegmentation = (Map<String, Object>) chartsData.get("audienceSegmentation");
|
||||
if (audienceSegmentation != null && !audienceSegmentation.isEmpty()) {
|
||||
markdown.append("### 3️⃣ Сегментация целевой аудитории\n\n");
|
||||
appendJsonChart(markdown, "### 3️⃣ Сегментация целевой аудитории", "audienceSegmentation",
|
||||
audienceSegmentation);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, String>> ageGroups = (List<Map<String, String>>) audienceSegmentation.get("ageGroups");
|
||||
if (ageGroups != null && !ageGroups.isEmpty()) {
|
||||
markdown.append("#### Возрастные группы\n\n");
|
||||
markdown.append("| Возрастная группа | Процент (%) |\n");
|
||||
markdown.append("|-------------------|-------------|\n");
|
||||
for (Map<String, String> group : ageGroups) {
|
||||
markdown.append("| ").append(group.get("label")).append(" | ").append(group.get("value"))
|
||||
.append(" |\n");
|
||||
}
|
||||
markdown.append("\n");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, String>> genders = (List<Map<String, String>>) audienceSegmentation.get("genders");
|
||||
if (genders != null && !genders.isEmpty()) {
|
||||
markdown.append("#### Пол\n\n");
|
||||
markdown.append("| Пол | Процент (%) |\n");
|
||||
markdown.append("|-----|-------------|\n");
|
||||
for (Map<String, String> gender : genders) {
|
||||
markdown.append("| ").append(gender.get("label")).append(" | ").append(gender.get("value"))
|
||||
.append(" |\n");
|
||||
}
|
||||
markdown.append("\n");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Map<String, String>> segments = (List<Map<String, String>>) audienceSegmentation.get("segments");
|
||||
if (segments != null && !segments.isEmpty()) {
|
||||
markdown.append("#### Сегменты ЦА\n\n");
|
||||
markdown.append("| Сегмент | Процент (%) |\n");
|
||||
markdown.append("|---------|-------------|\n");
|
||||
for (Map<String, String> 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<Map<String, Object>> channelsPotential = (List<Map<String, Object>>) chartsData
|
||||
.get("channelsPotential");
|
||||
if (channelsPotential != null && !channelsPotential.isEmpty()) {
|
||||
markdown.append("### 4️⃣ Потенциал каналов продвижения\n\n");
|
||||
markdown.append("| Канал | Потенциал (0–100) | Обоснование |\n");
|
||||
markdown.append("|-------|-------------------|-------------|\n");
|
||||
for (Map<String, Object> 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<Map<String, Object>> conversionFunnel = (List<Map<String, Object>>) chartsData.get("conversionFunnel");
|
||||
if (conversionFunnel != null && !conversionFunnel.isEmpty()) {
|
||||
markdown.append("### 5️⃣ Конверсионная воронка\n\n");
|
||||
markdown.append("| Этап | Значение | Конверсия (%) |\n");
|
||||
markdown.append("|------|----------|---------------|\n");
|
||||
for (Map<String, Object> 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<MarketingAnalysis> optAnalysis = repository.findById(analysisId);
|
||||
if (optAnalysis.isEmpty()) {
|
||||
|
||||
Reference in New Issue
Block a user