This commit is contained in:
root
2025-09-24 00:37:46 +05:00
parent 6b52c915ed
commit 0b2f9bc508
@@ -50,6 +50,36 @@ public class ReportGenerationService {
} }
} }
private static class Sections {
private final String annotation;
private final String introduction;
private final String mainNarrative;
private final String recommendations;
private Sections(String annotation, String introduction, String mainNarrative, String recommendations) {
this.annotation = annotation == null ? "" : annotation;
this.introduction = introduction == null ? "" : introduction;
this.mainNarrative = mainNarrative == null ? "" : mainNarrative;
this.recommendations = recommendations == null ? "" : recommendations;
}
public String getAnnotation() {
return annotation;
}
public String getIntroduction() {
return introduction;
}
public String getMainNarrative() {
return mainNarrative;
}
public String getRecommendations() {
return recommendations;
}
}
private final MarketItemRepository marketItemRepository; private final MarketItemRepository marketItemRepository;
private final OllamaAnalyticsService ollamaAnalyticsService; private final OllamaAnalyticsService ollamaAnalyticsService;
private final ReportHistoryRepository reportHistoryRepository; private final ReportHistoryRepository reportHistoryRepository;
@@ -73,17 +103,17 @@ public class ReportGenerationService {
.findByPublishedAtBetween(start, end, org.springframework.data.domain.Pageable.unpaged()) .findByPublishedAtBetween(start, end, org.springframework.data.domain.Pageable.unpaged())
.getContent(); .getContent();
String annotation = buildAnnotation(items); Sections sections = buildSections(req, items);
ReportBinary rb; ReportBinary rb;
if ("DOCX".equalsIgnoreCase(req.getFormat())) { if ("DOCX".equalsIgnoreCase(req.getFormat())) {
byte[] docx = buildDocx(req, items, annotation); byte[] docx = buildDocx(req, items, sections);
String filename = "report_" + end.toLocalDate().toString() + "-" + System.currentTimeMillis() + ".docx"; String filename = "report_" + end.toLocalDate().toString() + "-" + System.currentTimeMillis() + ".docx";
rb = new ReportBinary(filename, rb = new ReportBinary(filename,
MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.wordprocessingml.document"), MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.wordprocessingml.document"),
docx); docx);
} else { } else {
byte[] pdf = buildPdf(req, items, annotation); byte[] pdf = buildPdf(req, items, sections);
String filename = "report_" + end.toLocalDate().toString() + "-" + System.currentTimeMillis() + ".pdf"; String filename = "report_" + end.toLocalDate().toString() + "-" + System.currentTimeMillis() + ".pdf";
rb = new ReportBinary(filename, MediaType.APPLICATION_PDF, pdf); rb = new ReportBinary(filename, MediaType.APPLICATION_PDF, pdf);
} }
@@ -114,11 +144,15 @@ public class ReportGenerationService {
} }
} }
private String buildAnnotation(List<MarketItem> items) { private String getCombinedSummaries(List<MarketItem> items) {
String combined = items.stream() return items.stream()
.map(i -> i.getAnalytics() != null ? i.getAnalytics().getSummary() : null) .map(i -> i.getAnalytics() != null ? i.getAnalytics().getSummary() : null)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.collect(Collectors.joining("\n\n")); .collect(Collectors.joining("\n\n"));
}
private String buildAnnotation(List<MarketItem> items) {
String combined = getCombinedSummaries(items);
if (combined.isBlank()) { if (combined.isBlank()) {
return ""; return "";
} }
@@ -127,7 +161,27 @@ public class ReportGenerationService {
return res == null ? "" : res; return res == null ? "" : res;
} }
private byte[] buildDocx(ReportGenerateRequest req, List<MarketItem> items, String annotation) { private Sections buildSections(ReportGenerateRequest req, List<MarketItem> items) {
String combined = getCombinedSummaries(items);
if (combined.isBlank()) {
return new Sections("", "", "", "");
}
String annotation = ollamaAnalyticsService.generateWithInstruction(combined,
"На основе кратких сводок новостей напиши краткое содержание (2-3 абзаца), выделив ключевые тренды и события. Стиль: деловой, нейтральный.");
String intro = ollamaAnalyticsService.generateWithInstruction(combined,
"Напиши краткое введение к аналитическому отчёту за период. Укажи цель отчёта, источники данных и подход к анализу. Объём: 1 абзац.");
String mainPart = ollamaAnalyticsService.generateWithInstruction(combined,
"Сформируй основную часть отчёта: перечисли и кратко объясни 3-5 ключевых тенденций и событий периода, добавь замеченные риски и возможности. Используй маркированные пункты и короткие абзацы.");
String recs = ollamaAnalyticsService.generateWithInstruction(combined,
"На основе сводок сформулируй практические рекомендации (4-6 пунктов) для бизнеса/маркетинга. Формат: маркированный список, краткие и конкретные формулировки.");
return new Sections(annotation, intro, mainPart, recs);
}
private byte[] buildDocx(ReportGenerateRequest req, List<MarketItem> items, Sections sections) {
try (XWPFDocument doc = new XWPFDocument(); ByteArrayOutputStream out = new ByteArrayOutputStream()) { try (XWPFDocument doc = new XWPFDocument(); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
DateTimeFormatter dt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); DateTimeFormatter dt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
@@ -144,16 +198,19 @@ public class ReportGenerationService {
// Annotation // Annotation
addHeading(doc, "Краткое содержание"); addHeading(doc, "Краткое содержание");
addParagraph(doc, annotation.isBlank() ? "(недоступно)" : annotation); addParagraph(doc, sections.getAnnotation().isBlank() ? "(недоступно)" : sections.getAnnotation());
// Intro // Intro
addHeading(doc, "Введение"); addHeading(doc, "Введение");
addParagraph(doc, String.format( addParagraph(doc, sections.getIntroduction().isBlank()
"Целью данного отчёта является анализ новостного фона в сфере маркетинга и бизнеса за период с %s по %s. Были проанализированы публикации из ключевых источников для выявления основных трендов.", ? String.format(
req.getStartDate().format(dt), req.getEndDate().format(dt))); "Целью данного отчёта является анализ новостного фона в сфере маркетинга и бизнеса за период с %s по %s. Были проанализированы публикации из ключевых источников для выявления основных трендов.",
req.getStartDate().format(dt), req.getEndDate().format(dt))
: sections.getIntroduction());
// Main // Main
addHeading(doc, "Основная часть"); addHeading(doc, "Основная часть");
addParagraph(doc, sections.getMainNarrative());
addSubheading(doc, "Ключевые публикации"); addSubheading(doc, "Ключевые публикации");
List<MarketItem> top = items.stream() List<MarketItem> top = items.stream()
.sorted(Comparator .sorted(Comparator
@@ -187,7 +244,9 @@ public class ReportGenerationService {
// Recommendations // Recommendations
addHeading(doc, "Рекомендации"); addHeading(doc, "Рекомендации");
addParagraph(doc, "Рассмотрите возможность усиления присутствия в медиа по ключевым темам отчёта."); addParagraph(doc, sections.getRecommendations().isBlank()
? "Рассмотрите возможность усиления присутствия в медиа по ключевым темам отчёта."
: sections.getRecommendations());
// Bibliography // Bibliography
addHeading(doc, "Список литературы"); addHeading(doc, "Список литературы");
@@ -207,7 +266,7 @@ public class ReportGenerationService {
} }
} }
private byte[] buildPdf(ReportGenerateRequest req, List<MarketItem> items, String annotation) { private byte[] buildPdf(ReportGenerateRequest req, List<MarketItem> items, Sections sections) {
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
Document pdf = new Document(); Document pdf = new Document();
PdfWriter.getInstance(pdf, out); PdfWriter.getInstance(pdf, out);
@@ -223,14 +282,17 @@ public class ReportGenerationService {
"\nСодержание: Аннотация, Введение, Основная часть, Рекомендации, Список литературы, Приложения\n")); "\nСодержание: Аннотация, Введение, Основная часть, Рекомендации, Список литературы, Приложения\n"));
pdf.add(new Paragraph("Краткое содержание")); pdf.add(new Paragraph("Краткое содержание"));
pdf.add(new Paragraph(annotation.isBlank() ? "(недоступно)" : annotation)); pdf.add(new Paragraph(sections.getAnnotation().isBlank() ? "(недоступно)" : sections.getAnnotation()));
pdf.add(new Paragraph("Введение")); pdf.add(new Paragraph("Введение"));
pdf.add(new Paragraph(String.format( pdf.add(new Paragraph(sections.getIntroduction().isBlank()
"Целью данного отчёта является анализ новостного фона в сфере маркетинга и бизнеса за период с %s по %s. Были проанализированы публикации из ключевых источников для выявления основных трендов.", ? String.format(
req.getStartDate().format(dt), req.getEndDate().format(dt)))); "Целью данного отчёта является анализ новостного фона в сфере маркетинга и бизнеса за период с %s по %s. Были проанализированы публикации из ключевых источников для выявления основных трендов.",
req.getStartDate().format(dt), req.getEndDate().format(dt))
: sections.getIntroduction()));
pdf.add(new Paragraph("Основная часть")); pdf.add(new Paragraph("Основная часть"));
pdf.add(new Paragraph(sections.getMainNarrative()));
pdf.add(new Paragraph("Ключевые публикации")); pdf.add(new Paragraph("Ключевые публикации"));
List<MarketItem> top = items.stream() List<MarketItem> top = items.stream()
.sorted(Comparator .sorted(Comparator
@@ -262,7 +324,9 @@ public class ReportGenerationService {
pdf.add(new Paragraph("Персоны: " + formatTop(persons))); pdf.add(new Paragraph("Персоны: " + formatTop(persons)));
pdf.add(new Paragraph("Рекомендации")); pdf.add(new Paragraph("Рекомендации"));
pdf.add(new Paragraph("Рассмотрите возможность усиления присутствия в медиа по ключевым темам отчёта.")); pdf.add(new Paragraph(sections.getRecommendations().isBlank()
? "Рассмотрите возможность усиления присутствия в медиа по ключевым темам отчёта."
: sections.getRecommendations()));
pdf.add(new Paragraph("Список литературы")); pdf.add(new Paragraph("Список литературы"));
for (MarketItem mi : items) { for (MarketItem mi : items) {