feat: 正文简述分析重构
This commit is contained in:
parent
7a37b7604b
commit
dabda879d9
@ -176,7 +176,7 @@ function normalizeAiResult(result, payload) {
|
||||
category,
|
||||
institutionType,
|
||||
confidence: normalizeConfidence(result?.confidence),
|
||||
titleSummary: limitLength(result?.titleSummary || payload?.title || "", 60),
|
||||
titleSummary: limitLength(result?.titleSummary || "", 60),
|
||||
reason: limitLength(result?.reason || "", 120),
|
||||
matchedKeywords: dedupe(matchedKeywords)
|
||||
};
|
||||
@ -217,3 +217,54 @@ async function safeReadText(response) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function buildMessages(payload) {
|
||||
const compactInput = buildCompactInput(payload);
|
||||
|
||||
return [
|
||||
{
|
||||
role: "system",
|
||||
content: [
|
||||
"Analyze whether this procurement belongs to a financial-institution-related bidding case.",
|
||||
'Only hit when the service target is clearly a financial institution such as a bank, trust, insurance, consumer finance, financial leasing, or auto finance, and the business belongs to one of these categories: "法律服务", "催收/不良资产处置", "调解服务".',
|
||||
'Return JSON only with keys: {"category":"法律服务|催收/不良资产处置|调解服务|不命中","confidence":0-100,"summary":"...","reason":"..."}',
|
||||
'The "summary" must come from the announcement body, not the title.',
|
||||
'Do not repeat the title or start with the title.',
|
||||
'Do not include metadata such as "发布时间", "项目编号", "招标单位", "采购单位", "代理单位", "报名截止时间", or "投标截止时间".',
|
||||
'Keep "summary" around 50 Chinese characters, ideally within 30 to 50 Chinese characters.',
|
||||
'If the body does not provide a valid summary, return an empty string for "summary".',
|
||||
'Keep "reason" brief and concrete.'
|
||||
].join(" ")
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: JSON.stringify(compactInput)
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
function normalizeAiResult(result, payload) {
|
||||
const category = normalizeCategory(result?.category);
|
||||
const summary = limitLength(result?.summary || result?.titleSummary || "", 60);
|
||||
const institutionType = Array.isArray(result?.institutionType)
|
||||
? result.institutionType.filter(Boolean).map((item) => String(item).trim())
|
||||
: Array.isArray(payload?.keywordHints?.institutions)
|
||||
? payload.keywordHints.institutions.slice(0, 3)
|
||||
: [];
|
||||
const matchedKeywords = Array.isArray(result?.matchedKeywords)
|
||||
? result.matchedKeywords.filter(Boolean).map((item) => String(item).trim())
|
||||
: Array.isArray(payload?.keywordHints?.all)
|
||||
? payload.keywordHints.all
|
||||
: [];
|
||||
|
||||
return {
|
||||
isRelevant: (typeof result?.isRelevant === "boolean" ? result.isRelevant : category !== "不命中") && category !== "不命中",
|
||||
category,
|
||||
institutionType,
|
||||
confidence: normalizeConfidence(result?.confidence),
|
||||
summary,
|
||||
titleSummary: summary,
|
||||
reason: limitLength(result?.reason || "", 120),
|
||||
matchedKeywords: dedupe(matchedKeywords)
|
||||
};
|
||||
}
|
||||
|
||||
182
content.js
182
content.js
@ -11,6 +11,8 @@
|
||||
const BANNER_ID = "yfb-bid-assistant-banner";
|
||||
const KEYWORD_MARK_CLASS = "yfb-keyword-highlight";
|
||||
const MAX_LOG_ENTRIES = Number(CONFIG.maxLogEntries) || 80;
|
||||
const DEFAULT_MAX_PAGES = 10;
|
||||
const LEGACY_DEFAULT_MAX_PAGES = 3;
|
||||
const LIST_ROW_SELECTOR = "tr.el-table__row";
|
||||
const LIST_CARD_ROW_SELECTOR = ".list > div";
|
||||
const LIST_TITLE_SELECTOR = ".color1879F7.pointer, .color1879F7.textEll.pointer";
|
||||
@ -144,9 +146,10 @@
|
||||
stopRequested: false,
|
||||
panelCollapsed: false,
|
||||
panelHidden: false,
|
||||
hasCustomMaxPages: false,
|
||||
statusText: "等待开始",
|
||||
settings: {
|
||||
maxPages: 3,
|
||||
maxPages: DEFAULT_MAX_PAGES,
|
||||
delayMs: 300
|
||||
},
|
||||
stats: {
|
||||
@ -435,8 +438,15 @@
|
||||
|
||||
state.panelCollapsed = Boolean(saved.panelCollapsed);
|
||||
state.panelHidden = Boolean(saved.panelHidden);
|
||||
state.hasCustomMaxPages = Boolean(saved.hasCustomMaxPages);
|
||||
state.statusText = saved.statusText || state.statusText;
|
||||
state.settings = { ...state.settings, ...(saved.settings || {}) };
|
||||
if (
|
||||
!state.hasCustomMaxPages &&
|
||||
(!Number.isFinite(Number(state.settings.maxPages)) || Number(state.settings.maxPages) === LEGACY_DEFAULT_MAX_PAGES)
|
||||
) {
|
||||
state.settings.maxPages = DEFAULT_MAX_PAGES;
|
||||
}
|
||||
state.stats = { ...state.stats, ...(saved.stats || {}) };
|
||||
state.results = Array.isArray(saved.results) ? saved.results : [];
|
||||
state.rowStatusById = saved.rowStatusById || {};
|
||||
@ -713,11 +723,13 @@
|
||||
}
|
||||
|
||||
function handleSettingsChange() {
|
||||
const previousMaxPages = state.settings.maxPages;
|
||||
const maxPages = clampNumber(ui.maxPagesInput.value, 1, 200, state.settings.maxPages);
|
||||
const delayMs = clampNumber(ui.delayInput.value, 200, 10000, state.settings.delayMs);
|
||||
|
||||
state.settings.maxPages = maxPages;
|
||||
state.settings.delayMs = delayMs;
|
||||
state.hasCustomMaxPages = state.hasCustomMaxPages || previousMaxPages !== maxPages;
|
||||
ui.maxPagesInput.value = String(maxPages);
|
||||
ui.delayInput.value = String(delayMs);
|
||||
void persistState();
|
||||
@ -1180,6 +1192,68 @@
|
||||
return normalizeText(result);
|
||||
}
|
||||
|
||||
function normalizeSummaryCompareText(text) {
|
||||
return normalizeText(text).replace(/[\s,,。;、::\-()()[\]【】"'“”‘’《》]/g, "");
|
||||
}
|
||||
|
||||
function sanitizeSummaryCandidate(text, detailRecord) {
|
||||
let result = normalizeText(removeBoilerplateText(String(text || ""), detailRecord?.title || ""));
|
||||
if (!result) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const title = normalizeText(detailRecord?.title || "");
|
||||
if (title && result.startsWith(title)) {
|
||||
result = normalizeText(result.slice(title.length));
|
||||
}
|
||||
|
||||
const prefixPatterns = [
|
||||
/^(发布时间|发布日期|公告时间|时间|地区|项目编号|项目名称|项目概况|项目简介|招标编号|采购编号|预算金额|预估金额|招标单位|招标人|采购单位|业主单位|代理单位|代理机构|报名截止时间|投标截止时间|开标时间|开标日期|公告类型)\s*[::]?\s*/i,
|
||||
/^(\d{4}[./-]\d{1,2}[./-]\d{1,2}|\d{1,2}[./-]\d{1,2})\s*/,
|
||||
/^([一二三四五六七八九十]+、|\(?[一二三四五六七八九十]+\)|[0-9]+[、.])\s*/
|
||||
];
|
||||
|
||||
let previous = "";
|
||||
while (result && result !== previous) {
|
||||
previous = result;
|
||||
prefixPatterns.forEach((pattern) => {
|
||||
result = normalizeText(result.replace(pattern, ""));
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function isValidSummaryCandidate(text, detailRecord) {
|
||||
const candidate = normalizeText(text);
|
||||
if (!candidate || candidate.length < 12) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const candidateComparable = normalizeSummaryCompareText(candidate);
|
||||
const titleComparable = normalizeSummaryCompareText(detailRecord?.title || "");
|
||||
if (!candidateComparable || candidateComparable === titleComparable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (/^[\d\s,,。;、::./\-]+$/.test(candidate)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !/^(发布时间|发布日期|公告时间|时间|地区|项目编号|项目名称|项目概况|项目简介|招标编号|采购编号|预算金额|预估金额|招标单位|招标人|采购单位|业主单位|代理单位|代理机构|报名截止时间|投标截止时间|开标时间|开标日期|公告类型)\b/i.test(candidate);
|
||||
}
|
||||
|
||||
function buildFieldFallbackSummary(detailRecord) {
|
||||
const parts = [
|
||||
detailRecord?.bidder ? `招标单位:${detailRecord.bidder}` : "",
|
||||
detailRecord?.agency ? `代理单位:${detailRecord.agency}` : "",
|
||||
detailRecord?.signupDeadline ? `报名截止:${detailRecord.signupDeadline}` : "",
|
||||
detailRecord?.bidDeadline ? `投标截止:${detailRecord.bidDeadline}` : ""
|
||||
].filter(Boolean);
|
||||
|
||||
return parts.length > 0 ? limitLength(parts.join(";"), 60) : "";
|
||||
}
|
||||
|
||||
function collectKeywordHints(text) {
|
||||
const normalized = normalizeText(text);
|
||||
const institutions = collectHits(normalized, KEYWORDS.institutions);
|
||||
@ -1481,8 +1555,15 @@
|
||||
置信度分数: item.confidence || 0
|
||||
}));
|
||||
|
||||
const exportRows = state.results.map((item) => ({
|
||||
["标题"]: item.title,
|
||||
["简述"]: item.summary || "",
|
||||
["AI分类"]: item.category || "",
|
||||
["置信度"]: item.confidence || 0
|
||||
}));
|
||||
|
||||
const workbook = window.XLSX.utils.book_new();
|
||||
const worksheet = window.XLSX.utils.json_to_sheet(rows);
|
||||
const worksheet = window.XLSX.utils.json_to_sheet(exportRows);
|
||||
window.XLSX.utils.book_append_sheet(workbook, worksheet, "命中结果");
|
||||
|
||||
const fileBuffer = window.XLSX.write(workbook, {
|
||||
@ -2510,9 +2591,17 @@
|
||||
置信度分数: item.confidence || 0
|
||||
}));
|
||||
|
||||
const worksheet = window.XLSX.utils.json_to_sheet(rows, { header: headers });
|
||||
const exportHeaders = ["标题", "简述", "AI分类", "置信度"];
|
||||
const exportRows = state.results.map((item) => ({
|
||||
["标题"]: item.title,
|
||||
["简述"]: item.summary || "",
|
||||
["AI分类"]: item.category || "",
|
||||
["置信度"]: item.confidence || 0
|
||||
}));
|
||||
const worksheet = window.XLSX.utils.json_to_sheet(exportRows, { header: exportHeaders });
|
||||
worksheet["!cols"] = [
|
||||
{ wch: 44 },
|
||||
{ wch: 60 },
|
||||
{ wch: 18 },
|
||||
{ wch: 12 }
|
||||
];
|
||||
@ -2580,6 +2669,7 @@
|
||||
[STORAGE_KEY]: {
|
||||
panelCollapsed: state.panelCollapsed,
|
||||
panelHidden: state.panelHidden,
|
||||
hasCustomMaxPages: state.hasCustomMaxPages,
|
||||
statusText: state.statusText,
|
||||
settings: state.settings,
|
||||
stats: state.stats,
|
||||
@ -2690,6 +2780,92 @@
|
||||
return parts.join("");
|
||||
}
|
||||
|
||||
function isValidSummaryCandidate(text, detailRecord) {
|
||||
const candidate = normalizeText(text);
|
||||
if (!candidate || candidate.length < 12) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const title = normalizeText(detailRecord?.title || "");
|
||||
if (title && candidate.startsWith(title)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const candidateComparable = normalizeSummaryCompareText(candidate);
|
||||
const titleComparable = normalizeSummaryCompareText(title);
|
||||
if (!candidateComparable || candidateComparable === titleComparable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (/^[\d\s,,。;、::./\-]+$/.test(candidate)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !/^(发布时间|发布日期|公告时间|时间|地区|项目编号|项目名称|项目概况|项目简介|招标编号|采购编号|预算金额|预估金额|招标单位|招标人|采购单位|业主单位|代理单位|代理机构|报名截止时间|投标截止时间|开标时间|开标日期|公告类型)\b/i.test(candidate);
|
||||
}
|
||||
|
||||
function buildLocalSummary(detailRecord) {
|
||||
const candidateText = [detailRecord.announcementContent, detailRecord.detailText].filter(Boolean).join("\n");
|
||||
const candidateLines = candidateText
|
||||
.split(/[\n。;]/)
|
||||
.map((line) => sanitizeSummaryCandidate(line, detailRecord))
|
||||
.filter((line) => isValidSummaryCandidate(line, detailRecord));
|
||||
|
||||
return candidateLines.length > 0 ? limitLength(candidateLines[0], 60) : "";
|
||||
}
|
||||
|
||||
function buildSummary(detailRecord, matchedKeywords = [], aiSummary = "") {
|
||||
const normalizedAiSummary = normalizeText(removeBoilerplateText(String(aiSummary || ""), detailRecord?.title || ""));
|
||||
if (isValidSummaryCandidate(normalizedAiSummary, detailRecord)) {
|
||||
return limitLength(normalizedAiSummary, 60);
|
||||
}
|
||||
|
||||
const localSummary = buildLocalSummary(detailRecord);
|
||||
if (localSummary) {
|
||||
return localSummary;
|
||||
}
|
||||
|
||||
const fieldSummary = buildFieldFallbackSummary(detailRecord);
|
||||
if (fieldSummary) {
|
||||
return fieldSummary;
|
||||
}
|
||||
|
||||
return limitLength(detailRecord.title, 60);
|
||||
}
|
||||
|
||||
function addResult(detailRecord, decision) {
|
||||
const result = {
|
||||
id: detailRecord.id,
|
||||
title: detailRecord.title,
|
||||
summary: buildSummary(detailRecord, decision.matchedKeywords || [], decision.summary || decision.titleSummary || ""),
|
||||
category: decision.category || "未命中",
|
||||
institutionType: uniqueText(decision.institutionType || []),
|
||||
matchedKeywords: uniqueText(decision.matchedKeywords || []),
|
||||
confidence: Number(decision.confidence) || 0,
|
||||
reason: decision.reason || "",
|
||||
type: detailRecord.type,
|
||||
region: detailRecord.region,
|
||||
publishTime: detailRecord.publishTime,
|
||||
detailUrl: detailRecord.detailUrl,
|
||||
sourceUrl: detailRecord.sourceUrl,
|
||||
attachmentNames: detailRecord.attachmentNames,
|
||||
announcementContent: detailRecord.announcementContent,
|
||||
projectNumber: detailRecord.projectNumber,
|
||||
estimatedAmount: detailRecord.estimatedAmount,
|
||||
bidder: detailRecord.bidder,
|
||||
agency: detailRecord.agency,
|
||||
signupDeadline: detailRecord.signupDeadline,
|
||||
bidDeadline: detailRecord.bidDeadline
|
||||
};
|
||||
|
||||
const existingIndex = state.results.findIndex((item) => item.id === result.id);
|
||||
if (existingIndex >= 0) {
|
||||
state.results.splice(existingIndex, 1, result);
|
||||
} else {
|
||||
state.results.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
function escapeRegExp(text) {
|
||||
return String(text).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user