diff --git a/pom.xml b/pom.xml
index 0577d56..80857b1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -73,6 +73,33 @@
spring-boot-starter-test
test
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+
+ org.apache.poi
+ poi-ooxml
+ 5.2.5
+
+
+
+
+ com.github.librepdf
+ openpdf
+ 1.3.40
+
+
+
+
+ org.jfree
+ jfreechart
+ 1.5.4
+
diff --git a/src/main/java/kz/konturai/parser/controller/ReportController.java b/src/main/java/kz/konturai/parser/controller/ReportController.java
new file mode 100644
index 0000000..f62b1c5
--- /dev/null
+++ b/src/main/java/kz/konturai/parser/controller/ReportController.java
@@ -0,0 +1,79 @@
+package kz.konturai.parser.controller;
+
+import kz.konturai.parser.dto.ReportGenerateRequest;
+import kz.konturai.parser.model.ReportHistory;
+import kz.konturai.parser.repository.ReportHistoryRepository;
+import kz.konturai.parser.service.ReportGenerationService;
+import kz.konturai.parser.service.ReportGenerationService.ReportBinary;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Optional;
+
+@RestController
+@RequestMapping("/api/parser/report")
+public class ReportController {
+
+ private final ReportGenerationService reportGenerationService;
+ private final ReportHistoryRepository reportHistoryRepository;
+
+ public ReportController(ReportGenerationService reportGenerationService,
+ ReportHistoryRepository reportHistoryRepository) {
+ this.reportGenerationService = reportGenerationService;
+ this.reportHistoryRepository = reportHistoryRepository;
+ }
+
+ @PostMapping("/generate")
+ public ResponseEntity generate(@Validated @RequestBody ReportGenerateRequest request) {
+ ReportBinary rb = reportGenerationService.generate(request);
+ return ResponseEntity.ok()
+ .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + rb.getFilename() + "\"")
+ .contentType(rb.getMediaType())
+ .body(rb.getBytes());
+ }
+
+ @GetMapping("/history")
+ public ResponseEntity> history(@RequestParam(name = "page", defaultValue = "0") int page,
+ @RequestParam(name = "size", defaultValue = "20") int size,
+ @RequestParam(name = "sort", defaultValue = "createdAt,desc") String sort) {
+ String[] parts = sort.split(",");
+ String sortField = parts.length > 0 ? parts[0] : "createdAt";
+ Sort.Direction dir = parts.length > 1 && parts[1].equalsIgnoreCase("asc") ? Sort.Direction.ASC
+ : Sort.Direction.DESC;
+ Pageable pageable = PageRequest.of(page, size, Sort.by(dir, sortField));
+ Page res = reportHistoryRepository.findAll(pageable);
+ return ResponseEntity.ok(res);
+ }
+
+ @GetMapping("/history/{id}")
+ public ResponseEntity download(@PathVariable("id") String id) {
+ Optional opt = reportHistoryRepository.findById(id);
+ if (opt.isEmpty()) {
+ return ResponseEntity.notFound().build();
+ }
+ ReportHistory rh = opt.get();
+ Path path = Path.of(rh.getFilePath());
+ try {
+ byte[] bytes = Files.readAllBytes(path);
+ MediaType mt = rh.getFormat() != null && rh.getFormat().equalsIgnoreCase("DOCX")
+ ? MediaType
+ .parseMediaType("application/vnd.openxmlformats-officedocument.wordprocessingml.document")
+ : MediaType.APPLICATION_PDF;
+ return ResponseEntity.ok()
+ .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + rh.getFilename() + "\"")
+ .contentType(mt)
+ .body(bytes);
+ } catch (Exception e) {
+ return ResponseEntity.internalServerError().build();
+ }
+ }
+}
diff --git a/src/main/java/kz/konturai/parser/dto/ReportGenerateRequest.java b/src/main/java/kz/konturai/parser/dto/ReportGenerateRequest.java
new file mode 100644
index 0000000..bbf54f0
--- /dev/null
+++ b/src/main/java/kz/konturai/parser/dto/ReportGenerateRequest.java
@@ -0,0 +1,61 @@
+package kz.konturai.parser.dto;
+
+import java.time.LocalDateTime;
+
+public class ReportGenerateRequest {
+
+ private String reportTitle;
+ private String authorName;
+ private String companyName;
+ private LocalDateTime startDate;
+ private LocalDateTime endDate;
+ private String format; // PDF or DOCX
+
+ public String getReportTitle() {
+ return reportTitle;
+ }
+
+ public void setReportTitle(String reportTitle) {
+ this.reportTitle = reportTitle;
+ }
+
+ public String getAuthorName() {
+ return authorName;
+ }
+
+ public void setAuthorName(String authorName) {
+ this.authorName = authorName;
+ }
+
+ public String getCompanyName() {
+ return companyName;
+ }
+
+ public void setCompanyName(String companyName) {
+ this.companyName = companyName;
+ }
+
+ public LocalDateTime getStartDate() {
+ return startDate;
+ }
+
+ public void setStartDate(LocalDateTime startDate) {
+ this.startDate = startDate;
+ }
+
+ public LocalDateTime getEndDate() {
+ return endDate;
+ }
+
+ public void setEndDate(LocalDateTime endDate) {
+ this.endDate = endDate;
+ }
+
+ public String getFormat() {
+ return format;
+ }
+
+ public void setFormat(String format) {
+ this.format = format;
+ }
+}
diff --git a/src/main/java/kz/konturai/parser/model/ReportHistory.java b/src/main/java/kz/konturai/parser/model/ReportHistory.java
new file mode 100644
index 0000000..dca03dd
--- /dev/null
+++ b/src/main/java/kz/konturai/parser/model/ReportHistory.java
@@ -0,0 +1,132 @@
+package kz.konturai.parser.model;
+
+import org.springframework.data.annotation.Id;
+import org.springframework.data.mongodb.core.mapping.Document;
+import org.springframework.data.mongodb.core.mapping.Field;
+
+import java.time.LocalDateTime;
+
+@Document(collection = "report_history")
+public class ReportHistory {
+
+ @Id
+ private String id;
+
+ @Field("report_title")
+ private String reportTitle;
+
+ @Field("author_name")
+ private String authorName;
+
+ @Field("company_name")
+ private String companyName;
+
+ @Field("start_date")
+ private LocalDateTime startDate;
+
+ @Field("end_date")
+ private LocalDateTime endDate;
+
+ @Field("format")
+ private String format; // PDF or DOCX
+
+ @Field("filename")
+ private String filename;
+
+ @Field("file_path")
+ private String filePath;
+
+ @Field("file_size")
+ private long fileSize;
+
+ @Field("created_at")
+ private LocalDateTime createdAt = LocalDateTime.now();
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getReportTitle() {
+ return reportTitle;
+ }
+
+ public void setReportTitle(String reportTitle) {
+ this.reportTitle = reportTitle;
+ }
+
+ public String getAuthorName() {
+ return authorName;
+ }
+
+ public void setAuthorName(String authorName) {
+ this.authorName = authorName;
+ }
+
+ public String getCompanyName() {
+ return companyName;
+ }
+
+ public void setCompanyName(String companyName) {
+ this.companyName = companyName;
+ }
+
+ public LocalDateTime getStartDate() {
+ return startDate;
+ }
+
+ public void setStartDate(LocalDateTime startDate) {
+ this.startDate = startDate;
+ }
+
+ public LocalDateTime getEndDate() {
+ return endDate;
+ }
+
+ public void setEndDate(LocalDateTime endDate) {
+ this.endDate = endDate;
+ }
+
+ public String getFormat() {
+ return format;
+ }
+
+ public void setFormat(String format) {
+ this.format = format;
+ }
+
+ public String getFilename() {
+ return filename;
+ }
+
+ public void setFilename(String filename) {
+ this.filename = filename;
+ }
+
+ public String getFilePath() {
+ return filePath;
+ }
+
+ public void setFilePath(String filePath) {
+ this.filePath = filePath;
+ }
+
+ public long getFileSize() {
+ return fileSize;
+ }
+
+ public void setFileSize(long fileSize) {
+ this.fileSize = fileSize;
+ }
+
+ public LocalDateTime getCreatedAt() {
+ return createdAt;
+ }
+
+ public void setCreatedAt(LocalDateTime createdAt) {
+ this.createdAt = createdAt;
+ }
+}
diff --git a/src/main/java/kz/konturai/parser/repository/ReportHistoryRepository.java b/src/main/java/kz/konturai/parser/repository/ReportHistoryRepository.java
new file mode 100644
index 0000000..4fccec3
--- /dev/null
+++ b/src/main/java/kz/konturai/parser/repository/ReportHistoryRepository.java
@@ -0,0 +1,12 @@
+package kz.konturai.parser.repository;
+
+import kz.konturai.parser.model.ReportHistory;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface ReportHistoryRepository extends MongoRepository {
+ Page findAllByOrderByCreatedAtDesc(Pageable pageable);
+}
diff --git a/src/main/java/kz/konturai/parser/service/OllamaAnalyticsService.java b/src/main/java/kz/konturai/parser/service/OllamaAnalyticsService.java
index d4c8c94..398066d 100644
--- a/src/main/java/kz/konturai/parser/service/OllamaAnalyticsService.java
+++ b/src/main/java/kz/konturai/parser/service/OllamaAnalyticsService.java
@@ -116,4 +116,38 @@ public class OllamaAnalyticsService {
return null;
}
}
+
+ public String generateWithInstruction(String text, String instruction) {
+ String prompt = instruction + "\n\n" + text;
+ Map requestBody = new HashMap<>();
+ requestBody.put("model", modelName);
+ requestBody.put("prompt", prompt);
+ requestBody.put("stream", false);
+
+ try {
+ Map response = this.webClient.post()
+ .uri("/api/generate")
+ .contentType(MediaType.APPLICATION_JSON)
+ .accept(MediaType.APPLICATION_JSON)
+ .bodyValue(requestBody)
+ .retrieve()
+ .bodyToMono(new ParameterizedTypeReference