diff --git a/src/main/java/kz/konturai/parser/service/ReportGenerationService.java b/src/main/java/kz/konturai/parser/service/ReportGenerationService.java index 167ae74..6261dd0 100644 --- a/src/main/java/kz/konturai/parser/service/ReportGenerationService.java +++ b/src/main/java/kz/konturai/parser/service/ReportGenerationService.java @@ -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 OllamaAnalyticsService ollamaAnalyticsService; private final ReportHistoryRepository reportHistoryRepository; @@ -73,17 +103,17 @@ public class ReportGenerationService { .findByPublishedAtBetween(start, end, org.springframework.data.domain.Pageable.unpaged()) .getContent(); - String annotation = buildAnnotation(items); + Sections sections = buildSections(req, items); ReportBinary rb; 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"; rb = new ReportBinary(filename, MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.wordprocessingml.document"), docx); } else { - byte[] pdf = buildPdf(req, items, annotation); + byte[] pdf = buildPdf(req, items, sections); String filename = "report_" + end.toLocalDate().toString() + "-" + System.currentTimeMillis() + ".pdf"; rb = new ReportBinary(filename, MediaType.APPLICATION_PDF, pdf); } @@ -114,11 +144,15 @@ public class ReportGenerationService { } } - private String buildAnnotation(List items) { - String combined = items.stream() + private String getCombinedSummaries(List items) { + return items.stream() .map(i -> i.getAnalytics() != null ? i.getAnalytics().getSummary() : null) .filter(Objects::nonNull) .collect(Collectors.joining("\n\n")); + } + + private String buildAnnotation(List items) { + String combined = getCombinedSummaries(items); if (combined.isBlank()) { return ""; } @@ -127,7 +161,27 @@ public class ReportGenerationService { return res == null ? "" : res; } - private byte[] buildDocx(ReportGenerateRequest req, List items, String annotation) { + private Sections buildSections(ReportGenerateRequest req, List 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 items, Sections sections) { try (XWPFDocument doc = new XWPFDocument(); ByteArrayOutputStream out = new ByteArrayOutputStream()) { DateTimeFormatter dt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); @@ -144,16 +198,19 @@ public class ReportGenerationService { // Annotation addHeading(doc, "Краткое содержание"); - addParagraph(doc, annotation.isBlank() ? "(недоступно)" : annotation); + addParagraph(doc, sections.getAnnotation().isBlank() ? "(недоступно)" : sections.getAnnotation()); // Intro addHeading(doc, "Введение"); - addParagraph(doc, String.format( - "Целью данного отчёта является анализ новостного фона в сфере маркетинга и бизнеса за период с %s по %s. Были проанализированы публикации из ключевых источников для выявления основных трендов.", - req.getStartDate().format(dt), req.getEndDate().format(dt))); + addParagraph(doc, sections.getIntroduction().isBlank() + ? String.format( + "Целью данного отчёта является анализ новостного фона в сфере маркетинга и бизнеса за период с %s по %s. Были проанализированы публикации из ключевых источников для выявления основных трендов.", + req.getStartDate().format(dt), req.getEndDate().format(dt)) + : sections.getIntroduction()); // Main addHeading(doc, "Основная часть"); + addParagraph(doc, sections.getMainNarrative()); addSubheading(doc, "Ключевые публикации"); List top = items.stream() .sorted(Comparator @@ -187,7 +244,9 @@ public class ReportGenerationService { // Recommendations addHeading(doc, "Рекомендации"); - addParagraph(doc, "Рассмотрите возможность усиления присутствия в медиа по ключевым темам отчёта."); + addParagraph(doc, sections.getRecommendations().isBlank() + ? "Рассмотрите возможность усиления присутствия в медиа по ключевым темам отчёта." + : sections.getRecommendations()); // Bibliography addHeading(doc, "Список литературы"); @@ -207,7 +266,7 @@ public class ReportGenerationService { } } - private byte[] buildPdf(ReportGenerateRequest req, List items, String annotation) { + private byte[] buildPdf(ReportGenerateRequest req, List items, Sections sections) { try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { Document pdf = new Document(); PdfWriter.getInstance(pdf, out); @@ -223,14 +282,17 @@ public class ReportGenerationService { "\nСодержание: Аннотация, Введение, Основная часть, Рекомендации, Список литературы, Приложения\n")); 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(String.format( - "Целью данного отчёта является анализ новостного фона в сфере маркетинга и бизнеса за период с %s по %s. Были проанализированы публикации из ключевых источников для выявления основных трендов.", - req.getStartDate().format(dt), req.getEndDate().format(dt)))); + pdf.add(new Paragraph(sections.getIntroduction().isBlank() + ? String.format( + "Целью данного отчёта является анализ новостного фона в сфере маркетинга и бизнеса за период с %s по %s. Были проанализированы публикации из ключевых источников для выявления основных трендов.", + req.getStartDate().format(dt), req.getEndDate().format(dt)) + : sections.getIntroduction())); pdf.add(new Paragraph("Основная часть")); + pdf.add(new Paragraph(sections.getMainNarrative())); pdf.add(new Paragraph("Ключевые публикации")); List top = items.stream() .sorted(Comparator @@ -262,7 +324,9 @@ public class ReportGenerationService { pdf.add(new Paragraph("Персоны: " + formatTop(persons))); pdf.add(new Paragraph("Рекомендации")); - pdf.add(new Paragraph("Рассмотрите возможность усиления присутствия в медиа по ключевым темам отчёта.")); + pdf.add(new Paragraph(sections.getRecommendations().isBlank() + ? "Рассмотрите возможность усиления присутствия в медиа по ключевым темам отчёта." + : sections.getRecommendations())); pdf.add(new Paragraph("Список литературы")); for (MarketItem mi : items) {