134 lines
8.5 KiB
Markdown
134 lines
8.5 KiB
Markdown
Техническое Задание: Опциональное добавление диаграмм в PDF-отчёты
|
||
1. Общее описание
|
||
Целью является улучшение существующих PDF-отчётов путём опционального добавления в них диаграмм и графиков. Основной процесс генерации текстового отчёта на основе learnings остаётся без изменений. Параллельно с этим, AI-агент будет пытаться найти в "сырых" данных (learnings) числовую информацию, подходящую для визуализации.
|
||
|
||
Если такие данные найдены, агент сгенерирует диаграмму и вставит её в PDF. Если данных нет, будет сгенерирован обычный текстовый отчёт без диаграмм. Процесс не должен прерываться, если сгенерировать диаграмму невозможно.
|
||
|
||
2. Обновлённая схема работы
|
||
Процесс будет состоять из двух параллельных веток, которые затем объединяются:
|
||
|
||
Сбор данных: Бэкенд вызывает deep-research API и получает learnings и visitedUrls.
|
||
|
||
Запускаются два асинхронных вызова к Ollama:
|
||
|
||
Ветка A (Синтез текста - основная): Все learnings отправляются в Ollama с промптом на написание красивого, структурированного текстового отчёта в формате Markdown. (Этот шаг остаётся без изменений).
|
||
|
||
Ветка B (Поиск данных для диаграммы - опциональная): Все learnings отправляются в Ollama с новым, специальным промптом для поиска числовых данных и возврата их в виде JSON.
|
||
|
||
Обработка Ветки B (если успешно):
|
||
|
||
Если Ollama вернул валидный JSON с данными, делается третий вызов к Ollama с промптом на генерацию SVG-кода этой диаграммы.
|
||
|
||
|
||
Shutterstock
|
||
* Полученный SVG-код конвертируется в изображение (PNG) на стороне Java.
|
||
Финальная сборка PDF:
|
||
|
||
ResearchPdfService получает обязательный текстовый отчёт из Ветки А, опциональное изображение диаграммы из Ветки B и список visitedUrls.
|
||
|
||
Он собирает всё это в единый PDF-документ и возвращает пользователю.
|
||
|
||
3. Требования к реализации
|
||
3.1. Модификация сервиса синтеза (например, ReportSynthesisService)
|
||
|
||
Этот сервис теперь будет оркестрировать несколько асинхронных вызовов.
|
||
|
||
Основной метод synthesizeReportAndCharts будет возвращать объект, содержащий и текст, и изображения, например Mono<FinalReportPayload>.
|
||
|
||
Java
|
||
|
||
class FinalReportPayload {
|
||
String markdownContent;
|
||
List<byte[]> chartImages;
|
||
// ...
|
||
}
|
||
Внутри этого метода будут асинхронно запускаться два вызова к Ollama:
|
||
|
||
generateTextReport(learnings) - для получения текста.
|
||
|
||
generateChartData(learnings) - для получения JSON для диаграммы.
|
||
|
||
3.2. Промпт-инжиниринг для диаграмм (Ветка B)
|
||
|
||
Шаг 1: Извлечение данных в JSON
|
||
|
||
Задача: Найти в тексте данные для визуализации.
|
||
|
||
Промпт:
|
||
|
||
"Ты — AI-аналитик данных. Внимательно проанализируй следующий текст. Если найдёшь в нём числовые данные, которые можно представить в виде простой столбчатой или круговой диаграммы (например, рост по годам, доли рынка, сравнение показателей), верни ТОЛЬКО один JSON-объект со структурой: {\"chartType\": \"bar\" или \"pie\", \"title\": \"Название диаграммы\", \"labels\": [\"Метка 1\", \"Метка 2\"], \"data\": [число1, число2]}. Если подходящих данных нет, верни пустой JSON-объект {}. Текст для анализа:\n---\n${learnings_as_string}\n---"
|
||
|
||
Шаг 2: Генерация SVG из JSON
|
||
|
||
Задача: Превратить JSON с данными в SVG-код.
|
||
|
||
Промпт:
|
||
|
||
"Ты — эксперт по визуализации данных. На основе следующего JSON, сгенерируй полный и валидный SVG-код для диаграммы. SVG должен быть стильным и читаемым, с подписями на русском языке. Не добавляй никаких комментариев, верни ТОЛЬКО SVG-код. JSON с данными:\n---\n${json_from_previous_step}\n---"
|
||
|
||
3.3. Конвертация SVG в изображение
|
||
|
||
Этот модуль остаётся без изменений. Используется библиотека Apache Batik для преобразования SVG-строки в byte[] PNG-изображения.
|
||
|
||
Зависимость в pom.xml:
|
||
|
||
XML
|
||
|
||
<dependency>
|
||
<groupId>org.apache.xmlgraphics</groupId>
|
||
<artifactId>batik-transcoder</artifactId>
|
||
<version>1.17</version>
|
||
</dependency>
|
||
Вспомогательный класс-конвертер остаётся тем же.
|
||
|
||
3.4. Модификация PDF-сервиса (ResearchPdfService)
|
||
|
||
Сервис должен быть готов к тому, что диаграмм может и не быть.
|
||
|
||
Измените метод generatePdfReport, чтобы он принимал список изображений, который может быть пустым:
|
||
|
||
Java
|
||
|
||
public byte[] generatePdfReport(String query, String markdownContent, List<String> visitedUrls, List<byte[]> chartImages)
|
||
Внутри метода добавьте проверку: если список chartImages не пустой, вставляйте изображения в документ.
|
||
|
||
Java
|
||
|
||
// ... после вставки основного текста отчёта ...
|
||
|
||
if (chartImages != null && !chartImages.isEmpty()) {
|
||
document.newPage();
|
||
document.add(new Paragraph("Визуализация данных", headingFont));
|
||
|
||
for (byte[] imageData : chartImages) {
|
||
try {
|
||
Image chartImage = Image.getInstance(imageData);
|
||
// Масштабируем, чтобы поместилось на страницу
|
||
float scaler = ((document.getPageSize().getWidth() - document.leftMargin() - document.rightMargin()) / chartImage.getWidth()) * 80; // 80% ширины
|
||
chartImage.scalePercent(scaler);
|
||
chartImage.setAlignment(Element.ALIGN_CENTER);
|
||
|
||
document.add(new Paragraph(" ")); // Отступ
|
||
document.add(chartImage);
|
||
} catch (Exception e) {
|
||
// Логируем ошибку, но не прерываем генерацию PDF
|
||
System.err.println("Не удалось добавить изображение диаграммы в PDF: " + e.getMessage());
|
||
}
|
||
}
|
||
}
|
||
4. План действий
|
||
Добавить зависимость Apache Batik в pom.xml.
|
||
|
||
Реализовать асинхронную логику в ReportSynthesisService, которая параллельно запрашивает у Ollama:
|
||
|
||
Основной текст отчёта.
|
||
|
||
JSON с данными для диаграммы.
|
||
|
||
Добавить в тот же сервис условную логику: если JSON получен, сделать ещё один запрос к Ollama за SVG-кодом.
|
||
|
||
Реализовать SVG-конвертер (или добавить его как util-класс).
|
||
|
||
Обновить ResearchPdfService, чтобы он мог принимать и вставлять список изображений, если он не пуст.
|
||
|