Техническое Задание: Опциональное добавление диаграмм в 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, чтобы он мог принимать и вставлять список изображений, если он не пуст.

