feat(search): add SearchMatchData record for per-document match signals

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-15 15:32:26 +02:00
parent 47da0fa216
commit 8cbecd452b
2 changed files with 92 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
package org.raddatz.familienarchiv.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;
import java.util.UUID;
/**
* Match signals for a single document in a full-text search result.
* All fields are non-null except {@code transcriptionSnippet}, which is null
* when no transcription block matched the query.
*/
public record SearchMatchData(
/**
* Best-ranked matching transcription line, or null if no block matched.
*/
String transcriptionSnippet,
/**
* Character offsets of highlighted terms within the document title.
* Empty when the title did not contribute to the match.
*/
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
List<MatchOffset> titleOffsets,
/**
* True when the sender's name matched the query.
*/
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
boolean senderMatched,
/**
* IDs of receiver persons whose names matched the query.
*/
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
List<UUID> matchedReceiverIds,
/**
* IDs of tags whose names matched the query.
*/
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
List<UUID> matchedTagIds
) {
/** Canonical "no match data" value for a single document. */
public static SearchMatchData empty() {
return new SearchMatchData(null, List.of(), false, List.of(), List.of());
}
}

View File

@@ -0,0 +1,44 @@
package org.raddatz.familienarchiv.dto;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class SearchMatchDataTest {
@Test
void transcription_snippet_is_nullable() {
SearchMatchData data = new SearchMatchData(null, List.of(), false, List.of(), List.of());
assertThat(data.transcriptionSnippet()).isNull();
}
@Test
void non_null_list_fields_are_empty_by_default_in_empty_factory() {
SearchMatchData data = SearchMatchData.empty();
assertThat(data.transcriptionSnippet()).isNull();
assertThat(data.titleOffsets()).isEmpty();
assertThat(data.matchedReceiverIds()).isEmpty();
assertThat(data.matchedTagIds()).isEmpty();
assertThat(data.senderMatched()).isFalse();
}
@Test
void holds_all_field_values() {
MatchOffset offset = new MatchOffset(0, 4);
SearchMatchData data = new SearchMatchData(
"schreibt dir aus dem Feld",
List.of(offset),
true,
List.of(),
List.of()
);
assertThat(data.transcriptionSnippet()).isEqualTo("schreibt dir aus dem Feld");
assertThat(data.titleOffsets()).containsExactly(offset);
assertThat(data.senderMatched()).isTrue();
}
}