From 5a09cd4cb4ef7b0dfe935d50f9f347e68c760f02 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 6 Jun 2026 22:37:45 +0200 Subject: [PATCH] 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 --- .../familienarchiv/search/NlQueryInterpretation.java | 6 +++++- .../familienarchiv/search/NlQueryParserService.java | 12 +++++++++--- .../search/NlQueryParserServiceTest.java | 6 +++++- .../search/NlSearchControllerTest.java | 4 ++-- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/backend/src/main/java/org/raddatz/familienarchiv/search/NlQueryInterpretation.java b/backend/src/main/java/org/raddatz/familienarchiv/search/NlQueryInterpretation.java index 5313f093..37611488 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/search/NlQueryInterpretation.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/search/NlQueryInterpretation.java @@ -15,8 +15,12 @@ public record NlQueryInterpretation( @Schema(requiredMode = Schema.RequiredMode.REQUIRED) List keywords, @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + List resolvedTags, + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) String rawQuery, @Schema(requiredMode = Schema.RequiredMode.REQUIRED) - boolean keywordsApplied + boolean keywordsApplied, + @Schema(requiredMode = Schema.RequiredMode.REQUIRED) + boolean tagsApplied ) { } diff --git a/backend/src/main/java/org/raddatz/familienarchiv/search/NlQueryParserService.java b/backend/src/main/java/org/raddatz/familienarchiv/search/NlQueryParserService.java index 5938fb5e..4bf043e8 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/search/NlQueryParserService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/search/NlQueryParserService.java @@ -10,12 +10,15 @@ import org.raddatz.familienarchiv.exception.DomainException; import org.raddatz.familienarchiv.exception.ErrorCode; import org.raddatz.familienarchiv.person.Person; import org.raddatz.familienarchiv.person.PersonService; +import org.raddatz.familienarchiv.tag.Tag; import org.raddatz.familienarchiv.tag.TagOperator; +import org.raddatz.familienarchiv.tag.TagService; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import java.time.LocalDate; import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; import java.util.UUID; @@ -28,10 +31,13 @@ public class NlQueryParserService { private static final int MAX_QUERY = 500; private static final int MAX_NAME_LENGTH = 200; 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 PersonService personService; private final DocumentService documentService; + private final TagService tagService; public NlSearchResponse search(String query, Pageable pageable) { if (query == null || query.length() < MIN_QUERY || query.length() > MAX_QUERY) { @@ -50,7 +56,7 @@ public class NlQueryParserService { NlQueryInterpretation interpretation = new NlQueryInterpretation( List.of(), resolution.ambiguous(), ext.dateFrom(), ext.dateTo(), - keywords, ext.rawQuery(), false); + keywords, List.of(), ext.rawQuery(), false, false); return new NlSearchResponse(DocumentSearchResult.of(List.of()), interpretation); } @@ -65,7 +71,7 @@ public class NlQueryParserService { DocumentSearchResult docs = documentService.searchDocumentsByPersonId( personId, ext.dateFrom(), ext.dateTo(), pageable); 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); } @@ -82,7 +88,7 @@ public class NlQueryParserService { DocumentSearchResult docs = documentService.searchDocuments(filters, DocumentSort.DATE, "desc", pageable); boolean keywordsApplied = !text.isBlank(); 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); } diff --git a/backend/src/test/java/org/raddatz/familienarchiv/search/NlQueryParserServiceTest.java b/backend/src/test/java/org/raddatz/familienarchiv/search/NlQueryParserServiceTest.java index 65d73685..aa79b94a 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/search/NlQueryParserServiceTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/search/NlQueryParserServiceTest.java @@ -13,7 +13,9 @@ import org.raddatz.familienarchiv.exception.DomainException; import org.raddatz.familienarchiv.exception.ErrorCode; import org.raddatz.familienarchiv.person.Person; import org.raddatz.familienarchiv.person.PersonService; +import org.raddatz.familienarchiv.tag.Tag; import org.raddatz.familienarchiv.tag.TagOperator; +import org.raddatz.familienarchiv.tag.TagService; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -32,6 +34,7 @@ class NlQueryParserServiceTest { @Mock OllamaClient ollamaClient; @Mock PersonService personService; @Mock DocumentService documentService; + @Mock TagService tagService; NlQueryParserService service; @@ -40,11 +43,12 @@ class NlQueryParserServiceTest { @BeforeEach void setUp() { MockitoAnnotations.openMocks(this); - service = new NlQueryParserService(ollamaClient, personService, documentService); + service = new NlQueryParserService(ollamaClient, personService, documentService, tagService); when(documentService.searchDocuments(any(), any(), any(), any())) .thenReturn(DocumentSearchResult.of(List.of())); when(documentService.searchDocumentsByPersonId(any(), any(), any(), any())) .thenReturn(DocumentSearchResult.of(List.of())); + when(tagService.findByNameContaining(anyString())).thenReturn(List.of()); } // --- Factory helpers --- diff --git a/backend/src/test/java/org/raddatz/familienarchiv/search/NlSearchControllerTest.java b/backend/src/test/java/org/raddatz/familienarchiv/search/NlSearchControllerTest.java index b35b1c52..c0d30f40 100644 --- a/backend/src/test/java/org/raddatz/familienarchiv/search/NlSearchControllerTest.java +++ b/backend/src/test/java/org/raddatz/familienarchiv/search/NlSearchControllerTest.java @@ -48,7 +48,7 @@ class NlSearchControllerTest { PersonHint hint = new PersonHint(UUID.randomUUID(), "Walter Raddatz"); NlQueryInterpretation interp = new NlQueryInterpretation( 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); } @@ -77,7 +77,7 @@ class NlSearchControllerTest { PersonHint b = new PersonHint(UUID.randomUUID(), "Walter Schmidt"); NlQueryInterpretation interp = new NlQueryInterpretation( 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); when(nlQueryParserService.search(anyString(), any())).thenReturn(resp);