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:
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user