Files
rag-local/common/kg_name_normalize.go
2026-08-11 11:52:08 +08:00

100 lines
2.9 KiB
Go
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package common
import (
"strings"
"unicode"
)
// NormalizeKgTerm 知识图谱术语归一化(实体名与关系谓词共用,别名表由调用方传入)。
// 幂等:NormalizeKgTerm(NormalizeKgTerm(x)) == NormalizeKgTerm(x)。
// 规则保守,只做高置信合并,不做模糊相似度合并,防误合并("人民法院"与"最高人民法院"绝不合并)。
// 返回空串表示归一化后无意义(调用方应丢弃);原本合法但归一化后为空时保留原名(如"中华人民共和国")。
func NormalizeKgTerm(name string, aliasMap map[string]string) string {
orig := name
s := strings.TrimSpace(name)
s = fullToHalf(s)
s = strings.Join(strings.Fields(s), " ")
s = strings.Trim(s, "《》")
// 去版本注记括号及内容:内容含 年/修正/修订/施行/数字 才剥(人名限定语"张三(北京分公司)"不剥)
s = stripVersionParens(s)
s = strings.TrimRight(s, "。,;、!?,;!?.·。、")
s = strings.TrimSpace(s)
// 去"中华人民共和国"前缀(仅前缀;去完为空则保留原名,防实体"中华人民共和国"被删)
if strings.HasPrefix(s, "中华人民共和国") {
if rest := strings.TrimSpace(strings.TrimPrefix(s, "中华人民共和国")); rest != "" {
s = rest
}
}
// 别名全串精确映射(非 contains 替换,防"民诉法"吃掉"民诉法解释"
if std, ok := aliasMap[s]; ok && std != "" {
s = std
}
if s == "" && orig != "" {
return orig
}
return s
}
// fullToHalf 全角转半角(ASCII 可见区 U+FF01~U+FF5E → U+21~U+7E,全角空格 → 半角空格)
func fullToHalf(s string) string {
return strings.Map(func(r rune) rune {
switch {
case r == ' ':
return ' '
case r >= '' && r <= '':
return r - '' + '\x21'
}
return r
}, s)
}
// stripVersionParens 剥去含版本注记的括号及内容。注记判定:内容含 年/修正/修订/施行 或任一数字。
func stripVersionParens(s string) string {
pairs := [][2]rune{{'(', ')'}, {'', ''}, {'【', '】'}, {'[', ']'}, {'', ''}}
var out strings.Builder
runes := []rune(s)
for i := 0; i < len(runes); {
stripped := false
for _, p := range pairs {
if runes[i] == p[0] {
end := findCloseParen(runes, i, p[1])
if end > i && isVersionNote(runes[i+1:end]) {
i = end + 1
stripped = true
break
}
}
}
if !stripped {
out.WriteRune(runes[i])
i++
}
}
return out.String()
}
func findCloseParen(runes []rune, start int, close rune) int {
for j := start + 1; j < len(runes); j++ {
if runes[j] == close {
return j
}
}
return -1
}
// isVersionNote 括号内容是否版本注记(年/修正/修订/施行 或数字)
func isVersionNote(content []rune) bool {
for _, r := range content {
if unicode.IsDigit(r) {
return true
}
}
text := string(content)
for _, kw := range []string{"年", "修正", "修订", "施行"} {
if strings.Contains(text, kw) {
return true
}
}
return false
}