feat(ai-sdk): legal-corpus coverage + Phase-2 citation-graph assessment #33

Merged
Benjamin_Boenisch merged 3 commits from feat/coverage-legal-corpus-structure into main 2026-06-24 06:37:23 +00:00
4 changed files with 50 additions and 8 deletions
Showing only changes of commit c28c532958 - Show all commits
@@ -12,6 +12,7 @@ const (
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
supersededPenalty = 0.50 // superseded Alt-Quelle (pre-eu-v1): demoted, nicht versteckt
)
// authorityScore computes the normative relevance of a result for a query. It augments the
@@ -20,6 +21,12 @@ func authorityScore(query string, r LegalSearchResult, qDomain string, qForeign
info := classifyAuthority(r)
score := r.Score + authorityCoef*float64(info.weight)/100.0
if r.Superseded {
// Alt-Quelle (pre-eu-v1): Default-Fragen sollen die eu-v1-Norm sehen. Demoted,
// nicht entfernt — fuer Historie/Uebergangsfragen bleibt sie auffindbar.
score -= supersededPenalty
}
if info.jurisdiction == "CH" && !qForeign {
score -= foreignPenalty // Fremdrecht bei DE/EU-Frage: demoted, nicht geloescht
} else {
@@ -148,6 +148,7 @@ func (c *LegalRAGClient) searchInternal(ctx context.Context, collection string,
CitationUnit: getString(hit.Payload, "citation_unit"),
ReferencesOut: getStringSlice(hit.Payload, "references_out"),
ReferencesIn: getStringSlice(hit.Payload, "references_in"),
Superseded: getString(hit.Payload, "status") == "superseded",
}
}
@@ -0,0 +1,30 @@
package ucca
import "testing"
// A superseded alt-source must rank below the same result when it is NOT
// superseded (the eu-v1 norm), but only demoted — the penalty is finite, so it
// stays in the pool and remains findable for history/transition questions.
func TestAuthorityScore_SupersededIsDemotedNotRemoved(t *testing.T) {
fresh := LegalSearchResult{
Score: 0.65, SourceClass: "binding_law", AuthorityWeight: 100,
Jurisdiction: "EU", RegulationShort: "CRA", Article: "13",
}
old := fresh
old.Superseded = true
sFresh := authorityScore("CRA Sicherheitsupdates Hersteller", fresh, "", false)
sOld := authorityScore("CRA Sicherheitsupdates Hersteller", old, "", false)
if sOld >= sFresh {
t.Errorf("superseded must score lower: fresh=%.3f superseded=%.3f", sFresh, sOld)
}
gap := sFresh - sOld
if gap < supersededPenalty-0.001 || gap > supersededPenalty+0.001 {
t.Errorf("demotion should equal supersededPenalty (%.2f), got %.3f", supersededPenalty, gap)
}
// Still a positive, finite score → present in the pool, not hidden.
if sOld <= -1 {
t.Errorf("superseded score collapsed (%.3f) — must remain findable", sOld)
}
}
@@ -33,6 +33,10 @@ type LegalSearchResult struct {
CitationUnit string `json:"-"`
ReferencesOut []string `json:"-"`
ReferencesIn []string `json:"-"`
// Supersede-Status (status="superseded", use_for_primary=false) — Alt-Quelle,
// die fuer Default-Fragen demoted wird (nicht versteckt; fuer Historie auffindbar).
Superseded bool `json:"-"`
}
// LegalAssessment is the auditable explanation layer over a ranked result set: