124 lines
3.8 KiB
Python
124 lines
3.8 KiB
Python
|
|
"""公司知识库模块 — 公司匹配和证据排序。"""
|
|||
|
|
import re
|
|||
|
|
from typing import Optional
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _normalize_loose(input_str: str) -> str:
|
|||
|
|
"""模糊标准化:小写 + 去除括号/分隔符。"""
|
|||
|
|
s = input_str.lower()
|
|||
|
|
s = re.sub(r"[\s\-_·.()()\[\]{}]", "", s)
|
|||
|
|
s = s.replace("x", "")
|
|||
|
|
return s
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _score_profile(input_str: str, aliases: list[str], tags: list[str]) -> int:
|
|||
|
|
"""评分公司匹配度。"""
|
|||
|
|
normalized = _normalize_loose(input_str)
|
|||
|
|
score = 0
|
|||
|
|
for alias in aliases:
|
|||
|
|
if _normalize_loose(alias) in normalized or normalized in _normalize_loose(alias):
|
|||
|
|
score += 4
|
|||
|
|
for tag in tags:
|
|||
|
|
if _normalize_loose(tag) in normalized:
|
|||
|
|
score += 2
|
|||
|
|
return score
|
|||
|
|
|
|||
|
|
|
|||
|
|
def match_company_knowledge(
|
|||
|
|
product_name: Optional[str] = None,
|
|||
|
|
company_hint: Optional[str] = None,
|
|||
|
|
forced_company_id: Optional[str] = None,
|
|||
|
|
companies: list[dict] = None,
|
|||
|
|
products: list[dict] = None,
|
|||
|
|
) -> dict:
|
|||
|
|
"""匹配公司知识库。
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
product_name: 产品名称
|
|||
|
|
company_hint: 公司名称提示
|
|||
|
|
forced_company_id: 强制指定公司 ID
|
|||
|
|
companies: 公司配置列表
|
|||
|
|
products: 产品配置列表
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
CompanyKnowledgeMatch dict
|
|||
|
|
"""
|
|||
|
|
companies = companies or []
|
|||
|
|
products = products or []
|
|||
|
|
|
|||
|
|
# 1. 强制指定
|
|||
|
|
if forced_company_id:
|
|||
|
|
company = next((c for c in companies if c.get("id") == forced_company_id), None)
|
|||
|
|
if company:
|
|||
|
|
return {
|
|||
|
|
"companyId": company["id"],
|
|||
|
|
"companyName": company.get("displayName", ""),
|
|||
|
|
"evidenceFiles": [],
|
|||
|
|
"confidence": 1.0,
|
|||
|
|
"matchedBy": "company_forced",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 2. 产品目录匹配
|
|||
|
|
if product_name:
|
|||
|
|
normalized = product_name.lower()
|
|||
|
|
for prod in products:
|
|||
|
|
aliases = prod.get("aliases", [])
|
|||
|
|
if any(_normalize_loose(a) in normalized for a in aliases):
|
|||
|
|
company_id = prod.get("companyId")
|
|||
|
|
company = next((c for c in companies if c.get("id") == company_id), None)
|
|||
|
|
if company:
|
|||
|
|
return {
|
|||
|
|
"companyId": company["id"],
|
|||
|
|
"companyName": company.get("displayName", ""),
|
|||
|
|
"evidenceFiles": [],
|
|||
|
|
"confidence": 1.0,
|
|||
|
|
"matchedBy": "product_catalog",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 3. 别名/标签模糊匹配
|
|||
|
|
best_match = None
|
|||
|
|
best_score = 0
|
|||
|
|
search_text = f"{product_name or ''} {company_hint or ''}"
|
|||
|
|
|
|||
|
|
for company in companies:
|
|||
|
|
aliases = company.get("aliases", [])
|
|||
|
|
tags = company.get("aliases", []) # 用别名作为标签
|
|||
|
|
score = _score_profile(search_text, aliases, tags)
|
|||
|
|
if score > best_score:
|
|||
|
|
best_score = score
|
|||
|
|
best_match = company
|
|||
|
|
|
|||
|
|
if best_match and best_score > 0:
|
|||
|
|
confidence = min(0.95, 0.45 + best_score / 20)
|
|||
|
|
return {
|
|||
|
|
"companyId": best_match["id"],
|
|||
|
|
"companyName": best_match.get("displayName", ""),
|
|||
|
|
"evidenceFiles": [],
|
|||
|
|
"confidence": confidence,
|
|||
|
|
"matchedBy": "company_alias",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
"companyId": "",
|
|||
|
|
"companyName": "",
|
|||
|
|
"evidenceFiles": [],
|
|||
|
|
"confidence": 0,
|
|||
|
|
"matchedBy": "unknown",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
def expected_company_id_for_product(
|
|||
|
|
product_name: Optional[str],
|
|||
|
|
products: list[dict] = None,
|
|||
|
|
) -> Optional[str]:
|
|||
|
|
"""通过产品名查找对应公司 ID。"""
|
|||
|
|
if not product_name:
|
|||
|
|
return None
|
|||
|
|
products = products or []
|
|||
|
|
normalized = product_name.lower()
|
|||
|
|
for prod in products:
|
|||
|
|
aliases = prod.get("aliases", [])
|
|||
|
|
if any(_normalize_loose(a) in normalized for a in aliases):
|
|||
|
|
return prod.get("companyId")
|
|||
|
|
return None
|