+
+ Аналитический отчет готов
+
+
+
Здравствуйте!
+
+
Ваш аналитический отчет успешно сгенерирован и готов к просмотру.
+
+
+
Детали отчета:
+
Название: %s
+
Автор: %s
+
Компания: %s
+
Дата создания: %s
+
+
+
PDF файл с отчетом прикреплен к данному письму. Вы можете скачать его и сохранить для дальнейшего использования.
+
+
Если у вас возникли вопросы или нужна дополнительная информация, пожалуйста, свяжитесь с нами.
+
+
+
+
+ С уважением,
+ Команда Konturai Analytics
+ Это письмо отправлено автоматически, пожалуйста, не отвечайте на него.
+
+
+
+
+ """,
+ reportTitle != null ? reportTitle : "Не указано",
+ authorName != null ? authorName : "Не указан",
+ companyName != null ? companyName : "Не указана",
+ currentDate);
+ }
+}
diff --git a/src/main/java/kz/konturai/parser/service/ReportGenerationService.java b/src/main/java/kz/konturai/parser/service/ReportGenerationService.java
index 89195d0..ad4e8f4 100644
--- a/src/main/java/kz/konturai/parser/service/ReportGenerationService.java
+++ b/src/main/java/kz/konturai/parser/service/ReportGenerationService.java
@@ -85,15 +85,18 @@ public class ReportGenerationService {
private final OllamaAnalyticsService ollamaAnalyticsService;
private final ReportHistoryRepository reportHistoryRepository;
private final MinIOService minIOService;
+ private final EmailService emailService;
public ReportGenerationService(MarketItemRepository marketItemRepository,
OllamaAnalyticsService ollamaAnalyticsService,
ReportHistoryRepository reportHistoryRepository,
- MinIOService minIOService) {
+ MinIOService minIOService,
+ EmailService emailService) {
this.marketItemRepository = marketItemRepository;
this.ollamaAnalyticsService = ollamaAnalyticsService;
this.reportHistoryRepository = reportHistoryRepository;
this.minIOService = minIOService;
+ this.emailService = emailService;
}
public ReportBinary simpleGenerate(ReportGenerateRequest req) {
@@ -135,7 +138,8 @@ public class ReportGenerationService {
public void generateAsync(ReportGenerateRequest req) {
try {
generate(req);
- // Report is automatically saved to database and MinIO in the generate() method
+ // Report is automatically saved to database, MinIO and sent by email in the
+ // generate() method
} catch (Exception e) {
// Log error but don't throw to avoid async method issues
System.err.println("Error generating report asynchronously: " + e.getMessage());
@@ -165,6 +169,24 @@ public class ReportGenerationService {
rb = new ReportBinary(filename, MediaType.APPLICATION_PDF, pdf);
}
persistReport(req, rb);
+
+ // Отправляем PDF на email, если указан email и формат PDF
+ if (req.getRecipientEmail() != null && !req.getRecipientEmail().trim().isEmpty()
+ && "PDF".equalsIgnoreCase(req.getFormat())) {
+ try {
+ emailService.sendReportByEmail(
+ req.getRecipientEmail(),
+ req.getReportTitle(),
+ req.getAuthorName(),
+ req.getCompanyName(),
+ rb.getBytes(),
+ rb.getFilename());
+ } catch (Exception e) {
+ System.err.println("Ошибка при отправке email: " + e.getMessage());
+ // Не прерываем выполнение, если email не отправился
+ }
+ }
+
return rb;
}
diff --git a/src/main/java/kz/konturai/parser/service/ResearchPdfService.java b/src/main/java/kz/konturai/parser/service/ResearchPdfService.java
new file mode 100644
index 0000000..5f09098
--- /dev/null
+++ b/src/main/java/kz/konturai/parser/service/ResearchPdfService.java
@@ -0,0 +1,203 @@
+package kz.konturai.parser.service;
+
+import com.lowagie.text.*;
+import com.lowagie.text.pdf.PdfWriter;
+import kz.konturai.parser.dto.DeepResearchResponse;
+import org.springframework.stereotype.Service;
+
+import java.io.ByteArrayOutputStream;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+
+@Service
+public class ResearchPdfService {
+
+ private static final Font TITLE_FONT = new Font(Font.HELVETICA, 18, Font.BOLD);
+ private static final Font HEADING_FONT = new Font(Font.HELVETICA, 14, Font.BOLD);
+ private static final Font SUBHEADING_FONT = new Font(Font.HELVETICA, 12, Font.BOLD);
+ private static final Font NORMAL_FONT = new Font(Font.HELVETICA, 10, Font.NORMAL);
+ private static final Font SMALL_FONT = new Font(Font.HELVETICA, 8, Font.NORMAL);
+
+ /**
+ * Генерирует PDF отчёт на основе ответа от deep-research API
+ */
+ public byte[] generatePdfReport(String query, DeepResearchResponse researchResponse) {
+ try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
+ Document document = new Document(PageSize.A4);
+ PdfWriter.getInstance(document, outputStream);
+ document.open();
+
+ // Титульная страница
+ addTitlePage(document, query);
+
+ // Содержание
+ addTableOfContents(document);
+
+ // Основной отчёт
+ addMainReport(document, researchResponse);
+
+ // Список источников
+ addSources(document, researchResponse.getVisitedUrls());
+
+ document.close();
+ return outputStream.toByteArray();
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to generate PDF report", e);
+ }
+ }
+
+ private void addTitlePage(Document document, String query) throws DocumentException {
+ // Заголовок
+ Paragraph title = new Paragraph(query, TITLE_FONT);
+ title.setAlignment(Element.ALIGN_CENTER);
+ title.setSpacingAfter(50);
+ document.add(title);
+
+ // Подзаголовок
+ Paragraph subtitle = new Paragraph("Исследовательский отчёт", HEADING_FONT);
+ subtitle.setAlignment(Element.ALIGN_CENTER);
+ subtitle.setSpacingAfter(30);
+ document.add(subtitle);
+
+ // Информация о дате
+ String currentDate = LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"));
+ Paragraph date = new Paragraph("Дата создания: " + currentDate, NORMAL_FONT);
+ date.setAlignment(Element.ALIGN_CENTER);
+ date.setSpacingAfter(100);
+ document.add(date);
+
+ // Пустая строка для перехода на следующую страницу
+ document.add(new Paragraph(" "));
+ document.newPage();
+ }
+
+ private void addTableOfContents(Document document) throws DocumentException {
+ Paragraph tocTitle = new Paragraph("Содержание", HEADING_FONT);
+ tocTitle.setSpacingAfter(20);
+ document.add(tocTitle);
+
+ // Автоматически генерируемое содержание
+ String[] tocItems = {
+ "1. Введение",
+ "2. Основная часть",
+ "3. Заключение",
+ "4. Источники"
+ };
+
+ for (String item : tocItems) {
+ Paragraph tocItem = new Paragraph(item, NORMAL_FONT);
+ tocItem.setSpacingAfter(5);
+ document.add(tocItem);
+ }
+
+ document.add(new Paragraph(" "));
+ document.newPage();
+ }
+
+ private void addMainReport(Document document, DeepResearchResponse researchResponse) throws DocumentException {
+ String content = researchResponse.getMainContent();
+ if (content == null || content.trim().isEmpty()) {
+ content = "Отчёт не был сгенерирован.";
+ }
+
+ // Разбиваем контент на параграфы
+ String[] paragraphs = content.split("\n\n");
+
+ // Введение
+ Paragraph introTitle = new Paragraph("1. Введение", HEADING_FONT);
+ introTitle.setSpacingAfter(10);
+ document.add(introTitle);
+
+ Paragraph intro = new Paragraph(
+ "Данный отчёт представляет результаты исследования по теме: \"" +
+ researchResponse.getReport() != null ? "Полный отчёт"
+ : "Краткий ответ" + "\". " +
+ "Исследование проводилось с использованием автоматизированных инструментов анализа данных.",
+ NORMAL_FONT);
+ intro.setSpacingAfter(15);
+ document.add(intro);
+
+ // Основная часть
+ Paragraph mainTitle = new Paragraph("2. Основная часть", HEADING_FONT);
+ mainTitle.setSpacingAfter(10);
+ document.add(mainTitle);
+
+ // Добавляем основной контент
+ for (String paragraph : paragraphs) {
+ if (paragraph.trim().isEmpty())
+ continue;
+
+ // Проверяем, является ли строка заголовком (начинается с цифры или содержит
+ // ключевые слова)
+ if (isHeading(paragraph)) {
+ Paragraph heading = new Paragraph(paragraph, SUBHEADING_FONT);
+ heading.setSpacingAfter(5);
+ document.add(heading);
+ } else {
+ Paragraph para = new Paragraph(paragraph, NORMAL_FONT);
+ para.setSpacingAfter(8);
+ document.add(para);
+ }
+ }
+
+ // Заключение
+ Paragraph conclusionTitle = new Paragraph("3. Заключение", HEADING_FONT);
+ conclusionTitle.setSpacingAfter(10);
+ document.add(conclusionTitle);
+
+ Paragraph conclusion = new Paragraph(
+ "Исследование завершено. Полученные данные и выводы представлены в основной части отчёта. " +
+ "Для получения дополнительной информации рекомендуется обратиться к первоисточникам, " +
+ "указанным в разделе \"Источники\".",
+ NORMAL_FONT);
+ conclusion.setSpacingAfter(15);
+ document.add(conclusion);
+ }
+
+ private void addSources(Document document, List