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 }