feat(search): extend NlQueryInterpretation with resolvedTags + tagsApplied

Positional record fields added; all 3 construction sites updated with neutral
defaults; NlQueryParserService wired for TagService (4th constructor arg);
NlQueryParserServiceTest and NlSearchControllerTest synced.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-06 22:37:45 +02:00
parent 8905135006
commit 7eee688ce9
4 changed files with 21 additions and 7 deletions

View File

@@ -15,8 +15,12 @@ public record NlQueryInterpretation(
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
List<String> keywords, List<String> keywords,
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
List<TagHint> resolvedTags,
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
String rawQuery, String rawQuery,
@Schema(requiredMode = Schema.RequiredMode.REQUIRED) @Schema(requiredMode = Schema.RequiredMode.REQUIRED)
boolean keywordsApplied boolean keywordsApplied,
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
boolean tagsApplied
) { ) {
} }

View File

@@ -10,12 +10,15 @@ import org.raddatz.familienarchiv.exception.DomainException;
import org.raddatz.familienarchiv.exception.ErrorCode; import org.raddatz.familienarchiv.exception.ErrorCode;
import org.raddatz.familienarchiv.person.Person; import org.raddatz.familienarchiv.person.Person;
import org.raddatz.familienarchiv.person.PersonService; import org.raddatz.familienarchiv.person.PersonService;
import org.raddatz.familienarchiv.tag.Tag;
import org.raddatz.familienarchiv.tag.TagOperator; import org.raddatz.familienarchiv.tag.TagOperator;
import org.raddatz.familienarchiv.tag.TagService;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@@ -28,10 +31,13 @@ public class NlQueryParserService {
private static final int MAX_QUERY = 500; private static final int MAX_QUERY = 500;
private static final int MAX_NAME_LENGTH = 200; private static final int MAX_NAME_LENGTH = 200;
private static final int MAX_CANDIDATES = 10; private static final int MAX_CANDIDATES = 10;
private static final int MIN_TAG_TERM = 3;
private static final int MAX_RESOLVED_TAGS = 10;
private final OllamaClient ollamaClient; private final OllamaClient ollamaClient;
private final PersonService personService; private final PersonService personService;
private final DocumentService documentService; private final DocumentService documentService;
private final TagService tagService;
public NlSearchResponse search(String query, Pageable pageable) { public NlSearchResponse search(String query, Pageable pageable) {
if (query == null || query.length() < MIN_QUERY || query.length() > MAX_QUERY) { if (query == null || query.length() < MIN_QUERY || query.length() > MAX_QUERY) {
@@ -50,7 +56,7 @@ public class NlQueryParserService {
NlQueryInterpretation interpretation = new NlQueryInterpretation( NlQueryInterpretation interpretation = new NlQueryInterpretation(
List.of(), resolution.ambiguous(), List.of(), resolution.ambiguous(),
ext.dateFrom(), ext.dateTo(), ext.dateFrom(), ext.dateTo(),
keywords, ext.rawQuery(), false); keywords, List.of(), ext.rawQuery(), false, false);
return new NlSearchResponse(DocumentSearchResult.of(List.of()), interpretation); return new NlSearchResponse(DocumentSearchResult.of(List.of()), interpretation);
} }
@@ -65,7 +71,7 @@ public class NlQueryParserService {
DocumentSearchResult docs = documentService.searchDocumentsByPersonId( DocumentSearchResult docs = documentService.searchDocumentsByPersonId(
personId, ext.dateFrom(), ext.dateTo(), pageable); personId, ext.dateFrom(), ext.dateTo(), pageable);
NlQueryInterpretation interpretation = new NlQueryInterpretation( NlQueryInterpretation interpretation = new NlQueryInterpretation(
resolved, List.of(), ext.dateFrom(), ext.dateTo(), keywords, ext.rawQuery(), false); resolved, List.of(), ext.dateFrom(), ext.dateTo(), keywords, List.of(), ext.rawQuery(), false, false);
return new NlSearchResponse(docs, interpretation); return new NlSearchResponse(docs, interpretation);
} }
@@ -82,7 +88,7 @@ public class NlQueryParserService {
DocumentSearchResult docs = documentService.searchDocuments(filters, DocumentSort.DATE, "desc", pageable); DocumentSearchResult docs = documentService.searchDocuments(filters, DocumentSort.DATE, "desc", pageable);
boolean keywordsApplied = !text.isBlank(); boolean keywordsApplied = !text.isBlank();
NlQueryInterpretation interpretation = new NlQueryInterpretation( NlQueryInterpretation interpretation = new NlQueryInterpretation(
resolved, List.of(), ext.dateFrom(), ext.dateTo(), keywords, ext.rawQuery(), keywordsApplied); resolved, List.of(), ext.dateFrom(), ext.dateTo(), keywords, List.of(), ext.rawQuery(), keywordsApplied, false);
return new NlSearchResponse(docs, interpretation); return new NlSearchResponse(docs, interpretation);
} }

View File

@@ -13,7 +13,9 @@ import org.raddatz.familienarchiv.exception.DomainException;
import org.raddatz.familienarchiv.exception.ErrorCode; import org.raddatz.familienarchiv.exception.ErrorCode;
import org.raddatz.familienarchiv.person.Person; import org.raddatz.familienarchiv.person.Person;
import org.raddatz.familienarchiv.person.PersonService; import org.raddatz.familienarchiv.person.PersonService;
import org.raddatz.familienarchiv.tag.Tag;
import org.raddatz.familienarchiv.tag.TagOperator; import org.raddatz.familienarchiv.tag.TagOperator;
import org.raddatz.familienarchiv.tag.TagService;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
@@ -32,6 +34,7 @@ class NlQueryParserServiceTest {
@Mock OllamaClient ollamaClient; @Mock OllamaClient ollamaClient;
@Mock PersonService personService; @Mock PersonService personService;
@Mock DocumentService documentService; @Mock DocumentService documentService;
@Mock TagService tagService;
NlQueryParserService service; NlQueryParserService service;
@@ -40,11 +43,12 @@ class NlQueryParserServiceTest {
@BeforeEach @BeforeEach
void setUp() { void setUp() {
MockitoAnnotations.openMocks(this); MockitoAnnotations.openMocks(this);
service = new NlQueryParserService(ollamaClient, personService, documentService); service = new NlQueryParserService(ollamaClient, personService, documentService, tagService);
when(documentService.searchDocuments(any(), any(), any(), any())) when(documentService.searchDocuments(any(), any(), any(), any()))
.thenReturn(DocumentSearchResult.of(List.of())); .thenReturn(DocumentSearchResult.of(List.of()));
when(documentService.searchDocumentsByPersonId(any(), any(), any(), any())) when(documentService.searchDocumentsByPersonId(any(), any(), any(), any()))
.thenReturn(DocumentSearchResult.of(List.of())); .thenReturn(DocumentSearchResult.of(List.of()));
when(tagService.findByNameContaining(anyString())).thenReturn(List.of());
} }
// --- Factory helpers --- // --- Factory helpers ---

View File

@@ -48,7 +48,7 @@ class NlSearchControllerTest {
PersonHint hint = new PersonHint(UUID.randomUUID(), "Walter Raddatz"); PersonHint hint = new PersonHint(UUID.randomUUID(), "Walter Raddatz");
NlQueryInterpretation interp = new NlQueryInterpretation( NlQueryInterpretation interp = new NlQueryInterpretation(
List.of(hint), List.of(), null, null, List.of(hint), List.of(), null, null,
List.of("Krieg"), "Briefe von Walter im Krieg", true); List.of("Krieg"), List.of(), "Briefe von Walter im Krieg", true, false);
return new NlSearchResponse(DocumentSearchResult.of(List.of()), interp); return new NlSearchResponse(DocumentSearchResult.of(List.of()), interp);
} }
@@ -77,7 +77,7 @@ class NlSearchControllerTest {
PersonHint b = new PersonHint(UUID.randomUUID(), "Walter Schmidt"); PersonHint b = new PersonHint(UUID.randomUUID(), "Walter Schmidt");
NlQueryInterpretation interp = new NlQueryInterpretation( NlQueryInterpretation interp = new NlQueryInterpretation(
List.of(), List.of(a, b), null, null, List.of(), List.of(a, b), null, null,
List.of(), "Briefe von Walter", false); List.of(), List.of(), "Briefe von Walter", false, false);
NlSearchResponse resp = new NlSearchResponse(DocumentSearchResult.of(List.of()), interp); NlSearchResponse resp = new NlSearchResponse(DocumentSearchResult.of(List.of()), interp);
when(nlQueryParserService.search(anyString(), any())).thenReturn(resp); when(nlQueryParserService.search(anyString(), any())).thenReturn(resp);