feat(ai-sdk): EvidenceType-Schicht — autoritative Fußnoten/Tabellen/Figuren surfacen

Router-Schicht Intent→KnowledgeSpace→EvidenceType→Collection→Merge→Authority (User-
Entscheidung A generalisiert). Neuer EvidenceType{TEXT,FOOTNOTE,TABLE,FIGURE} +
classifyEvidence (aus is_footnote/is_table/is_figure-Payload). RetrieveEvidence() zieht
die autoritative typisierte Evidence GEZIELT aus der KB-Slice (top-20, in-scope) statt
sie im Breit-Basis-Text-Merge zu verlieren; /retrieve liefert footnotes[]/tables[]/
figures[]. Kein perColl-Blindanstieg. Dieselbe Infra trägt C8 (FIGURE) ohne Router-Umbau.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-07-01 09:30:42 +02:00
parent 6f0c1cf30d
commit a606000a20
4 changed files with 132 additions and 20 deletions
@@ -111,15 +111,27 @@ func (h *RAGHandlers) Retrieve(c *gin.Context) {
return
}
// C-FN: Fußnoten-Hits als separates footnotes[] herausziehen (Frontend RawFootnote-Shape),
// damit der Advisor-Evidence-Workspace sie im dedizierten Fußnoten-Bereich rendert. Die Hits
// bleiben zusätzlich in results[] (LLM-Kontext). figures[] = C8-Platzhalter (leer bis C8).
footnotes := make([]gin.H, 0)
for _, r := range results {
if !r.IsFootnote {
continue
}
footnotes = append(footnotes, gin.H{
// Evidence-Type-Schicht: die autoritative typisierte Evidence (Fußnoten/Tabellen/Figuren) aus
// dem KB-Wissensraum SEPARAT surfacen, statt sie im Breit-Basis-Text-Merge zu verlieren.
// results[] bleibt der Text-Kontext fürs LLM + die Quellen-Liste.
ev := h.ragClient.RetrieveEvidence(c.Request.Context(), req.Query)
c.JSON(http.StatusOK, gin.H{
"query": req.Query,
"results": results,
"count": len(results),
"assessment": ucca.Assess(results),
"footnotes": footnotesFromEvidence(ev[ucca.EvidenceFootnote]),
"tables": tablesFromEvidence(ev[ucca.EvidenceTable]),
"figures": figuresFromEvidence(ev[ucca.EvidenceFigure]),
})
}
// footnotesFromEvidence maps FOOTNOTE evidence to the Evidence-Workspace RawFootnote shape.
func footnotesFromEvidence(rs []ucca.LegalSearchResult) []gin.H {
out := make([]gin.H, 0, len(rs))
for _, r := range rs {
out = append(out, gin.H{
"id": r.CitationUnit,
"ref": r.CitationUnit,
"number": r.FootnoteLabel,
@@ -130,15 +142,42 @@ func (h *RAGHandlers) Retrieve(c *gin.Context) {
"text": r.FootnoteVerbatim,
})
}
return out
}
c.JSON(http.StatusOK, gin.H{
"query": req.Query,
"results": results,
"count": len(results),
"assessment": ucca.Assess(results),
"footnotes": footnotes,
"figures": []gin.H{},
})
// tablesFromEvidence maps TABLE evidence (C6/C9). Key is present so the same Evidence-Type path
// carries tables the moment the UI adds a table section.
func tablesFromEvidence(rs []ucca.LegalSearchResult) []gin.H {
out := make([]gin.H, 0, len(rs))
for _, r := range rs {
out = append(out, gin.H{
"id": r.CitationUnit,
"caption": r.ArticleLabel,
"regulation_code": r.RegulationCode,
"regulation_short": r.RegulationShort,
"regulation_name": r.RegulationName,
"section": r.RefCitationUnit,
"text": r.Text,
})
}
return out
}
// figuresFromEvidence maps FIGURE evidence (C8). Empty until C8 populates figure units; image_url/
// caption/vision_summary get added here when C8 lands — same path, no router change.
func figuresFromEvidence(rs []ucca.LegalSearchResult) []gin.H {
out := make([]gin.H, 0, len(rs))
for _, r := range rs {
out = append(out, gin.H{
"figure_id": r.CitationUnit,
"caption": r.ArticleLabel,
"regulation_code": r.RegulationCode,
"regulation_short": r.RegulationShort,
"regulation_name": r.RegulationName,
"section": r.RefCitationUnit,
})
}
return out
}
// ListRegulations returns the list of available regulations in the corpus.