Files
breakpilot-compliance/ai-compliance-sdk/internal/ucca/concept_fetch.go
T

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,
}
}