From 3736cc73aeb6cbbf7e077a4755eef41a441c85be Mon Sep 17 00:00:00 2001 From: kasun Date: Tue, 21 Jul 2026 01:04:19 +0200 Subject: [PATCH] Nest extraData by section for clearer embedding content Homebox's location fields were flattened into single-purpose keys (locationName/locationDescription), which gave EmbeddingService no way to group related fields when rendering vector-store content. extraData is now Map>, letting HomeboxItemClient express "location" as its own section; buildExtraData renders each section as a header with indented fields instead of a flat "Extradata: key: value" line. --- .../vaessl/app/homebox/HomeboxItemClient.java | 14 ++++++--- .../com/vaessl/app/shared/ServiceItem.java | 2 +- .../vaessl/app/vector/EmbeddingService.java | 31 +++++++++++++------ .../app/search/SearchControllerTest.java | 2 +- .../vaessl/app/search/SearchResponseTest.java | 5 +-- 5 files changed, 36 insertions(+), 18 deletions(-) diff --git a/backend/src/main/java/com/vaessl/app/homebox/HomeboxItemClient.java b/backend/src/main/java/com/vaessl/app/homebox/HomeboxItemClient.java index 44a51cb..6a21a38 100644 --- a/backend/src/main/java/com/vaessl/app/homebox/HomeboxItemClient.java +++ b/backend/src/main/java/com/vaessl/app/homebox/HomeboxItemClient.java @@ -1,6 +1,6 @@ package com.vaessl.app.homebox; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.springframework.data.domain.Page; @@ -73,12 +73,18 @@ public class HomeboxItemClient { String description = i.description(); HomeboxParent parent = i.parent(); - Map extraData = new HashMap<>(); + Map> extraData = new LinkedHashMap<>(); + Map locationData = new LinkedHashMap<>(); + if (parent.name() != null && !parent.name().isBlank()) { - extraData.put("locationName", parent.name()); + locationData.put("name", parent.name()); } if (parent.description() != null && !parent.description().isBlank()) { - extraData.put("locationDescription", parent.description()); + locationData.put("description", parent.description()); + } + + if (locationData != null && !locationData.isEmpty()) { + extraData.put("location", locationData); } return new ServiceItem(id, title, description, extraData); diff --git a/backend/src/main/java/com/vaessl/app/shared/ServiceItem.java b/backend/src/main/java/com/vaessl/app/shared/ServiceItem.java index 951fb46..9603afd 100644 --- a/backend/src/main/java/com/vaessl/app/shared/ServiceItem.java +++ b/backend/src/main/java/com/vaessl/app/shared/ServiceItem.java @@ -4,7 +4,7 @@ import java.util.Map; import jakarta.validation.constraints.NotNull; public record ServiceItem(String id, @NotNull String title, String description, - Map extraData) { + Map> extraData) { public String getExtra(String key) { if (extraData == null) { diff --git a/backend/src/main/java/com/vaessl/app/vector/EmbeddingService.java b/backend/src/main/java/com/vaessl/app/vector/EmbeddingService.java index 0bf030d..006a930 100644 --- a/backend/src/main/java/com/vaessl/app/vector/EmbeddingService.java +++ b/backend/src/main/java/com/vaessl/app/vector/EmbeddingService.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import org.springframework.ai.document.Document; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder; @@ -54,21 +55,30 @@ public class EmbeddingService { return new Document(connectionId + ":" + item.id(), content, metadata); } - private String buildExtraData(Map extraData) { + private String buildExtraData(Map> extraData) { if (extraData == null) { return ""; } - StringBuilder data = new StringBuilder(); - for (Map.Entry entry : extraData.entrySet()) { + + StringBuilder formattedData = new StringBuilder(); + + for (Entry> entry : extraData.entrySet()) { + StringBuilder data = new StringBuilder(); if (entry.getValue() == null || entry.getValue().toString().isEmpty()) { continue; } - if (!data.isEmpty()) { - data.append(" \n"); + + if (entry.getValue().entrySet() != null + && !entry.getValue().entrySet().toString().isEmpty()) { + for (Entry nestedEntry : entry.getValue().entrySet()) { + data.append(" ").append(nestedEntry.getKey()).append(": ") + .append(nestedEntry.getValue()).append("\n"); + } } - data.append(entry.getKey()).append(": ").append(entry.getValue()); + + formattedData.append(entry.getKey()).append(": ").append("\n").append(data.toString()); } - return data.toString(); + return formattedData.toString(); } private Map buildMetadata(ServiceItem item, ServiceType serviceType, @@ -81,12 +91,13 @@ public class EmbeddingService { } private String buildContent(ServiceItem item, String extraData) { - StringBuilder content = new StringBuilder("Title: ").append(item.title()); + StringBuilder content = new StringBuilder("title: ").append(item.title()); if (item.description() != null && !item.description().isEmpty()) { - content.append("\nDescription: ").append(item.description()); + content.append("\ndescription: ").append(item.description()); } if (!extraData.isEmpty()) { - content.append("\nExtradata: ").append(extraData); + + content.append("\n").append(extraData); } return content.toString(); } diff --git a/backend/src/test/java/com/vaessl/app/search/SearchControllerTest.java b/backend/src/test/java/com/vaessl/app/search/SearchControllerTest.java index 680a54a..7700ff0 100644 --- a/backend/src/test/java/com/vaessl/app/search/SearchControllerTest.java +++ b/backend/src/test/java/com/vaessl/app/search/SearchControllerTest.java @@ -72,7 +72,7 @@ class SearchControllerTest { .andExpect(status().isOk()) .andExpect(jsonPath("$.content[0].title").value("MacBook Pro A1398")) .andExpect(jsonPath("$.totalElements").value(1)) - .andExpect(jsonPath("$.content[0].extraData.locationName") + .andExpect(jsonPath("$.content[0].extraData.location.name") .value("Server Schrank Ikea weiß")); } diff --git a/backend/src/test/java/com/vaessl/app/search/SearchResponseTest.java b/backend/src/test/java/com/vaessl/app/search/SearchResponseTest.java index 115bdef..ae0a6df 100644 --- a/backend/src/test/java/com/vaessl/app/search/SearchResponseTest.java +++ b/backend/src/test/java/com/vaessl/app/search/SearchResponseTest.java @@ -18,7 +18,7 @@ class SearchResponseTest { @Test void shouldReturnNullWhenExtraDataKeyIsMissing() { - ServiceItem response = new ServiceItem(MOCK_ID, MOCK_TITLE, MOCK_DESCRIPTION, Map.of("key", "value")); + ServiceItem response = new ServiceItem(MOCK_ID, MOCK_TITLE, MOCK_DESCRIPTION, Map.of("key", Map.of("key", "value"))); assertThat(response.getExtra("missing")).isNull(); } @@ -26,7 +26,8 @@ class SearchResponseTest { @Test void shouldReturnExtraDataValue() { - ServiceItem response = new ServiceItem(MOCK_ID, MOCK_TITLE, MOCK_DESCRIPTION, Map.of("key", "value")); + ServiceItem response = new ServiceItem(MOCK_ID, MOCK_TITLE, MOCK_DESCRIPTION, Map.of("key", + Map.of("key", "value"))); assertThat(response.id()).isEqualTo(MOCK_ID); assertThat(response.getExtra("key")).contains("value");