49147d9497
CI / detect-changes (pull_request) Successful in 16s
CI / branch-name (pull_request) Successful in 2s
CI / guardrail-integrity (pull_request) Successful in 5s
CI / secret-scan (pull_request) Successful in 6s
CI / dep-audit (pull_request) Failing after 1m1s
CI / sbom-scan (pull_request) Failing after 1m4s
CI / build-sha-integrity (pull_request) Successful in 14s
CI / validate-canonical-controls (pull_request) Successful in 13s
CI / test-go (pull_request) Successful in 1m2s
CI / loc-budget (pull_request) Successful in 24s
CI / go-lint (pull_request) Failing after 20s
CI / python-lint (pull_request) Failing after 23s
CI / nodejs-lint (pull_request) Failing after 1m10s
CI / nodejs-build (pull_request) Successful in 3m26s
CI / iace-gt-coverage (pull_request) Successful in 16s
CI / test-python-backend (pull_request) Successful in 27s
CI / test-python-document-crawler (pull_request) Successful in 13s
CI / test-python-dsms-gateway (pull_request) Successful in 9s
Re-orders /sdk/v1/rag/search results so binding law from the matching jurisdiction and domain ranks above guidance, foreign and off-domain law — without dropping anything (guidance stays as interpretation context). Internal-only: response schema is unchanged (json:"-" fields), so every consumer benefits without a contract change. - authority.go: classifyAuthority / queryDomain / chunkDomain / scopeClass / topic ontology. Tagged payload (authority_weight/source_class/jurisdiction) wins; deterministic fallback via category + name markers for the untagged corpus. - authority_rerank.go: rerankByAuthority. final = semantic + authority + jurisdiction + domain + scope + topic; the authority score is written back to Score so the multi-collection advisor merge preserves the order. - legal_rag_client: stratified retrieval — the binding-law pool AUGMENTS the semantic pool (mergeDedupHits), then re-rank. - legal_rag_http: searchBinding (source_class filter) + shared doPointsSearch. - table-driven tests for authority/domain/scope/topic + rerank acceptance + a stratified-binding integration test. go test -race green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
69 lines
2.4 KiB
Go
69 lines
2.4 KiB
Go
package ucca
|
|
|
|
import "sort"
|
|
|
|
// Re-ranking coefficients (validated in the offline golden harness; Phase A — conservative).
|
|
const (
|
|
authorityCoef = 0.40 // * weight/100
|
|
jurisdictionGain = 0.05 // binding/guidance from DE or EU
|
|
foreignPenalty = 0.60 // foreign law on a DE/EU question (demoted, not removed)
|
|
unknownPenalty = 0.08
|
|
domainMatchGain = 0.15
|
|
offDomainPenalty = 0.10 // off-domain binding (demoted, not removed)
|
|
scopePenalty = 0.25 // BDSG Teil 3 (law enforcement) on a general DP question
|
|
topicGain = 0.18 // amplifier only
|
|
)
|
|
|
|
// authorityScore computes the normative relevance of a result for a query. It augments the
|
|
// semantic score with authority/jurisdiction/domain/scope/topic signals. Exposed for tests.
|
|
func authorityScore(query string, r LegalSearchResult, qDomain string, qForeign bool) float64 {
|
|
info := classifyAuthority(r)
|
|
score := r.Score + authorityCoef*float64(info.weight)/100.0
|
|
|
|
if info.jurisdiction == "CH" && !qForeign {
|
|
score -= foreignPenalty // Fremdrecht bei DE/EU-Frage: demoted, nicht geloescht
|
|
} else {
|
|
score += jurisdictionGain
|
|
}
|
|
if info.sourceClass == "unknown" {
|
|
score -= unknownPenalty
|
|
}
|
|
if qDomain != "" {
|
|
switch cd := chunkDomain(r); {
|
|
case cd == qDomain:
|
|
score += domainMatchGain
|
|
case cd != "":
|
|
score -= offDomainPenalty // off-domain binding: demoted, nicht geloescht
|
|
}
|
|
}
|
|
if qDomain == "data_protection" && scopeClass(r) == "law_enforcement" {
|
|
score -= scopePenalty
|
|
}
|
|
if resultMatchesTopic(query, r) {
|
|
score += topicGain // Verstaerker, kein Override
|
|
}
|
|
return score
|
|
}
|
|
|
|
// rerankByAuthority re-orders results so binding law from the matching jurisdiction/domain
|
|
// ranks above guidance, foreign and off-domain law — WITHOUT dropping anything (guidance is
|
|
// kept as interpretation context). The computed score is written back to Score so downstream
|
|
// merges (e.g. the multi-collection advisor) preserve this order. Pure + deterministic.
|
|
func rerankByAuthority(query string, results []LegalSearchResult) []LegalSearchResult {
|
|
if len(results) < 2 {
|
|
return results
|
|
}
|
|
qDomain := queryDomain(query)
|
|
qForeign := queryIsForeign(query)
|
|
|
|
out := make([]LegalSearchResult, len(results))
|
|
copy(out, results)
|
|
for i := range out {
|
|
out[i].Score = authorityScore(query, out[i], qDomain, qForeign)
|
|
}
|
|
sort.SliceStable(out, func(a, b int) bool {
|
|
return out[a].Score > out[b].Score
|
|
})
|
|
return out
|
|
}
|