feat(ai-sdk): Advisor Reasoning Stack — Clarity+G1+Concept-Injector+Context-Scope+Term-Resolution+E4-Curation+Intent-Signal

This commit is contained in:
Claude
2026-07-01 15:27:23 +02:00
parent a606000a20
commit e901447096
12 changed files with 902 additions and 10 deletions
@@ -0,0 +1,97 @@
package ucca
import (
"sort"
"strings"
)
// Legal Concept Ontology — the fachliche IP bridge for the Concept->Norm recall
// injector. The words users type ("Datenschutzerklärung", "Cookie Banner") are
// rarely identical to the article titles that actually govern them (Art. 12/13/14
// DSGVO, § 25 TDDDG). Embedding similarity misses this leap, so these bridges are
// curated: concept keyword -> load-bearing norm_ids. This is NOT a fallback to
// hardcoding — it is domain knowledge that surfaces the normatively load-bearing
// units within the (already correctly retrieved) documents.
type conceptNorm struct {
keywords []string
normIDs []string
}
var legalConceptOntology = []conceptNorm{
{[]string{"datenschutzerklärung", "datenschutzerklaerung", "privacy policy", "datenschutzhinweise", "datenschutzinformation"},
[]string{"EU-DSGVO-Art12", "EU-DSGVO-Art13", "EU-DSGVO-Art14"}},
{[]string{"cookie banner", "cookie-banner", "cookies", "cookie", "tracking"},
[]string{"DE-TDDDG-§25", "EU-DSGVO-Art6", "EU-DSGVO-Art7"}},
{[]string{"dsfa", "folgenabschätzung", "folgenabschaetzung", "datenschutz-folgenabschätzung"},
[]string{"EU-DSGVO-Art35", "EU-DSGVO-Art36"}},
{[]string{"auskunft", "auskunftsrecht", "auskunftsersuchen"},
[]string{"EU-DSGVO-Art15"}},
{[]string{"löschung", "loeschung", "vergessenwerden", "recht auf vergessen"},
[]string{"EU-DSGVO-Art17"}},
{[]string{"datenübertragbarkeit", "datenuebertragbarkeit", "portabilität", "portabilitaet"},
[]string{"EU-DSGVO-Art20"}},
{[]string{"widerspruch", "widerspruchsrecht"},
[]string{"EU-DSGVO-Art21"}},
{[]string{"datenpanne", "datenschutzverletzung", "data breach", "verletzung des schutzes"},
[]string{"EU-DSGVO-Art33", "EU-DSGVO-Art34"}},
// E4-Quick-Curation (2026-07-01): resolved abbreviations (E2) pull their core norms.
{[]string{"technische und organisatorische maßnahmen", "technische und organisatorische massnahmen"},
[]string{"EU-DSGVO-Art32", "EU-DSGVO-Art25", "EU-DSGVO-Art5"}},
{[]string{"verzeichnis von verarbeitungstätigkeiten", "verzeichnis von verarbeitungstaetigkeiten", "verarbeitungsverzeichnis"},
[]string{"EU-DSGVO-Art30"}},
{[]string{"auftragsverarbeitungsvertrag", "auftragsverarbeitung", "auftragsverarbeiter"},
[]string{"EU-DSGVO-Art28"}},
{[]string{"datenschutzbeauftragt"},
[]string{"EU-DSGVO-Art37", "EU-DSGVO-Art38", "EU-DSGVO-Art39"}},
}
// ConceptNorms returns the load-bearing norm_ids for the concepts named in the
// query (dedup, order-preserving). Empty if no concept is named.
func ConceptNorms(query string) []string {
q := strings.ToLower(query)
seen := map[string]bool{}
out := []string{}
for _, cn := range legalConceptOntology {
for _, kw := range cn.keywords {
if strings.Contains(q, kw) {
for _, nid := range cn.normIDs {
if !seen[nid] {
seen[nid] = true
out = append(out, nid)
}
}
break
}
}
}
return out
}
// InjectConceptNorms merges concept-injected norm units into the results so the
// load-bearing norms are VISIBLE in the evidence set. Dedups by citation_unit
// (skips norms already retrieved), then re-sorts by score — the injected units
// carry a just-below-top score so they surface high WITHOUT displacing the top
// document hit (inject, don't blindly dominate). Caps at topK.
func InjectConceptNorms(results, injected []LegalSearchResult, topK int) []LegalSearchResult {
if len(injected) == 0 {
return results
}
present := map[string]bool{}
for _, r := range results {
if r.CitationUnit != "" {
present[r.CitationUnit] = true
}
}
merged := append([]LegalSearchResult{}, results...)
for _, in := range injected {
if in.CitationUnit != "" && !present[in.CitationUnit] {
merged = append(merged, in)
present[in.CitationUnit] = true
}
}
sort.SliceStable(merged, func(i, j int) bool { return merged[i].Score > merged[j].Score })
if topK > 0 && len(merged) > topK {
merged = merged[:topK]
}
return merged
}