89 lines
2.9 KiB
Go
89 lines
2.9 KiB
Go
package ucca
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
)
|
|
|
|
// FetchByNormIDs loads one representative unit per norm_id from the KB slice
|
|
// collection — the fetch side of the Concept->Norm recall injector. Returns
|
|
// LegalSearchResult with the caller-provided concept-relevance score (there is no
|
|
// similarity query; the injector places them by that score). Returns nil on any
|
|
// error or when no KB slice is configured (graceful degradation).
|
|
func (c *LegalRAGClient) FetchByNormIDs(ctx context.Context, normIDs []string, score float64) []LegalSearchResult {
|
|
if c.kbSliceCollection == "" || len(normIDs) == 0 {
|
|
return nil
|
|
}
|
|
should := make([]map[string]interface{}, 0, len(normIDs))
|
|
for _, nid := range normIDs {
|
|
should = append(should, map[string]interface{}{"key": "norm_id", "match": map[string]interface{}{"value": nid}})
|
|
}
|
|
reqBody := map[string]interface{}{
|
|
"limit": len(normIDs) * 3,
|
|
"with_payload": true,
|
|
"with_vectors": false,
|
|
"filter": map[string]interface{}{"should": should},
|
|
}
|
|
jsonBody, err := json.Marshal(reqBody)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
url := fmt.Sprintf("%s/collections/%s/points/scroll", c.qdrantURL, c.kbSliceCollection)
|
|
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(jsonBody))
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
if c.qdrantAPIKey != "" {
|
|
req.Header.Set("api-key", c.qdrantAPIKey)
|
|
}
|
|
resp, err := c.httpClient.Do(req)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil
|
|
}
|
|
var scrollResp qdrantScrollResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&scrollResp); err != nil {
|
|
return nil
|
|
}
|
|
seen := map[string]bool{}
|
|
out := make([]LegalSearchResult, 0, len(normIDs))
|
|
for _, pt := range scrollResp.Result.Points {
|
|
nid := getString(pt.Payload, "norm_id")
|
|
if nid == "" || seen[nid] {
|
|
continue
|
|
}
|
|
seen[nid] = true
|
|
out = append(out, scrollPointToResult(pt.Payload, score))
|
|
}
|
|
return out
|
|
}
|
|
|
|
// scrollPointToResult maps a scroll-point payload to a LegalSearchResult. Mirrors
|
|
// hitsToResults' payload keys; the score is assigned by the caller (concept rank).
|
|
func scrollPointToResult(payload map[string]interface{}, score float64) LegalSearchResult {
|
|
regCode := getString(payload, "regulation_code")
|
|
if regCode == "" {
|
|
regCode = getString(payload, "regulation_id")
|
|
}
|
|
return LegalSearchResult{
|
|
Text: getString(payload, "chunk_text"),
|
|
RegulationCode: regCode,
|
|
RegulationName: getString(payload, "regulation_name_de"),
|
|
RegulationShort: getString(payload, "regulation_short"),
|
|
Category: getString(payload, "category"),
|
|
Article: getString(payload, "article"),
|
|
ArticleLabel: getString(payload, "article_label"),
|
|
Paragraph: getString(payload, "paragraph"),
|
|
SourceURL: getString(payload, "source_url"),
|
|
CitationUnit: getString(payload, "citation_unit"),
|
|
Score: score,
|
|
}
|
|
}
|