This commit is contained in:
root
2025-12-10 18:58:14 +05:00
parent b0b9f18ef5
commit 5a4cd9748d
2 changed files with 149 additions and 94 deletions
+109
View File
@@ -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()) {