package com.superbiz.agent.service; import com.fasterxml.jackson.databind.ObjectMapper; import com.superbiz.agent.dto.ComparableRetrievalResult; import org.springframework.ai.document.Document; import org.springframework.stereotype.Component; import java.util.LinkedHashMap; import java.util.Map; @Component public class RetrievalResultNormalizer { private final ObjectMapper objectMapper; public RetrievalResultNormalizer(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } public ComparableRetrievalResult fromCurrent(VectorSearchService.SearchResult result, int rank, int previewLimit) { Map metadata = parseMetadata(result.getMetadata()); String source = firstNonBlank(metadata.get("_source"), metadata.get("source"), result.getMetadata(), result.getId()); return ComparableRetrievalResult.builder() .path("current") .rank(rank) .id(result.getId()) .source(source) .docId(metadata.get("docId")) .title(metadata.get("title")) .breadcrumb(metadata.get("breadcrumb")) .category(metadata.get("category")) .contentPreview(truncate(result.getContent(), previewLimit)) .scoreLabel("l2_distance") .scoreValue((double) result.getScore()) .build(); } public ComparableRetrievalResult fromSidecar(Document document, int rank, int previewLimit) { Map metadata = stringifyMetadata(document.getMetadata()); String source = firstNonBlank(metadata.get("_source"), metadata.get("source"), metadata.get("docId"), document.getId()); return ComparableRetrievalResult.builder() .path("sidecar") .rank(rank) .id(document.getId()) .source(source) .docId(metadata.get("docId")) .title(metadata.get("title")) .breadcrumb(metadata.get("breadcrumb")) .category(metadata.get("category")) .contentPreview(truncate(document.getText(), previewLimit)) .scoreLabel("similarity") .scoreValue(document.getScore()) .build(); } private Map parseMetadata(String metadata) { if (metadata == null || metadata.isBlank()) { return Map.of(); } try { Map raw = objectMapper.readValue(metadata, Map.class); return stringifyMetadata(raw); } catch (Exception e) { return Map.of(); } } private Map stringifyMetadata(Map raw) { if (raw == null || raw.isEmpty()) { return Map.of(); } Map result = new LinkedHashMap<>(); for (Map.Entry entry : raw.entrySet()) { if (entry.getKey() != null && entry.getValue() != null) { result.put(String.valueOf(entry.getKey()), String.valueOf(entry.getValue())); } } return result; } private String firstNonBlank(String... values) { for (String value : values) { if (value != null && !value.isBlank()) { return value; } } return null; } private String truncate(String text, int maxLength) { if (text == null || text.length() <= maxLength) { return text; } return text.substring(0, maxLength) + "..."; } }