From 5a2be87f658d01bc5b3b7ecdba92ac69bf09dcf7 Mon Sep 17 00:00:00 2001
From: ai_xiaopei <xiaopei@aisim.cn>
Date: Thu, 03 Sep 2026 15:15:37 +0800
Subject: [PATCH] fix(search): 搜索准确性四连修——FTS5 MATCH 引号包裹(连字符不再被解析成 MINUS);KeywordSearch 截断按命中关键词数排序(高频 bigram 不再挤出强相关文档);原词奖励档(完整短语压过泛化 bigram);tags 字段真实参与评分(此前误用 Section)

---
 internal/graph/rwr.go |  106 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 106 insertions(+), 0 deletions(-)

diff --git a/internal/graph/rwr.go b/internal/graph/rwr.go
new file mode 100644
index 0000000..d1d2252
--- /dev/null
+++ b/internal/graph/rwr.go
@@ -0,0 +1,106 @@
+package graph
+
+// AdjEdge 带权邻接边(导出:index 包 LoadRWRGraph 需引用该类型)
+type AdjEdge struct {
+	To     int64
+	Weight float64
+}
+
+// RWR Random-Walk-with-Restart(个性化 PageRank):
+// 从种子集合出发在无向带权图上做 power iteration,restart 概率 alpha 回到种子。
+// 返回归一化到 [0,1] 的游走质量。确定性算法,无随机数。
+func RWR(seedIDs []int64, adj map[int64][]AdjEdge, alpha float64) map[int64]float64 {
+	if len(seedIDs) == 0 {
+		return map[int64]float64{}
+	}
+	// 收集参与节点:种子 + 邻接可达
+	nodes := make(map[int64]bool)
+	for _, s := range seedIDs {
+		nodes[s] = true
+	}
+	for from := range adj {
+		nodes[from] = true
+		for _, e := range adj[from] {
+			nodes[e.To] = true
+		}
+	}
+	n := len(nodes)
+	if n == 0 {
+		return map[int64]float64{}
+	}
+	idx := make(map[int64]int, n)
+	ids := make([]int64, 0, n)
+	for id := range nodes {
+		idx[id] = len(ids)
+		ids = append(ids, id)
+	}
+
+	// 重启向量:种子均匀
+	r := make([]float64, n)
+	for _, s := range seedIDs {
+		if i, ok := idx[s]; ok {
+			r[i] = 1.0 / float64(len(seedIDs))
+		}
+	}
+
+	const maxIter = 50
+	const eps = 1e-6
+	newR := make([]float64, n)
+	for iter := 0; iter < maxIter; iter++ {
+		for i := range newR {
+			newR[i] = 0
+		}
+		// 游走传播:out[i] = sum(w_ij)
+		for i, id := range ids {
+			var total float64
+			for _, e := range adj[id] {
+				total += e.Weight
+			}
+			if total == 0 {
+				continue
+			}
+			for _, e := range adj[id] {
+				j, ok := idx[e.To]
+				if !ok {
+					continue
+				}
+				newR[j] += (1 - alpha) * r[i] * e.Weight / total
+			}
+		}
+		// restart
+		for i := range newR {
+			newR[i] += alpha * r[i]
+		}
+		// 收敛判断
+		diff := 0.0
+		for i := range newR {
+			d := newR[i] - r[i]
+			if d < 0 {
+				d = -d
+			}
+			diff += d
+		}
+		r, newR = newR, r
+		if diff < eps {
+			break
+		}
+	}
+	// 归一化 [0,1]
+	maxV := 0.0
+	for _, v := range r {
+		if v > maxV {
+			maxV = v
+		}
+	}
+	out := make(map[int64]float64, n)
+	if maxV == 0 {
+		for _, id := range ids {
+			out[id] = 0
+		}
+		return out
+	}
+	for i, id := range ids {
+		out[id] = r[i] / maxV
+	}
+	return out
+}

--
Gitblit v1.10.0