refactor(documents): split getDensity into resolve/load/aggregate (#385)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -19,4 +19,9 @@ public record DocumentDensityResult(
|
|||||||
List<MonthBucket> buckets,
|
List<MonthBucket> buckets,
|
||||||
LocalDate minDate,
|
LocalDate minDate,
|
||||||
LocalDate maxDate
|
LocalDate maxDate
|
||||||
) {}
|
) {
|
||||||
|
/** The "no documents match the filter" result, with no buckets and null date bounds. */
|
||||||
|
public static DocumentDensityResult empty() {
|
||||||
|
return new DocumentDensityResult(List.of(), null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -147,29 +147,41 @@ public class DocumentService {
|
|||||||
* parallel native-query path.
|
* parallel native-query path.
|
||||||
*/
|
*/
|
||||||
public DocumentDensityResult getDensity(DensityFilters filters) {
|
public DocumentDensityResult getDensity(DensityFilters filters) {
|
||||||
String text = filters.text();
|
List<UUID> ftsIds = resolveFtsIds(filters.text());
|
||||||
boolean hasText = StringUtils.hasText(text);
|
if (ftsIds != null && ftsIds.isEmpty()) {
|
||||||
List<UUID> rankedIds = null;
|
return DocumentDensityResult.empty();
|
||||||
if (hasText) {
|
|
||||||
rankedIds = documentRepository.findRankedIdsByFts(text);
|
|
||||||
if (rankedIds.isEmpty()) {
|
|
||||||
return new DocumentDensityResult(List.of(), null, null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
List<LocalDate> dates = loadFilteredDates(filters, ftsIds);
|
||||||
|
return aggregateByMonth(dates);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the FTS-ranked document IDs when {@code text} is non-blank, or {@code null}
|
||||||
|
* when no full-text query is active. An empty list means the FTS query ran but
|
||||||
|
* matched zero documents — the caller short-circuits on that signal.
|
||||||
|
*/
|
||||||
|
private List<UUID> resolveFtsIds(String text) {
|
||||||
|
if (!StringUtils.hasText(text)) return null;
|
||||||
|
return documentRepository.findRankedIdsByFts(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Loads matching documents and projects to non-null {@link LocalDate}s. */
|
||||||
|
private List<LocalDate> loadFilteredDates(DensityFilters filters, List<UUID> ftsIds) {
|
||||||
|
boolean hasFts = ftsIds != null;
|
||||||
Specification<Document> spec = buildSearchSpec(
|
Specification<Document> spec = buildSearchSpec(
|
||||||
hasText, rankedIds, null, null,
|
hasFts, ftsIds, null, null,
|
||||||
filters.sender(), filters.receiver(),
|
filters.sender(), filters.receiver(),
|
||||||
filters.tags(), filters.tagQ(),
|
filters.tags(), filters.tagQ(),
|
||||||
filters.status(), filters.tagOperator());
|
filters.status(), filters.tagOperator());
|
||||||
|
return documentRepository.findAll(spec).stream()
|
||||||
List<LocalDate> dates = documentRepository.findAll(spec).stream()
|
|
||||||
.map(Document::getDocumentDate)
|
.map(Document::getDocumentDate)
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.toList();
|
.toList();
|
||||||
if (dates.isEmpty()) {
|
}
|
||||||
return new DocumentDensityResult(List.of(), null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/** Buckets {@code dates} into one {@link MonthBucket} per YYYY-MM and computes min/max. */
|
||||||
|
private DocumentDensityResult aggregateByMonth(List<LocalDate> dates) {
|
||||||
|
if (dates.isEmpty()) return DocumentDensityResult.empty();
|
||||||
Map<String, Integer> counts = new java.util.TreeMap<>();
|
Map<String, Integer> counts = new java.util.TreeMap<>();
|
||||||
for (LocalDate d : dates) {
|
for (LocalDate d : dates) {
|
||||||
counts.merge(YearMonth.from(d).toString(), 1, Integer::sum);
|
counts.merge(YearMonth.from(d).toString(), 1, Integer::sum);
|
||||||
|
|||||||
Reference in New Issue
Block a user