@reconcrap/boss-recommend-mcp 2.1.23 → 2.1.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/bin/boss-recommend-mcp.js +4 -4
- package/package.json +6 -1
- package/scripts/install-macos.sh +280 -280
- package/scripts/postinstall.cjs +44 -44
- package/skills/boss-chat/README.md +43 -42
- package/skills/boss-chat/SKILL.md +107 -106
- package/skills/boss-recommend-pipeline/README.md +13 -13
- package/skills/boss-recommend-pipeline/SKILL.md +47 -47
- package/skills/boss-recruit-pipeline/README.md +19 -19
- package/skills/boss-recruit-pipeline/SKILL.md +89 -89
- package/src/chat-mcp.js +301 -127
- package/src/chat-runtime-config.js +7 -5
- package/src/core/boss-cards/index.js +199 -199
- package/src/core/browser/index.js +302 -145
- package/src/core/capture/index.js +2930 -1201
- package/src/core/cv-acquisition/index.js +512 -238
- package/src/core/cv-capture-target/index.js +513 -299
- package/src/core/greet-quota/index.js +71 -71
- package/src/core/infinite-list/index.js +31 -31
- package/src/core/reporting/legacy-csv.js +12 -12
- package/src/core/run/detached-launcher.js +305 -0
- package/src/core/run/index.js +109 -55
- package/src/core/run/timing.js +33 -33
- package/src/core/run/windows-detached-worker.ps1 +106 -0
- package/src/core/screening/index.js +2400 -2135
- package/src/core/self-heal/index.js +989 -973
- package/src/core/self-heal/viewport.js +1505 -564
- package/src/detached-worker.js +99 -99
- package/src/domains/chat/action-journal.js +536 -0
- package/src/domains/chat/cards.js +137 -137
- package/src/domains/chat/constants.js +9 -9
- package/src/domains/chat/detail.js +1684 -401
- package/src/domains/chat/index.js +8 -7
- package/src/domains/chat/jobs.js +620 -620
- package/src/domains/chat/page-guard.js +157 -122
- package/src/domains/chat/roots.js +56 -56
- package/src/domains/chat/run-service.js +1801 -760
- package/src/domains/common/account-rights-panel.js +314 -314
- package/src/domains/common/recovery-settle.js +159 -159
- package/src/domains/recommend/actions.js +1219 -472
- package/src/domains/recommend/cards.js +243 -243
- package/src/domains/recommend/colleague-contact.js +1079 -333
- package/src/domains/recommend/constants.js +92 -92
- package/src/domains/recommend/detail.js +4037 -136
- package/src/domains/recommend/filters.js +608 -590
- package/src/domains/recommend/index.js +9 -9
- package/src/domains/recommend/jobs.js +571 -542
- package/src/domains/recommend/location.js +754 -707
- package/src/domains/recommend/refresh.js +677 -392
- package/src/domains/recommend/roots.js +80 -80
- package/src/domains/recommend/run-service.js +4048 -1447
- package/src/domains/recommend/scopes.js +246 -246
- package/src/domains/recruit/actions.js +277 -277
- package/src/domains/recruit/cards.js +74 -74
- package/src/domains/recruit/constants.js +236 -236
- package/src/domains/recruit/detail.js +588 -588
- package/src/domains/recruit/index.js +9 -9
- package/src/domains/recruit/instruction-parser.js +866 -866
- package/src/domains/recruit/refresh.js +45 -45
- package/src/domains/recruit/roots.js +68 -68
- package/src/domains/recruit/run-service.js +1817 -1620
- package/src/domains/recruit/search.js +3229 -3229
- package/src/index.js +16 -1
- package/src/parser.js +1296 -1296
- package/src/recommend-mcp.js +1061 -450
- package/src/recommend-scheduler.js +75 -75
package/src/parser.js
CHANGED
|
@@ -1,1296 +1,1296 @@
|
|
|
1
|
-
const SCHOOL_TAG_OPTIONS = [
|
|
2
|
-
"不限",
|
|
3
|
-
"985",
|
|
4
|
-
"211",
|
|
5
|
-
"双一流院校",
|
|
6
|
-
"留学",
|
|
7
|
-
"国内外名校",
|
|
8
|
-
"公办本科"
|
|
9
|
-
];
|
|
10
|
-
const DEGREE_OPTIONS = [
|
|
11
|
-
"不限",
|
|
12
|
-
"初中及以下",
|
|
13
|
-
"中专/中技",
|
|
14
|
-
"高中",
|
|
15
|
-
"大专",
|
|
16
|
-
"本科",
|
|
17
|
-
"硕士",
|
|
18
|
-
"博士"
|
|
19
|
-
];
|
|
20
|
-
const DEGREE_ORDER = [
|
|
21
|
-
"初中及以下",
|
|
22
|
-
"中专/中技",
|
|
23
|
-
"高中",
|
|
24
|
-
"大专",
|
|
25
|
-
"本科",
|
|
26
|
-
"硕士",
|
|
27
|
-
"博士"
|
|
28
|
-
];
|
|
29
|
-
const GENDER_OPTIONS = ["不限", "男", "女"];
|
|
30
|
-
const RECENT_NOT_VIEW_OPTIONS = ["不限", "近14天没有"];
|
|
31
|
-
const ACTIVITY_LEVEL_OPTIONS = [
|
|
32
|
-
"不限",
|
|
33
|
-
"刚刚活跃",
|
|
34
|
-
"今日活跃",
|
|
35
|
-
"3日内活跃",
|
|
36
|
-
"本周活跃",
|
|
37
|
-
"本月活跃"
|
|
38
|
-
];
|
|
39
|
-
const ACTIVITY_LEVEL_ALIASES = Object.freeze({
|
|
40
|
-
"不限": [
|
|
41
|
-
"不限", "不限制", "无要求", "没有要求", "任意", "全部", "都可以", "均可", "随便", "无所谓",
|
|
42
|
-
"nolimit", "norestriction", "unrestricted", "any", "all", "anytime", "nopreference", "doesntmatter", "whatever"
|
|
43
|
-
],
|
|
44
|
-
"刚刚活跃": [
|
|
45
|
-
"刚刚活跃", "刚才活跃", "刚刚", "刚才", "刚上线", "刚刚上线", "当前在线", "现在在线", "正在在线", "在线", "最近活跃", "近期活跃",
|
|
46
|
-
"高活跃", "高活跃度", "活跃度高", "高度活跃", "非常活跃", "很活跃", "超级活跃", "极其活跃", "最活跃", "高频活跃",
|
|
47
|
-
"activenow", "online", "onlinenow", "justnow", "justactive", "justonline", "currentlyactive", "currentlyonline",
|
|
48
|
-
"recentlyactive", "activerecently", "highactivity", "highlyactive", "veryactive", "superactive", "extremelyactive", "mostactive"
|
|
49
|
-
],
|
|
50
|
-
"今日活跃": [
|
|
51
|
-
"今日活跃", "今天活跃", "今日", "今天", "当日", "当天", "当天活跃", "24小时内", "近24小时", "最近24小时", "过去24小时",
|
|
52
|
-
"today", "activetoday", "todayactive", "daily", "dailyactive", "每日活跃", "sameday", "withinthelastday", "inthelastday", "duringthecurrentday",
|
|
53
|
-
"within24hours", "last24hours", "past24hours"
|
|
54
|
-
],
|
|
55
|
-
"3日内活跃": [
|
|
56
|
-
"3日内活跃", "3天内活跃", "三日内活跃", "三天内活跃", "3日内", "3天内", "三日内", "三天内",
|
|
57
|
-
"近3日", "近3天", "最近3日", "最近3天", "过去3日", "过去3天", "昨日", "昨天", "前天", "最近几天",
|
|
58
|
-
"within3days", "last3days", "past3days", "recent3days", "yesterday", "daybeforeyesterday", "recentfewdays"
|
|
59
|
-
],
|
|
60
|
-
"本周活跃": [
|
|
61
|
-
"本周活跃", "本舟活跃", "这周活跃", "本周", "这周", "这个星期", "本星期", "本礼拜", "这礼拜", "一周内", "近一周", "最近一周", "近7天", "最近7天", "7日内", "7天内",
|
|
62
|
-
"中等活跃", "中度活跃", "一般活跃", "普通活跃", "适度活跃", "活跃度一般", "中等活跃度",
|
|
63
|
-
"thisweek", "currentweek", "thisweekend", "weekend", "withinweek", "withinaweek", "withinthelastweek", "inthelastweek", "inthepastweek",
|
|
64
|
-
"duringthecurrentweek", "overthelastweek", "overthepastweek", "lastweek", "pastweek", "weekly", "recent7days", "last7days", "past7days",
|
|
65
|
-
"mediumactivity", "moderatelyactive", "mediumactive", "fairlyactive", "averageactivity", "averagelyactive", "normallyactive"
|
|
66
|
-
],
|
|
67
|
-
"本月活跃": [
|
|
68
|
-
"本月活跃", "这个月活跃", "本月", "这个月", "当月", "一个月内", "近一个月", "最近一个月", "近30天", "最近30天", "30日内", "30天内",
|
|
69
|
-
"低活跃", "低活跃度", "活跃度低", "活跃度不高", "低度活跃", "不太活跃", "偶尔活跃", "较少活跃", "很少活跃", "低频活跃",
|
|
70
|
-
"thismonth", "currentmonth", "withinmonth", "withinamonth", "withinthelastmonth", "inthelastmonth", "inthepastmonth", "duringthecurrentmonth",
|
|
71
|
-
"overthelastmonth", "overthepastmonth", "lastmonth", "pastmonth", "monthly", "recent30days", "last30days", "past30days",
|
|
72
|
-
"lowactivity", "lowactive", "lessactive", "notveryactive", "occasionallyactive", "rarelyactive", "infrequentlyactive", "seldomactive"
|
|
73
|
-
]
|
|
74
|
-
});
|
|
75
|
-
const FILTER_CONFIRM_OPTIONS = [
|
|
76
|
-
{ label: "筛选项无误,继续", value: "confirm" },
|
|
77
|
-
{ label: "筛选项需要调整", value: "revise" }
|
|
78
|
-
];
|
|
79
|
-
const POST_ACTION_OPTIONS = ["greet", "none"];
|
|
80
|
-
const POST_ACTION_LABELS = {
|
|
81
|
-
greet: "直接沟通",
|
|
82
|
-
none: "什么也不做"
|
|
83
|
-
};
|
|
84
|
-
const PAGE_SCOPE_OPTIONS = ["recommend", "featured", "latest"];
|
|
85
|
-
const PAGE_SCOPE_LABELS = {
|
|
86
|
-
recommend: "推荐",
|
|
87
|
-
featured: "精选",
|
|
88
|
-
latest: "最新"
|
|
89
|
-
};
|
|
90
|
-
const LEADING_NOISE_PATTERNS = [
|
|
91
|
-
/^使用boss-recommend-pipeline skills/i,
|
|
92
|
-
/^使用boss recommend pipeline skills/i,
|
|
93
|
-
/^帮我(?:在boss上)?(?:筛选|处理|看一下|跑一下)/i,
|
|
94
|
-
/^请(?:帮我)?(?:在boss上)?(?:筛选|处理|跑一下)/i,
|
|
95
|
-
/^在推荐页(?:上)?/i,
|
|
96
|
-
/^在boss推荐页(?:上)?/i
|
|
97
|
-
];
|
|
98
|
-
const SCHOOL_TAG_PATTERNS = [
|
|
99
|
-
{ label: "985", pattern: /(?:学校|院校|学历|标签|筛选|要求)?[^。;;\n]{0,12}(?:985)(?!\d)/i },
|
|
100
|
-
{ label: "211", pattern: /(?:学校|院校|学历|标签|筛选|要求)?[^。;;\n]{0,12}(?:211)(?!\d)/i },
|
|
101
|
-
{ label: "双一流院校", pattern: /双一流(?:院校|学校)?/i },
|
|
102
|
-
{ label: "留学", pattern: /留学|留学生|海归/i },
|
|
103
|
-
{ label: "国内外名校", pattern: /国内外名校|海内外名校|海外名校|qs\s*(?:top|前)?\s*\d+|名校/i },
|
|
104
|
-
{ label: "公办本科", pattern: /公办本科/i }
|
|
105
|
-
];
|
|
106
|
-
const DEGREE_PATTERNS = [
|
|
107
|
-
{ label: "初中及以下", pattern: /初中及以下|初中以下/i },
|
|
108
|
-
{ label: "中专/中技", pattern: /中专\s*\/\s*中技|中专中技|中专|中技/i },
|
|
109
|
-
{ label: "高中", pattern: /(?:学历|教育|要求)?[^。;;\n]{0,8}高中/i },
|
|
110
|
-
{ label: "大专", pattern: /(?:学历|教育|要求)?[^。;;\n]{0,8}(?:大专|专科)/i },
|
|
111
|
-
{ label: "本科", pattern: /(?:学历|教育|要求)?[^。;;\n]{0,8}(?:本科|学士)/i },
|
|
112
|
-
{ label: "硕士", pattern: /(?:学历|教育|要求)?[^。;;\n]{0,8}(?:硕士|研究生)/i },
|
|
113
|
-
{ label: "博士", pattern: /(?:学历|教育|要求)?[^。;;\n]{0,8}博士/i }
|
|
114
|
-
];
|
|
115
|
-
const GENDER_PATTERNS = [
|
|
116
|
-
{ label: "男", pattern: /(?:性别|候选人|人选)?[^。;;\n]{0,8}(?:男生|男性|男)/i },
|
|
117
|
-
{ label: "女", pattern: /(?:性别|候选人|人选)?[^。;;\n]{0,8}(?:女生|女性|女)/i }
|
|
118
|
-
];
|
|
119
|
-
const RECENT_NOT_VIEW_POSITIVE_PATTERNS = [
|
|
120
|
-
/近?14天(?:内)?没有/i,
|
|
121
|
-
/近?14天(?:内)?没看过/i,
|
|
122
|
-
/近?14天(?:内)?未查看/i,
|
|
123
|
-
/过滤[^。;;\n]{0,12}14天/i,
|
|
124
|
-
/排除[^。;;\n]{0,12}14天/i
|
|
125
|
-
];
|
|
126
|
-
const RECENT_NOT_VIEW_NEGATIVE_PATTERNS = [
|
|
127
|
-
/不限[^。;;\n]{0,8}14天/i,
|
|
128
|
-
/不过滤[^。;;\n]{0,12}14天/i,
|
|
129
|
-
/保留[^。;;\n]{0,12}14天/i
|
|
130
|
-
];
|
|
131
|
-
const TARGET_COUNT_PATTERNS = [
|
|
132
|
-
/目标筛选数(?:量)?(?:为|是|:|:)?\s*(\d+)/i,
|
|
133
|
-
/目标通过数(?:量)?(?:为|是|:|:)?\s*(\d+)/i,
|
|
134
|
-
/目标(?:处理|筛选|通过)?(?:人数|数量)?(?:为|是|:|:)?\s*(\d+)/i,
|
|
135
|
-
/至少(?:处理|筛选|通过)\s*(\d+)\s*(?:位|人)/i,
|
|
136
|
-
/(?:处理|筛选|通过)\s*(\d+)\s*(?:位|人)/i
|
|
137
|
-
];
|
|
138
|
-
const MAX_GREET_COUNT_PATTERNS = [
|
|
139
|
-
/最大招呼数(?:量)?(?:为|是|:|:)?\s*(\d+)/i,
|
|
140
|
-
/最大(?:打招呼|招呼|沟通|联系)(?:人数|数量|数)?(?:为|是|:|:)?\s*(\d+)/i,
|
|
141
|
-
/最多(?:打招呼|沟通|联系)\s*(\d+)\s*(?:位|人|个)?/i,
|
|
142
|
-
/(?:打招呼|沟通|联系)(?:上限|最多|不超过|至多)(?:为|是|:|:)?\s*(\d+)/i
|
|
143
|
-
];
|
|
144
|
-
const RUN_META_FIELD_LABEL_PATTERN = "(?:页面选择|学校标签|院校标签|学历|学位|性别|是否过滤近14天看过|当前城市筛选|仅推荐本城市|仅推荐期望城市为本城市(?:的牛人)?|current[_\\s-]?city[_\\s-]?only|活跃度|活动度|activity[_\\s-]?level|筛选条件|目标筛选数|目标通过人数|通过筛选后动作|最大招呼数|最大打招呼数|岗位)";
|
|
145
|
-
const CRITERIA_EXPLICIT_MARKER_PATTERN = /筛选条件\s*[::]/i;
|
|
146
|
-
const CRITERIA_EXPLICIT_STOP_PATTERN = new RegExp(`(?:^|[\\s;;])\\s*${RUN_META_FIELD_LABEL_PATTERN}\\s*[::]`, "i");
|
|
147
|
-
const CRITERIA_META_FIELD_PREFIX_PATTERNS = [
|
|
148
|
-
new RegExp(`^${RUN_META_FIELD_LABEL_PATTERN}\\s*(?:[::]|$)`, "i"),
|
|
149
|
-
/^(?:近?14天(?:内)?(?:没有|没看过|未查看)|(?:不过滤|保留|过滤|排除)[^。;;\n]{0,12}14天)\s*(?:[::]|$)?/i,
|
|
150
|
-
/^(?:目标(?:处理|筛选|通过)?(?:人数|数量)?|至少(?:处理|筛选|通过)|(?:处理|筛选|通过)\s*\d+\s*(?:位|人))(?:[::\s]|$)/i,
|
|
151
|
-
/^(?:最多(?:打招呼|沟通|联系)|(?:打招呼|沟通|联系)(?:上限|最多|不超过|至多))(?:[::\s]|$)/i,
|
|
152
|
-
/^(?:(?:通过筛选后)?动作|post[_\s-]?action|max[_\s-]?greet[_\s-]?count|target[_\s-]?count)\s*(?:[::]|$)/i
|
|
153
|
-
];
|
|
154
|
-
const META_CLAUSE_PATTERNS = [
|
|
155
|
-
/^推荐页|^推荐页面|^boss推荐/i,
|
|
156
|
-
/^帮我|^请|^运行|^使用.*skill/i,
|
|
157
|
-
/^启动boss推荐任务/i,
|
|
158
|
-
/^条件如下(?:[::]|$)/i,
|
|
159
|
-
/^(?:符合标准(?:的人选)?(?:都)?(?:的)?(?:动作)?[::]?\s*)?(?:收藏|打招呼|直接沟通|什么也不做|不做任何操作|不操作|仅筛选|只筛选)(?:[::]|$)/i
|
|
160
|
-
];
|
|
161
|
-
const FEATURED_SCOPE_PATTERN = /(?:精选牛人|精选页|精选页面|精选tab|精选标签|tab[^。;;\n]{0,6}精选|精选)/i;
|
|
162
|
-
const LATEST_SCOPE_PATTERN = /(?:最新页|最新页面|最新tab|最新标签|tab[^。;;\n]{0,6}最新|最新)/i;
|
|
163
|
-
const RECOMMEND_SCOPE_PATTERN = /(?:推荐页|推荐页面|推荐tab|推荐标签|tab[^。;;\n]{0,6}推荐|推荐)/i;
|
|
164
|
-
|
|
165
|
-
function normalizeText(input) {
|
|
166
|
-
return String(input || "").replace(/\s+/g, " ").trim();
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function normalizeCompactText(input) {
|
|
170
|
-
return normalizeText(input).replace(/\s+/g, "").toLowerCase();
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function parsePositiveIntegerValue(value) {
|
|
174
|
-
const parsed = Number.parseInt(String(value ?? ""), 10);
|
|
175
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function uniqueList(items) {
|
|
179
|
-
return Array.from(new Set(items.filter(Boolean)));
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function buildTextOptions(values = []) {
|
|
183
|
-
return values.map((value) => ({
|
|
184
|
-
label: value,
|
|
185
|
-
value
|
|
186
|
-
}));
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function normalizeSchoolTag(value) {
|
|
190
|
-
const normalized = normalizeText(value);
|
|
191
|
-
if (!normalized) return null;
|
|
192
|
-
const compact = normalizeCompactText(normalized);
|
|
193
|
-
if (normalized === "双一流" || compact === "双一流") return "双一流院校";
|
|
194
|
-
if (["留学", "留学生", "海归", "海外留学"].includes(compact)) return "留学";
|
|
195
|
-
if (
|
|
196
|
-
["国内外名校", "海内外名校", "国内外高校", "海内外高校", "海外名校"].includes(compact)
|
|
197
|
-
|| /^qs(?:前|top)?\d+$/.test(compact)
|
|
198
|
-
) {
|
|
199
|
-
return "国内外名校";
|
|
200
|
-
}
|
|
201
|
-
if (SCHOOL_TAG_OPTIONS.includes(normalized)) return normalized;
|
|
202
|
-
return null;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function toSchoolTagInputList(input) {
|
|
206
|
-
if (Array.isArray(input)) {
|
|
207
|
-
return input.map((item) => normalizeText(item)).filter(Boolean);
|
|
208
|
-
}
|
|
209
|
-
const text = normalizeText(input);
|
|
210
|
-
if (!text) return [];
|
|
211
|
-
return text.split(/[,,、/|]/).map((item) => normalizeText(item)).filter(Boolean);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function auditSchoolTagSelections(input) {
|
|
215
|
-
const rawItems = toSchoolTagInputList(input);
|
|
216
|
-
const valid = [];
|
|
217
|
-
const invalid = [];
|
|
218
|
-
for (const item of rawItems) {
|
|
219
|
-
const normalized = normalizeSchoolTag(item);
|
|
220
|
-
if (normalized) {
|
|
221
|
-
valid.push(normalized);
|
|
222
|
-
} else {
|
|
223
|
-
invalid.push(item);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
return {
|
|
227
|
-
valid: sortSchoolTagSelections(valid),
|
|
228
|
-
invalid: uniqueList(invalid)
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function sortSchoolTagSelections(values) {
|
|
233
|
-
const order = new Map(SCHOOL_TAG_OPTIONS.map((item, index) => [item, index]));
|
|
234
|
-
const unique = Array.from(
|
|
235
|
-
new Set((values || []).map((item) => normalizeSchoolTag(item)).filter(Boolean))
|
|
236
|
-
);
|
|
237
|
-
if (!unique.length) return [];
|
|
238
|
-
if (unique.includes("不限")) {
|
|
239
|
-
return unique.length === 1
|
|
240
|
-
? ["不限"]
|
|
241
|
-
: unique.filter((item) => item !== "不限").sort((left, right) => order.get(left) - order.get(right));
|
|
242
|
-
}
|
|
243
|
-
return unique.sort((left, right) => order.get(left) - order.get(right));
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function normalizeSchoolTagSelections(input) {
|
|
247
|
-
const audited = auditSchoolTagSelections(input);
|
|
248
|
-
return audited.valid.length ? audited.valid : null;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function normalizeDegree(value) {
|
|
252
|
-
const normalized = normalizeText(value);
|
|
253
|
-
if (!normalized) return null;
|
|
254
|
-
if (normalized === "专科") return "大专";
|
|
255
|
-
if (normalized === "研究生") return "硕士";
|
|
256
|
-
if (normalized === "中专" || normalized === "中技" || normalized === "中专中技") return "中专/中技";
|
|
257
|
-
return DEGREE_OPTIONS.includes(normalized) ? normalized : null;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function sortDegreeSelections(values) {
|
|
261
|
-
return uniqueList(values).sort((left, right) => {
|
|
262
|
-
const leftIndex = DEGREE_ORDER.indexOf(left);
|
|
263
|
-
const rightIndex = DEGREE_ORDER.indexOf(right);
|
|
264
|
-
return leftIndex - rightIndex;
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function expandDegreeAtOrAbove(value) {
|
|
269
|
-
const normalized = normalizeDegree(value);
|
|
270
|
-
if (!normalized || normalized === "不限") return [];
|
|
271
|
-
const startIndex = DEGREE_ORDER.indexOf(normalized);
|
|
272
|
-
if (startIndex === -1) return [];
|
|
273
|
-
return DEGREE_ORDER.slice(startIndex);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function parseDegreeSelectionsFromText(text) {
|
|
277
|
-
const normalizedText = normalizeText(text);
|
|
278
|
-
if (!normalizedText) return [];
|
|
279
|
-
if (
|
|
280
|
-
/(?:学历|学位|教育)(?:要求)?\s*(?:[::]\s*)?(?:不限|不限制|无要求)|(?:不限|不限制)\s*(?:[::]\s*)?(?:学历|学位|教育)(?:要求)?/i
|
|
281
|
-
.test(normalizedText)
|
|
282
|
-
) {
|
|
283
|
-
return ["不限"];
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const selected = [];
|
|
287
|
-
const atOrAbovePattern = /(初中及以下|中专\/中技|中专中技|中专|中技|高中|大专|专科|本科|硕士|研究生|博士)\s*(?:及|或)?以上/g;
|
|
288
|
-
let match;
|
|
289
|
-
while ((match = atOrAbovePattern.exec(normalizedText)) !== null) {
|
|
290
|
-
selected.push(...expandDegreeAtOrAbove(match[1]));
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
for (const { label, pattern } of DEGREE_PATTERNS) {
|
|
294
|
-
if (pattern.test(normalizedText)) {
|
|
295
|
-
selected.push(label);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
return sortDegreeSelections(selected);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
function normalizeDegreeSelections(input) {
|
|
302
|
-
if (Array.isArray(input)) {
|
|
303
|
-
const normalized = sortDegreeSelections(input.map((item) => normalizeDegree(item)).filter(Boolean));
|
|
304
|
-
if (!normalized.length) return null;
|
|
305
|
-
return normalized.includes("不限") ? ["不限"] : normalized;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
const text = normalizeText(input);
|
|
309
|
-
if (!text) return null;
|
|
310
|
-
if (text.includes("以上")) {
|
|
311
|
-
const fromText = parseDegreeSelectionsFromText(text);
|
|
312
|
-
if (fromText.length) return fromText;
|
|
313
|
-
}
|
|
314
|
-
const parts = text.split(/[,,、/|]/).map((item) => normalizeDegree(item)).filter(Boolean);
|
|
315
|
-
if (parts.length) {
|
|
316
|
-
const normalized = sortDegreeSelections(parts);
|
|
317
|
-
return normalized.includes("不限") ? ["不限"] : normalized;
|
|
318
|
-
}
|
|
319
|
-
const single = normalizeDegree(text);
|
|
320
|
-
return single ? [single] : null;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
function normalizeGender(value) {
|
|
324
|
-
const normalized = normalizeText(value);
|
|
325
|
-
if (!normalized) return null;
|
|
326
|
-
if (normalized === "男性" || normalized === "男生") return "男";
|
|
327
|
-
if (normalized === "女性" || normalized === "女生") return "女";
|
|
328
|
-
return GENDER_OPTIONS.includes(normalized) ? normalized : null;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
function normalizeRecentNotView(value) {
|
|
332
|
-
const normalized = normalizeText(value);
|
|
333
|
-
if (!normalized) return null;
|
|
334
|
-
const compact = normalizeCompactText(normalized).replace(/天内/g, "天");
|
|
335
|
-
if (
|
|
336
|
-
/^(?:近)?14天(?:没有|未看|没看过|没看|未查看|未查看过)$/.test(compact)
|
|
337
|
-
|| /^(?:过滤|排除)(?:近)?14天(?:已看|看过)?$/.test(compact)
|
|
338
|
-
) {
|
|
339
|
-
return "近14天没有";
|
|
340
|
-
}
|
|
341
|
-
if (
|
|
342
|
-
/^(?:不限|不限制|无要求|无|全部|都可以)$/.test(compact)
|
|
343
|
-
|| /^(?:不过滤|保留)(?:近)?14天(?:已看|看过)?$/.test(compact)
|
|
344
|
-
) {
|
|
345
|
-
return "不限";
|
|
346
|
-
}
|
|
347
|
-
return RECENT_NOT_VIEW_OPTIONS.includes(normalized) ? normalized : null;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
function normalizeActivityIntentText(value) {
|
|
351
|
-
return normalizeText(value)
|
|
352
|
-
.normalize("NFKC")
|
|
353
|
-
.toLowerCase()
|
|
354
|
-
.replace(/[內内]/g, "内")
|
|
355
|
-
.replace(/[週周]/g, "周")
|
|
356
|
-
.replace(/[剛刚]/g, "刚")
|
|
357
|
-
.replace(/[當当]/g, "当")
|
|
358
|
-
.replace(/[無无]/g, "无")
|
|
359
|
-
.replace(/[躍跃]/g, "跃");
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
function compactActivityIntentText(value) {
|
|
363
|
-
return normalizeActivityIntentText(value).replace(/[\s_\-—–::,,。;;、/|()[\]{}'"“”‘’]+/g, "");
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
function stripActivityIntentWrappers(value) {
|
|
367
|
-
let current = compactActivityIntentText(value);
|
|
368
|
-
for (let index = 0; index < 4; index += 1) {
|
|
369
|
-
const previous = current;
|
|
370
|
-
current = current
|
|
371
|
-
.replace(/^(?:please|showme|iwant|want|setto|select|choose|filterby|activitylevel|activitystatus|activity|active|活跃度单选|活跃度|活动度|请选择|请选|选择|设置为|设为|设置|筛选为|筛选|仅推荐|只推荐|推荐|仅看|只看|只要|仅要|要求|希望|我要|想要|选项为|选项是|为|是|选)/, "")
|
|
372
|
-
.replace(/(?:candidates?|people|users?|talents?|option|active|的牛人|牛人|的候选人|候选人|的人选|人选|的人才|人才|的用户|用户|的人|选项|档位|程度|活跃度|活跃)$/u, "")
|
|
373
|
-
.replace(/^(?:为|是|to|is)/, "");
|
|
374
|
-
if (current === previous) break;
|
|
375
|
-
}
|
|
376
|
-
return current;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
function parseChineseActivityNumber(value) {
|
|
380
|
-
const normalized = String(value || "");
|
|
381
|
-
if (/^\d+(?:\.\d+)?$/.test(normalized)) return Number(normalized);
|
|
382
|
-
if (normalized === "半") return 0.5;
|
|
383
|
-
const englishNumbers = {
|
|
384
|
-
half: 0.5,
|
|
385
|
-
one: 1,
|
|
386
|
-
two: 2,
|
|
387
|
-
couple: 2,
|
|
388
|
-
three: 3,
|
|
389
|
-
few: 3,
|
|
390
|
-
four: 4,
|
|
391
|
-
five: 5,
|
|
392
|
-
six: 6,
|
|
393
|
-
seven: 7,
|
|
394
|
-
eight: 8,
|
|
395
|
-
nine: 9,
|
|
396
|
-
ten: 10,
|
|
397
|
-
twenty: 20,
|
|
398
|
-
thirty: 30
|
|
399
|
-
};
|
|
400
|
-
if (Object.prototype.hasOwnProperty.call(englishNumbers, normalized.toLowerCase())) {
|
|
401
|
-
return englishNumbers[normalized.toLowerCase()];
|
|
402
|
-
}
|
|
403
|
-
const digits = { 零: 0, 一: 1, 二: 2, 两: 2, 三: 3, 四: 4, 五: 5, 六: 6, 七: 7, 八: 8, 九: 9 };
|
|
404
|
-
if (normalized === "十") return 10;
|
|
405
|
-
if (normalized.includes("十")) {
|
|
406
|
-
const [tens, ones] = normalized.split("十");
|
|
407
|
-
return (tens ? digits[tens] || 0 : 1) * 10 + (ones ? digits[ones] || 0 : 0);
|
|
408
|
-
}
|
|
409
|
-
return digits[normalized] ?? null;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
function activityLevelForRelativeDuration(value) {
|
|
413
|
-
const normalized = normalizeActivityIntentText(value);
|
|
414
|
-
const match = normalized.match(
|
|
415
|
-
/(?:近|最近|过去|过去的|within\s*|last\s*|past\s*)?(\d+(?:\.\d+)?|半|[一二两三四五六七八九十]+|half|one|two|couple|three|few|four|five|six|seven|eight|nine|ten|twenty|thirty)\s*(?:个)?\s*(小时|hours?|hrs?|hr|h|天|日|days?|day|d|周|星期|weeks?|week|w|月|months?|month)(?:内|以内|之内|ago)?/i
|
|
416
|
-
);
|
|
417
|
-
if (!match) return null;
|
|
418
|
-
const amount = parseChineseActivityNumber(match[1]);
|
|
419
|
-
if (!Number.isFinite(amount) || amount < 0) return null;
|
|
420
|
-
const unit = match[2].toLowerCase();
|
|
421
|
-
if (/^(?:小时|hours?|hrs?|hr|h)$/.test(unit)) {
|
|
422
|
-
if (amount <= 2) return "刚刚活跃";
|
|
423
|
-
if (amount <= 24) return "今日活跃";
|
|
424
|
-
return activityLevelForRelativeDuration(`${Math.ceil(amount / 24)}天`);
|
|
425
|
-
}
|
|
426
|
-
if (/^(?:天|日|days?|day|d)$/.test(unit)) {
|
|
427
|
-
if (amount <= 1) return "今日活跃";
|
|
428
|
-
const nominalWindows = [
|
|
429
|
-
{ days: 3, activityLevel: "3日内活跃" },
|
|
430
|
-
{ days: 7, activityLevel: "本周活跃" },
|
|
431
|
-
{ days: 30, activityLevel: "本月活跃" }
|
|
432
|
-
];
|
|
433
|
-
return nominalWindows.reduce((closest, candidate) => {
|
|
434
|
-
const closestDistance = Math.abs(amount - closest.days);
|
|
435
|
-
const candidateDistance = Math.abs(amount - candidate.days);
|
|
436
|
-
if (candidateDistance < closestDistance) return candidate;
|
|
437
|
-
if (candidateDistance === closestDistance && candidate.days > closest.days) return candidate;
|
|
438
|
-
return closest;
|
|
439
|
-
}, nominalWindows[0]).activityLevel;
|
|
440
|
-
}
|
|
441
|
-
if (/^(?:周|星期|weeks?|week|w)$/.test(unit)) {
|
|
442
|
-
return activityLevelForRelativeDuration(`${amount * 7}天`);
|
|
443
|
-
}
|
|
444
|
-
return "本月活跃";
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
function activityLevelsForSemanticPhrase(value) {
|
|
448
|
-
const normalized = normalizeActivityIntentText(value);
|
|
449
|
-
const compact = compactActivityIntentText(normalized);
|
|
450
|
-
const englishQualitativeText = normalized
|
|
451
|
-
.replace(/\bnot\s+(?:very|highly|high|super|extremely)\b/g, " low ");
|
|
452
|
-
const highQualitativeText = compact
|
|
453
|
-
.replace(/not(?:very|highly|high|super|extremely)active|nothighactivity/g, "")
|
|
454
|
-
.replace(/不太活跃|很少活跃|较少活跃|偶尔活跃|低频活跃|低度活跃|低活跃|活跃度不高|活跃度低/g, "");
|
|
455
|
-
const hasEnglishActivityContext = /\b(?:activity|active)\b/i.test(englishQualitativeText);
|
|
456
|
-
const hasChineseActivityContext = /活跃/.test(normalized);
|
|
457
|
-
const hasHighQualitativeIntent = (
|
|
458
|
-
/highactivity|highlyactive|veryactive|superactive|extremelyactive|mostactive|高活跃|活跃度高|高度活跃|非常活跃|很活跃|超级活跃|极其活跃|最活跃|高频活跃/.test(highQualitativeText)
|
|
459
|
-
|| (hasEnglishActivityContext && /\b(?:high|highly|very|super|extremely|most)\b/i.test(englishQualitativeText))
|
|
460
|
-
|| (hasChineseActivityContext && /高|非常|超级|极其|最活跃/.test(highQualitativeText))
|
|
461
|
-
);
|
|
462
|
-
const hasMediumQualitativeIntent = (
|
|
463
|
-
/mediumactivity|moderatelyactive|mediumactive|fairlyactive|averageactivity|averagelyactive|normallyactive|中等活跃|中度活跃|一般活跃|普通活跃|适度活跃|活跃度一般/.test(compact)
|
|
464
|
-
|| (hasEnglishActivityContext && /\b(?:medium|moderate|moderately|fair|fairly|average|normal|normally)\b/i.test(englishQualitativeText))
|
|
465
|
-
|| (hasChineseActivityContext && /中等|中度|一般|普通|适度/.test(normalized))
|
|
466
|
-
);
|
|
467
|
-
const hasLowQualitativeIntent = (
|
|
468
|
-
/lowactivity|lowactive|lessactive|notveryactive|nothighactivity|occasionallyactive|rarelyactive|infrequentlyactive|seldomactive|低活跃|活跃度低|活跃度不高|低度活跃|不太活跃|偶尔活跃|较少活跃|很少活跃|低频活跃/.test(compact)
|
|
469
|
-
|| (hasEnglishActivityContext && /\b(?:low|less|rare|rarely|occasional|occasionally|infrequent|infrequently|seldom)\b/i.test(englishQualitativeText))
|
|
470
|
-
|| (hasChineseActivityContext && /低|不太|偶尔|较少|很少|低频/.test(normalized))
|
|
471
|
-
);
|
|
472
|
-
const matches = [];
|
|
473
|
-
if (
|
|
474
|
-
/(?:no|without)(?:activity)?(?:limit|restriction|preference)|notrestricted|notactive|notonline|anyactivity|allactivity|dontcare|doesntmatter|不限|不限制|无要求|没有要求|任意|都可以|均可|无所谓/.test(compact)
|
|
475
|
-
) {
|
|
476
|
-
matches.push("不限");
|
|
477
|
-
}
|
|
478
|
-
if (/justnow|rightnow|currentlyactive|currentlyonline|onlinenow|刚刚|刚才|刚上线|当前在线|现在在线/.test(compact)) {
|
|
479
|
-
matches.push("刚刚活跃");
|
|
480
|
-
}
|
|
481
|
-
if (hasHighQualitativeIntent) {
|
|
482
|
-
matches.push("刚刚活跃");
|
|
483
|
-
}
|
|
484
|
-
if (/activetoday|todayactive|today|sameday|(?:within|during|in|over)?(?:the)?(?:last|past|current)(?:one)?day|今日|今天|当日|当天/.test(compact)) {
|
|
485
|
-
matches.push("今日活跃");
|
|
486
|
-
}
|
|
487
|
-
if (/yesterday|daybeforeyesterday|recentfewdays|昨日|昨天|前天|最近几天/.test(compact)) {
|
|
488
|
-
matches.push("3日内活跃");
|
|
489
|
-
}
|
|
490
|
-
if (/(?:this|current)week|(?:within|during|in|over)?(?:the)?(?:last|past)(?:one)?week|thisweekend|weekend|本周|这周|这个星期|本星期/.test(compact)) {
|
|
491
|
-
matches.push("本周活跃");
|
|
492
|
-
}
|
|
493
|
-
if (hasMediumQualitativeIntent) {
|
|
494
|
-
matches.push("本周活跃");
|
|
495
|
-
}
|
|
496
|
-
if (/(?:this|current)month|(?:within|during|in|over)?(?:the)?(?:last|past)(?:one)?month|本月|这个月|当月/.test(compact)) {
|
|
497
|
-
matches.push("本月活跃");
|
|
498
|
-
}
|
|
499
|
-
if (hasLowQualitativeIntent) {
|
|
500
|
-
matches.push("本月活跃");
|
|
501
|
-
}
|
|
502
|
-
return uniqueList(matches);
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
function activityEditDistance(left, right) {
|
|
506
|
-
const a = String(left || "");
|
|
507
|
-
const b = String(right || "");
|
|
508
|
-
const previous = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
509
|
-
for (let leftIndex = 1; leftIndex <= a.length; leftIndex += 1) {
|
|
510
|
-
const current = [leftIndex];
|
|
511
|
-
for (let rightIndex = 1; rightIndex <= b.length; rightIndex += 1) {
|
|
512
|
-
current[rightIndex] = Math.min(
|
|
513
|
-
current[rightIndex - 1] + 1,
|
|
514
|
-
previous[rightIndex] + 1,
|
|
515
|
-
previous[rightIndex - 1] + (a[leftIndex - 1] === b[rightIndex - 1] ? 0 : 1)
|
|
516
|
-
);
|
|
517
|
-
}
|
|
518
|
-
previous.splice(0, previous.length, ...current);
|
|
519
|
-
}
|
|
520
|
-
return previous[b.length];
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
function resolveActivityLevelIntent(value) {
|
|
524
|
-
const normalized = normalizeActivityIntentText(value);
|
|
525
|
-
if (!normalized) return { value: "不限", recognized: false, method: "fallback" };
|
|
526
|
-
const compact = compactActivityIntentText(normalized);
|
|
527
|
-
const stripped = stripActivityIntentWrappers(compact);
|
|
528
|
-
const candidates = uniqueList([compact, stripped]);
|
|
529
|
-
|
|
530
|
-
for (const [activityLevel, aliases] of Object.entries(ACTIVITY_LEVEL_ALIASES)) {
|
|
531
|
-
const compactAliases = aliases.map((alias) => compactActivityIntentText(alias));
|
|
532
|
-
if (candidates.some((candidate) => compactAliases.includes(candidate))) {
|
|
533
|
-
return { value: activityLevel, recognized: true, method: "alias" };
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
const durationLevel = activityLevelForRelativeDuration(normalized)
|
|
538
|
-
|| activityLevelForRelativeDuration(stripped);
|
|
539
|
-
const semanticLevels = activityLevelsForSemanticPhrase(normalized);
|
|
540
|
-
const inferredLevels = uniqueList([...semanticLevels, durationLevel]);
|
|
541
|
-
if (inferredLevels.length === 1) {
|
|
542
|
-
return {
|
|
543
|
-
value: inferredLevels[0],
|
|
544
|
-
recognized: true,
|
|
545
|
-
method: durationLevel ? "duration" : "semantic"
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
|
-
if (inferredLevels.length > 1) {
|
|
549
|
-
return { value: "不限", recognized: false, method: "ambiguous" };
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
const fuzzyMatches = [];
|
|
553
|
-
for (const [activityLevel, aliases] of Object.entries(ACTIVITY_LEVEL_ALIASES)) {
|
|
554
|
-
for (const alias of aliases) {
|
|
555
|
-
const compactAlias = compactActivityIntentText(alias);
|
|
556
|
-
if (compactAlias.length < 4) continue;
|
|
557
|
-
for (const candidate of candidates) {
|
|
558
|
-
if (candidate.length < 4 || Math.abs(candidate.length - compactAlias.length) > 1) continue;
|
|
559
|
-
const distance = activityEditDistance(candidate, compactAlias);
|
|
560
|
-
if (distance <= 1) fuzzyMatches.push({ activityLevel, distance });
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
if (fuzzyMatches.length) {
|
|
565
|
-
const minimumDistance = Math.min(...fuzzyMatches.map((item) => item.distance));
|
|
566
|
-
const closestLevels = uniqueList(
|
|
567
|
-
fuzzyMatches.filter((item) => item.distance === minimumDistance).map((item) => item.activityLevel)
|
|
568
|
-
);
|
|
569
|
-
if (closestLevels.length === 1) {
|
|
570
|
-
return { value: closestLevels[0], recognized: true, method: "fuzzy" };
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
return { value: "不限", recognized: false, method: "fallback" };
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
function normalizeActivityLevel(value) {
|
|
578
|
-
return resolveActivityLevelIntent(value).value;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
function normalizeBooleanOverride(value) {
|
|
582
|
-
if (typeof value === "boolean") return value;
|
|
583
|
-
if (typeof value === "number") return value !== 0;
|
|
584
|
-
const normalized = normalizeText(value).toLowerCase();
|
|
585
|
-
if (!normalized) return null;
|
|
586
|
-
if (["true", "yes", "y", "1", "on", "enable", "enabled", "需要", "是", "开启", "启用", "勾选", "选中"].includes(normalized)) return true;
|
|
587
|
-
if (["false", "no", "n", "0", "off", "disable", "disabled", "不需要", "否", "关闭", "不启用", "不勾选", "未选中"].includes(normalized)) return false;
|
|
588
|
-
return null;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
function normalizePostAction(value) {
|
|
592
|
-
const normalized = normalizeText(value).toLowerCase();
|
|
593
|
-
if (!normalized) return null;
|
|
594
|
-
if (["greet", "chat", "打招呼", "直接沟通", "沟通"].includes(normalized)) return "greet";
|
|
595
|
-
if (["none", "noop", "no-op", "什么也不做", "不做任何操作", "不操作", "仅筛选", "只筛选"].includes(normalized)) {
|
|
596
|
-
return "none";
|
|
597
|
-
}
|
|
598
|
-
return null;
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
function normalizePageScope(value) {
|
|
602
|
-
const normalized = normalizeText(value).toLowerCase();
|
|
603
|
-
if (!normalized) return null;
|
|
604
|
-
if (["recommend", "推荐", "推荐页", "推荐页面"].includes(normalized)) return "recommend";
|
|
605
|
-
if (["featured", "精选", "精选页", "精选页面", "精选牛人"].includes(normalized)) return "featured";
|
|
606
|
-
if (["latest", "最新", "最新页", "最新页面"].includes(normalized)) return "latest";
|
|
607
|
-
return PAGE_SCOPE_OPTIONS.includes(normalized) ? normalized : null;
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
function extractPageScope(text) {
|
|
611
|
-
if (FEATURED_SCOPE_PATTERN.test(text)) return "featured";
|
|
612
|
-
if (LATEST_SCOPE_PATTERN.test(text)) return "latest";
|
|
613
|
-
if (RECOMMEND_SCOPE_PATTERN.test(text)) return "recommend";
|
|
614
|
-
return null;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
function sanitizeInstruction(text) {
|
|
618
|
-
let current = normalizeText(text);
|
|
619
|
-
for (const pattern of LEADING_NOISE_PATTERNS) {
|
|
620
|
-
current = current.replace(pattern, "").trim();
|
|
621
|
-
}
|
|
622
|
-
return current;
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
function extractSchoolTags(text) {
|
|
626
|
-
const matches = [];
|
|
627
|
-
for (const { label, pattern } of SCHOOL_TAG_PATTERNS) {
|
|
628
|
-
if (pattern.test(text)) {
|
|
629
|
-
matches.push(label);
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
return uniqueList(matches);
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
function extractGender(text) {
|
|
636
|
-
for (const { label, pattern } of GENDER_PATTERNS) {
|
|
637
|
-
if (pattern.test(text)) {
|
|
638
|
-
return label;
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
return null;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
function extractDegrees(text) {
|
|
645
|
-
return parseDegreeSelectionsFromText(text);
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
function extractRecentNotView(text) {
|
|
649
|
-
for (const pattern of RECENT_NOT_VIEW_NEGATIVE_PATTERNS) {
|
|
650
|
-
if (pattern.test(text)) {
|
|
651
|
-
return "不限";
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
for (const pattern of RECENT_NOT_VIEW_POSITIVE_PATTERNS) {
|
|
655
|
-
if (pattern.test(text)) {
|
|
656
|
-
return "近14天没有";
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
return null;
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
function extractStructuredRunMetaValue(rawText, labelPattern) {
|
|
663
|
-
const normalized = String(rawText || "").replace(/\r\n/g, "\n");
|
|
664
|
-
const match = normalized.match(new RegExp(
|
|
665
|
-
`(?:^|[\\s;;])\\s*(?:${labelPattern})\\s*[::]\\s*([\\s\\S]*?)(?=(?:[\\s;;]\\s*${RUN_META_FIELD_LABEL_PATTERN}\\s*[::])|$)`,
|
|
666
|
-
"i"
|
|
667
|
-
));
|
|
668
|
-
return match ? normalizeText(String(match[1] || "").replace(/[。;;]+$/, "")) : null;
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
function extractActivityLevel(rawText) {
|
|
672
|
-
const structured = extractStructuredRunMetaValue(rawText, "(?:活跃度|活动度|activity[_\\s-]?level)");
|
|
673
|
-
if (structured !== null) {
|
|
674
|
-
return {
|
|
675
|
-
explicit: true,
|
|
676
|
-
raw: structured || "(空值)",
|
|
677
|
-
value: normalizeActivityLevel(structured)
|
|
678
|
-
};
|
|
679
|
-
}
|
|
680
|
-
const normalized = normalizeText(rawText);
|
|
681
|
-
for (const value of ACTIVITY_LEVEL_OPTIONS.filter((item) => item !== "不限")) {
|
|
682
|
-
if (normalized.includes(value)) {
|
|
683
|
-
return { explicit: false, raw: value, value };
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
const activityClauses = String(rawText || "")
|
|
687
|
-
.split(/[,,。;;\n]+/)
|
|
688
|
-
.map((item) => normalizeText(item))
|
|
689
|
-
.filter((item) => /活[跃躍越]|在线|上[线線]|activity|active|online/i.test(item));
|
|
690
|
-
for (const clause of activityClauses) {
|
|
691
|
-
const resolved = resolveActivityLevelIntent(clause);
|
|
692
|
-
if (resolved.recognized) {
|
|
693
|
-
return { explicit: false, raw: clause, value: resolved.value };
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
return { explicit: false, raw: null, value: null };
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
function extractCurrentCityOnly(rawText) {
|
|
700
|
-
const structured = extractStructuredRunMetaValue(
|
|
701
|
-
rawText,
|
|
702
|
-
"(?:current[_\\s-]?city[_\\s-]?only|当前城市筛选|仅推荐本城市|仅推荐期望城市为本城市(?:的牛人)?)"
|
|
703
|
-
);
|
|
704
|
-
const structuredValue = normalizeBooleanOverride(structured);
|
|
705
|
-
if (structured !== null) return structuredValue;
|
|
706
|
-
|
|
707
|
-
const normalized = normalizeText(rawText);
|
|
708
|
-
if (
|
|
709
|
-
/(?:不限|不限制)期望城市|(?:关闭|取消|禁用|去掉|取消勾选)\s*(?:当前城市筛选|仅推荐本城市|仅推荐期望城市为本城市(?:的牛人)?|只推荐期望城市(?:是|为)?本城市)/i.test(normalized)
|
|
710
|
-
) {
|
|
711
|
-
return false;
|
|
712
|
-
}
|
|
713
|
-
if (
|
|
714
|
-
/\bcurrent[_\s-]?city[_\s-]?only\b|仅推荐期望城市为本城市(?:的牛人)?|只推荐期望城市(?:是|为)?本城市|仅推荐本城市/i.test(normalized)
|
|
715
|
-
) {
|
|
716
|
-
return true;
|
|
717
|
-
}
|
|
718
|
-
return null;
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
function extractTargetCount(text) {
|
|
722
|
-
for (const pattern of TARGET_COUNT_PATTERNS) {
|
|
723
|
-
const match = text.match(pattern);
|
|
724
|
-
if (match?.[1]) {
|
|
725
|
-
const count = Number.parseInt(match[1], 10);
|
|
726
|
-
if (Number.isFinite(count) && count > 0) {
|
|
727
|
-
return count;
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
return null;
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
function extractMaxGreetCount(text) {
|
|
735
|
-
for (const pattern of MAX_GREET_COUNT_PATTERNS) {
|
|
736
|
-
const match = text.match(pattern);
|
|
737
|
-
if (match?.[1]) {
|
|
738
|
-
const count = Number.parseInt(match[1], 10);
|
|
739
|
-
if (Number.isFinite(count) && count > 0) {
|
|
740
|
-
return count;
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
return null;
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
function extractJobSelectionHint(text) {
|
|
748
|
-
const normalized = String(text || "").replace(/\r\n/g, "\n");
|
|
749
|
-
const match = normalized.match(new RegExp(
|
|
750
|
-
`(?:^|[\\s;;])\\s*(?:岗位|职位|job)\\s*[::]\\s*([\\s\\S]*?)(?=(?:[\\s;;]\\s*${RUN_META_FIELD_LABEL_PATTERN}\\s*[::])|$)`,
|
|
751
|
-
"i"
|
|
752
|
-
));
|
|
753
|
-
if (!match?.[1]) return null;
|
|
754
|
-
return normalizeText(String(match[1] || "").replace(/[。;;]+$/, "").trim());
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
function sanitizeClause(clause) {
|
|
758
|
-
let current = normalizeText(clause);
|
|
759
|
-
for (const pattern of LEADING_NOISE_PATTERNS) {
|
|
760
|
-
current = current.replace(pattern, "").trim();
|
|
761
|
-
}
|
|
762
|
-
current = current
|
|
763
|
-
.replace(/^符合标准的人选(?:都)?/i, "")
|
|
764
|
-
.replace(/^人选(?:需要|要求)?/i, "")
|
|
765
|
-
.replace(/^候选人(?:需要|要求)?/i, "")
|
|
766
|
-
.replace(/^要求/i, "")
|
|
767
|
-
.trim();
|
|
768
|
-
return current;
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
function isMetaClause(clause) {
|
|
772
|
-
const normalized = sanitizeClause(clause);
|
|
773
|
-
if (!normalized) return true;
|
|
774
|
-
const withoutNumbering = normalized.replace(/^\d+\s*[))]\s*/, "").trim();
|
|
775
|
-
if (!withoutNumbering) return true;
|
|
776
|
-
if (CRITERIA_META_FIELD_PREFIX_PATTERNS.some((pattern) => pattern.test(withoutNumbering))) return true;
|
|
777
|
-
if (META_CLAUSE_PATTERNS.some((pattern) => pattern.test(withoutNumbering))) return true;
|
|
778
|
-
const activityCandidate = stripActivityIntentWrappers(withoutNumbering);
|
|
779
|
-
const activityIntent = resolveActivityLevelIntent(withoutNumbering);
|
|
780
|
-
const isKnownActivityAlias = Object.values(ACTIVITY_LEVEL_ALIASES)
|
|
781
|
-
.flat()
|
|
782
|
-
.map((item) => stripActivityIntentWrappers(item))
|
|
783
|
-
.includes(activityCandidate);
|
|
784
|
-
const isRelativeActivityDuration = /^(?:近|最近|过去|过去的|(?:(?:within|during|in|over)?(?:the)?(?:last|past)?))?(?:\d+(?:\.\d+)?|半|[一二两三四五六七八九十]+|half|one|two|couple|three|few|four|five|six|seven|eight|nine|ten|twenty|thirty)(?:个)?(?:小时|hours?|hrs?|hr|h|天|日|days?|day|d|周|星期|weeks?|week|w|月|months?|month)(?:内|以内|之内|ago)?$/i
|
|
785
|
-
.test(activityCandidate);
|
|
786
|
-
if (
|
|
787
|
-
activityIntent.recognized
|
|
788
|
-
&& (isKnownActivityAlias || isRelativeActivityDuration || activityIntent.method === "fuzzy")
|
|
789
|
-
) {
|
|
790
|
-
return true;
|
|
791
|
-
}
|
|
792
|
-
return false;
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
function splitRawCriteriaClauses(text) {
|
|
796
|
-
const normalized = String(text || "").replace(/\r\n/g, "\n").trim();
|
|
797
|
-
if (!normalized) return [];
|
|
798
|
-
const firstNumberedIndex = normalized.search(/\d+\s*[))]/);
|
|
799
|
-
if (firstNumberedIndex === -1) {
|
|
800
|
-
return normalized
|
|
801
|
-
.split(/[;;\n]+/)
|
|
802
|
-
.map((item) => String(item || "").trim())
|
|
803
|
-
.filter(Boolean);
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
const prefix = normalized
|
|
807
|
-
.slice(0, firstNumberedIndex)
|
|
808
|
-
.replace(/[;;,,。]+$/, "")
|
|
809
|
-
.trim();
|
|
810
|
-
const numberedClauses = normalized
|
|
811
|
-
.slice(firstNumberedIndex)
|
|
812
|
-
.split(/(?=\d+\s*[))])/)
|
|
813
|
-
.map((item) => String(item || "").trim())
|
|
814
|
-
.filter(Boolean);
|
|
815
|
-
|
|
816
|
-
return prefix ? [prefix, ...numberedClauses] : numberedClauses;
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
function normalizeRawCriteriaClauses(clauses = []) {
|
|
820
|
-
const filtered = clauses
|
|
821
|
-
.map((item) => String(item || "").replace(/^[;;,,。]+/, "").replace(/[;;,,。]+$/, "").trim())
|
|
822
|
-
.filter(Boolean)
|
|
823
|
-
.filter((item) => !isMetaClause(item));
|
|
824
|
-
const unique = uniqueList(filtered);
|
|
825
|
-
if (!unique.length) return null;
|
|
826
|
-
return unique.reduce((acc, clause) => {
|
|
827
|
-
if (!acc) return clause;
|
|
828
|
-
if (/[::]$/.test(acc) && /^\d+\s*[))]/.test(clause)) {
|
|
829
|
-
return `${acc}${clause}`;
|
|
830
|
-
}
|
|
831
|
-
return `${acc};${clause}`;
|
|
832
|
-
}, "");
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
function normalizeCriteriaClauses(clauses = []) {
|
|
836
|
-
const filtered = clauses
|
|
837
|
-
.map((item) => sanitizeClause(item))
|
|
838
|
-
.map((item) => item.replace(/^[;;,,。]+/, "").replace(/[;;,,。]+$/, "").trim())
|
|
839
|
-
.filter(Boolean)
|
|
840
|
-
.filter((item) => !isMetaClause(item));
|
|
841
|
-
const unique = uniqueList(filtered.map((item) => normalizeText(item)));
|
|
842
|
-
if (!unique.length) return null;
|
|
843
|
-
return unique.reduce((acc, clause) => {
|
|
844
|
-
if (!acc) return clause;
|
|
845
|
-
if (/[::]$/.test(acc) && /^\d+\s*[))]/.test(clause)) {
|
|
846
|
-
return `${acc}${clause}`;
|
|
847
|
-
}
|
|
848
|
-
return `${acc};${clause}`;
|
|
849
|
-
}, "");
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
function extractExplicitCriteriaBlock(text) {
|
|
853
|
-
const normalizedText = String(text || "").replace(/\r\n/g, "\n");
|
|
854
|
-
const markerMatch = normalizedText.match(CRITERIA_EXPLICIT_MARKER_PATTERN);
|
|
855
|
-
if (!markerMatch) return {
|
|
856
|
-
raw: null,
|
|
857
|
-
normalized: null
|
|
858
|
-
};
|
|
859
|
-
|
|
860
|
-
let block = normalizedText.slice(markerMatch.index + markerMatch[0].length);
|
|
861
|
-
const stopMatch = block.match(CRITERIA_EXPLICIT_STOP_PATTERN);
|
|
862
|
-
if (stopMatch && stopMatch.index > 0) {
|
|
863
|
-
block = block.slice(0, stopMatch.index);
|
|
864
|
-
}
|
|
865
|
-
const rawClauses = splitRawCriteriaClauses(block);
|
|
866
|
-
return {
|
|
867
|
-
raw: normalizeRawCriteriaClauses(rawClauses),
|
|
868
|
-
normalized: normalizeCriteriaClauses(rawClauses)
|
|
869
|
-
};
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
function buildFallbackCriteria(text) {
|
|
873
|
-
const clauses = sanitizeInstruction(text)
|
|
874
|
-
.split(/[,,。;;\n]/)
|
|
875
|
-
.map((item) => String(item || "").trim())
|
|
876
|
-
.filter(Boolean);
|
|
877
|
-
return {
|
|
878
|
-
raw: normalizeRawCriteriaClauses(clauses),
|
|
879
|
-
normalized: normalizeCriteriaClauses(clauses)
|
|
880
|
-
};
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
function buildCriteria({ instruction, rawInstruction, overrideCriteria }) {
|
|
884
|
-
const rawOverride = String(overrideCriteria || "").trim();
|
|
885
|
-
const normalizedOverride = normalizeText(rawOverride);
|
|
886
|
-
if (normalizedOverride) {
|
|
887
|
-
return {
|
|
888
|
-
raw: rawOverride || normalizedOverride,
|
|
889
|
-
normalized: normalizedOverride,
|
|
890
|
-
source: "override"
|
|
891
|
-
};
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
const explicitCriteria = extractExplicitCriteriaBlock(rawInstruction || instruction);
|
|
895
|
-
if (explicitCriteria.raw) {
|
|
896
|
-
return {
|
|
897
|
-
...explicitCriteria,
|
|
898
|
-
source: "explicit"
|
|
899
|
-
};
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
const fallbackCriteria = buildFallbackCriteria(rawInstruction || instruction);
|
|
903
|
-
return {
|
|
904
|
-
...fallbackCriteria,
|
|
905
|
-
source: fallbackCriteria.raw ? "fallback" : null
|
|
906
|
-
};
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
function resolvePostAction({ instruction, confirmation, overrides, finalConfirmed = false }) {
|
|
910
|
-
const confirmed = confirmation?.post_action_confirmed === true;
|
|
911
|
-
const confirmationValue = normalizePostAction(confirmation?.post_action_value);
|
|
912
|
-
const overrideValue = normalizePostAction(overrides?.post_action);
|
|
913
|
-
const instructionValue =
|
|
914
|
-
/打招呼|直接沟通|沟通/.test(instruction)
|
|
915
|
-
? "greet"
|
|
916
|
-
: /什么也不做|不做任何操作|不操作|仅筛选|只筛选/.test(instruction)
|
|
917
|
-
? "none"
|
|
918
|
-
: null;
|
|
919
|
-
const proposed = overrideValue || confirmationValue || instructionValue || null;
|
|
920
|
-
|
|
921
|
-
return {
|
|
922
|
-
post_action: (confirmed || finalConfirmed) && proposed ? proposed : null,
|
|
923
|
-
proposed_post_action: proposed,
|
|
924
|
-
needs_post_action_confirmation: !proposed
|
|
925
|
-
};
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
function resolveTargetCount({ instruction, confirmation, overrides, finalConfirmed = false }) {
|
|
929
|
-
const confirmed = confirmation?.target_count_confirmed === true;
|
|
930
|
-
const overrideValue = parsePositiveIntegerValue(overrides?.target_count);
|
|
931
|
-
const confirmationValue = parsePositiveIntegerValue(confirmation?.target_count_value);
|
|
932
|
-
const instructionValue = extractTargetCount(instruction);
|
|
933
|
-
const proposed = overrideValue || confirmationValue || instructionValue || null;
|
|
934
|
-
const resolved = (confirmed || finalConfirmed)
|
|
935
|
-
? (overrideValue || confirmationValue || instructionValue || null)
|
|
936
|
-
: null;
|
|
937
|
-
|
|
938
|
-
return {
|
|
939
|
-
target_count: resolved,
|
|
940
|
-
proposed_target_count: proposed,
|
|
941
|
-
needs_target_count_confirmation: false
|
|
942
|
-
};
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
function resolveMaxGreetCount({ instruction, confirmation, overrides, postActionResolution, finalConfirmed = false }) {
|
|
946
|
-
const actionHint = postActionResolution.post_action || postActionResolution.proposed_post_action;
|
|
947
|
-
if (actionHint !== "greet") {
|
|
948
|
-
return {
|
|
949
|
-
max_greet_count: null,
|
|
950
|
-
proposed_max_greet_count: null,
|
|
951
|
-
needs_max_greet_count_confirmation: false,
|
|
952
|
-
suspicious_auto_fill: false
|
|
953
|
-
};
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
const overrideValue = parsePositiveIntegerValue(overrides?.max_greet_count);
|
|
957
|
-
const confirmationValue = parsePositiveIntegerValue(confirmation?.max_greet_count_value);
|
|
958
|
-
const instructionValue = extractMaxGreetCount(instruction);
|
|
959
|
-
const proposed = confirmationValue || overrideValue || instructionValue || null;
|
|
960
|
-
const resolved = confirmationValue || overrideValue || instructionValue || null;
|
|
961
|
-
|
|
962
|
-
return {
|
|
963
|
-
max_greet_count: resolved,
|
|
964
|
-
proposed_max_greet_count: proposed,
|
|
965
|
-
needs_max_greet_count_confirmation: false,
|
|
966
|
-
suspicious_auto_fill: false
|
|
967
|
-
};
|
|
968
|
-
}
|
|
969
|
-
|
|
970
|
-
function resolvePageScope({ instruction, confirmation, overrides, finalConfirmed = false }) {
|
|
971
|
-
const confirmed = confirmation?.page_confirmed === true;
|
|
972
|
-
const confirmationValue = normalizePageScope(confirmation?.page_value);
|
|
973
|
-
const overrideValue = normalizePageScope(overrides?.page_scope);
|
|
974
|
-
const instructionValue = extractPageScope(instruction);
|
|
975
|
-
const proposed = overrideValue || confirmationValue || instructionValue || "recommend";
|
|
976
|
-
return {
|
|
977
|
-
page_scope: (confirmed && confirmationValue) || finalConfirmed ? proposed : null,
|
|
978
|
-
proposed_page_scope: proposed,
|
|
979
|
-
needs_page_confirmation: !proposed
|
|
980
|
-
};
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
function collectSuspiciousFields({ invalidOverrideSchoolTags }) {
|
|
984
|
-
const suspicious = [];
|
|
985
|
-
if (Array.isArray(invalidOverrideSchoolTags) && invalidOverrideSchoolTags.length > 0) {
|
|
986
|
-
suspicious.push({
|
|
987
|
-
field: "school_tag",
|
|
988
|
-
value: invalidOverrideSchoolTags,
|
|
989
|
-
reason: `已忽略无效学校标签:${invalidOverrideSchoolTags.join(" / ")};仅保留可识别选项。`
|
|
990
|
-
});
|
|
991
|
-
}
|
|
992
|
-
return suspicious;
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
export function parseRecommendInstruction({ instruction, confirmation, overrides }) {
|
|
996
|
-
const rawInstruction = String(instruction || "");
|
|
997
|
-
const text = normalizeText(rawInstruction);
|
|
998
|
-
const finalConfirmed = confirmation?.final_confirmed === true;
|
|
999
|
-
const detectedSchoolTags = extractSchoolTags(text);
|
|
1000
|
-
const detectedDegrees = extractDegrees(text);
|
|
1001
|
-
const schoolTagAudit = auditSchoolTagSelections(overrides?.school_tag);
|
|
1002
|
-
const overrideSchoolTag = schoolTagAudit.valid.length > 0 ? schoolTagAudit.valid : null;
|
|
1003
|
-
const confirmationSchoolTag = normalizeSchoolTagSelections(confirmation?.school_tag_value);
|
|
1004
|
-
const overrideDegrees = normalizeDegreeSelections(overrides?.degree);
|
|
1005
|
-
const confirmationDegrees = normalizeDegreeSelections(confirmation?.degree_value);
|
|
1006
|
-
const overrideGender = normalizeGender(overrides?.gender);
|
|
1007
|
-
const confirmationGender = normalizeGender(confirmation?.gender_value);
|
|
1008
|
-
const overrideRecentNotView = normalizeRecentNotView(overrides?.recent_not_view);
|
|
1009
|
-
const confirmationRecentNotView = normalizeRecentNotView(confirmation?.recent_not_view_value);
|
|
1010
|
-
const hasCurrentCityOnlyOverride = Object.prototype.hasOwnProperty.call(overrides || {}, "current_city_only");
|
|
1011
|
-
const currentCityOnly = hasCurrentCityOnlyOverride
|
|
1012
|
-
? normalizeBooleanOverride(overrides?.current_city_only) ?? false
|
|
1013
|
-
: extractCurrentCityOnly(rawInstruction) ?? false;
|
|
1014
|
-
const hasActivityLevelOverride = Object.prototype.hasOwnProperty.call(overrides || {}, "activity_level");
|
|
1015
|
-
const overrideActivityLevel = hasActivityLevelOverride
|
|
1016
|
-
? normalizeActivityLevel(overrides?.activity_level)
|
|
1017
|
-
: null;
|
|
1018
|
-
const instructionActivityLevel = extractActivityLevel(rawInstruction);
|
|
1019
|
-
const activityLevel = hasActivityLevelOverride
|
|
1020
|
-
? overrideActivityLevel
|
|
1021
|
-
: instructionActivityLevel.value || "不限";
|
|
1022
|
-
const overrideCriteria = overrides?.criteria;
|
|
1023
|
-
const criteriaResolution = buildCriteria({
|
|
1024
|
-
instruction: text,
|
|
1025
|
-
rawInstruction,
|
|
1026
|
-
overrideCriteria
|
|
1027
|
-
});
|
|
1028
|
-
const jobSelectionHint = normalizeText(
|
|
1029
|
-
overrides?.job
|
|
1030
|
-
|| confirmation?.job_value
|
|
1031
|
-
|| extractJobSelectionHint(rawInstruction)
|
|
1032
|
-
|| ""
|
|
1033
|
-
);
|
|
1034
|
-
const pageScopeResolution = resolvePageScope({ instruction: text, confirmation, overrides, finalConfirmed });
|
|
1035
|
-
const hasSkipRecentColleagueOverride = Object.prototype.hasOwnProperty.call(
|
|
1036
|
-
overrides || {},
|
|
1037
|
-
"skip_recent_colleague_contacted"
|
|
1038
|
-
);
|
|
1039
|
-
const confirmationSkipRecentColleagueContacted = normalizeBooleanOverride(
|
|
1040
|
-
confirmation?.skip_recent_colleague_contacted_value
|
|
1041
|
-
);
|
|
1042
|
-
const skipRecentColleagueContacted = hasSkipRecentColleagueOverride
|
|
1043
|
-
? normalizeBooleanOverride(overrides?.skip_recent_colleague_contacted) ?? true
|
|
1044
|
-
: confirmationSkipRecentColleagueContacted ?? true;
|
|
1045
|
-
|
|
1046
|
-
const inferredSchoolTag = detectedSchoolTags.length > 0
|
|
1047
|
-
? sortSchoolTagSelections(detectedSchoolTags)
|
|
1048
|
-
: ["不限"];
|
|
1049
|
-
const searchParams = {
|
|
1050
|
-
school_tag: overrideSchoolTag || confirmationSchoolTag || inferredSchoolTag,
|
|
1051
|
-
degree: (
|
|
1052
|
-
(Array.isArray(overrideDegrees) && overrideDegrees.length > 0
|
|
1053
|
-
? overrideDegrees
|
|
1054
|
-
: Array.isArray(confirmationDegrees) && confirmationDegrees.length > 0
|
|
1055
|
-
? confirmationDegrees
|
|
1056
|
-
: Array.isArray(detectedDegrees) && detectedDegrees.length > 0
|
|
1057
|
-
? detectedDegrees
|
|
1058
|
-
: ["不限"])
|
|
1059
|
-
),
|
|
1060
|
-
gender: overrideGender || confirmationGender || extractGender(text) || "不限",
|
|
1061
|
-
recent_not_view: overrideRecentNotView || confirmationRecentNotView || extractRecentNotView(text) || "不限",
|
|
1062
|
-
current_city_only: currentCityOnly,
|
|
1063
|
-
activity_level: activityLevel
|
|
1064
|
-
};
|
|
1065
|
-
const screenParams = {
|
|
1066
|
-
criteria: criteriaResolution.raw || criteriaResolution.normalized || null,
|
|
1067
|
-
target_count: null,
|
|
1068
|
-
post_action: null,
|
|
1069
|
-
max_greet_count: null,
|
|
1070
|
-
skip_recent_colleague_contacted: skipRecentColleagueContacted,
|
|
1071
|
-
colleague_contact_window_days: 14
|
|
1072
|
-
};
|
|
1073
|
-
const targetCountResolution = resolveTargetCount({ instruction: text, confirmation, overrides, finalConfirmed });
|
|
1074
|
-
screenParams.target_count = targetCountResolution.target_count;
|
|
1075
|
-
const postActionResolution = resolvePostAction({ instruction: text, confirmation, overrides, finalConfirmed });
|
|
1076
|
-
screenParams.post_action = postActionResolution.post_action;
|
|
1077
|
-
const maxGreetCountResolution = resolveMaxGreetCount({
|
|
1078
|
-
instruction: text,
|
|
1079
|
-
confirmation,
|
|
1080
|
-
overrides,
|
|
1081
|
-
postActionResolution,
|
|
1082
|
-
finalConfirmed
|
|
1083
|
-
});
|
|
1084
|
-
screenParams.max_greet_count = maxGreetCountResolution.max_greet_count;
|
|
1085
|
-
|
|
1086
|
-
const missing_fields = [];
|
|
1087
|
-
if (!screenParams.criteria) {
|
|
1088
|
-
missing_fields.push("criteria");
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
const suspicious_fields = collectSuspiciousFields({
|
|
1092
|
-
invalidOverrideSchoolTags: schoolTagAudit.invalid
|
|
1093
|
-
});
|
|
1094
|
-
const hasResolvedSchoolTagValue = Array.isArray(searchParams.school_tag) && searchParams.school_tag.length > 0;
|
|
1095
|
-
const hasResolvedDegreeValue = Array.isArray(searchParams.degree) && searchParams.degree.length > 0;
|
|
1096
|
-
const hasResolvedGenderValue = Boolean(searchParams.gender);
|
|
1097
|
-
const hasResolvedRecentNotViewValue = Boolean(searchParams.recent_not_view);
|
|
1098
|
-
const needs_school_tag_confirmation = !hasResolvedSchoolTagValue;
|
|
1099
|
-
const needs_degree_confirmation = !hasResolvedDegreeValue;
|
|
1100
|
-
const needs_gender_confirmation = !hasResolvedGenderValue;
|
|
1101
|
-
const needs_recent_not_view_confirmation = !hasResolvedRecentNotViewValue;
|
|
1102
|
-
const needs_filters_confirmation = (
|
|
1103
|
-
needs_school_tag_confirmation
|
|
1104
|
-
|| needs_degree_confirmation
|
|
1105
|
-
|| needs_gender_confirmation
|
|
1106
|
-
|| needs_recent_not_view_confirmation
|
|
1107
|
-
);
|
|
1108
|
-
const needs_criteria_confirmation = !screenParams.criteria;
|
|
1109
|
-
const needs_target_count_confirmation = targetCountResolution.needs_target_count_confirmation;
|
|
1110
|
-
const needs_post_action_confirmation = postActionResolution.needs_post_action_confirmation;
|
|
1111
|
-
const needs_max_greet_count_confirmation = maxGreetCountResolution.needs_max_greet_count_confirmation;
|
|
1112
|
-
const needs_page_confirmation = pageScopeResolution.needs_page_confirmation;
|
|
1113
|
-
const needs_skip_recent_colleague_contacted_confirmation = (
|
|
1114
|
-
!finalConfirmed
|
|
1115
|
-
&& !hasSkipRecentColleagueOverride
|
|
1116
|
-
&& confirmationSkipRecentColleagueContacted === null
|
|
1117
|
-
&& confirmation?.skip_recent_colleague_contacted_confirmed !== true
|
|
1118
|
-
);
|
|
1119
|
-
const pending_questions = [];
|
|
1120
|
-
|
|
1121
|
-
if (needs_page_confirmation) {
|
|
1122
|
-
pending_questions.push({
|
|
1123
|
-
field: "page_scope",
|
|
1124
|
-
question: "请确认本次在推荐里的哪个页面执行筛选:推荐 / 精选 / 最新。",
|
|
1125
|
-
value: pageScopeResolution.proposed_page_scope,
|
|
1126
|
-
options: [
|
|
1127
|
-
{ label: PAGE_SCOPE_LABELS.recommend, value: "recommend" },
|
|
1128
|
-
{ label: PAGE_SCOPE_LABELS.featured, value: "featured" },
|
|
1129
|
-
{ label: PAGE_SCOPE_LABELS.latest, value: "latest" }
|
|
1130
|
-
]
|
|
1131
|
-
});
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
if (needs_school_tag_confirmation) {
|
|
1135
|
-
const schoolTagQuestion = detectedSchoolTags.length > 1
|
|
1136
|
-
? `检测到学校标签:${detectedSchoolTags.join(" / ")}。请确认学校标签筛选(可多选)。`
|
|
1137
|
-
: "请确认学校标签筛选(可多选)。";
|
|
1138
|
-
pending_questions.push({
|
|
1139
|
-
field: "school_tag",
|
|
1140
|
-
question: schoolTagQuestion,
|
|
1141
|
-
value: searchParams.school_tag,
|
|
1142
|
-
options: buildTextOptions(SCHOOL_TAG_OPTIONS)
|
|
1143
|
-
});
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
if (needs_degree_confirmation) {
|
|
1147
|
-
pending_questions.push({
|
|
1148
|
-
field: "degree",
|
|
1149
|
-
question: "请确认学历筛选(可多选)。",
|
|
1150
|
-
value: searchParams.degree,
|
|
1151
|
-
options: buildTextOptions(DEGREE_OPTIONS)
|
|
1152
|
-
});
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
if (needs_gender_confirmation) {
|
|
1156
|
-
pending_questions.push({
|
|
1157
|
-
field: "gender",
|
|
1158
|
-
question: "请确认性别筛选。",
|
|
1159
|
-
value: searchParams.gender,
|
|
1160
|
-
options: buildTextOptions(GENDER_OPTIONS)
|
|
1161
|
-
});
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
if (needs_recent_not_view_confirmation) {
|
|
1165
|
-
pending_questions.push({
|
|
1166
|
-
field: "recent_not_view",
|
|
1167
|
-
question: "请确认是否过滤近14天内已看过的人选。",
|
|
1168
|
-
value: searchParams.recent_not_view,
|
|
1169
|
-
options: buildTextOptions(RECENT_NOT_VIEW_OPTIONS)
|
|
1170
|
-
});
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
if (needs_filters_confirmation && pending_questions.every((item) => item.field !== "filters")) {
|
|
1174
|
-
pending_questions.push({
|
|
1175
|
-
field: "filters",
|
|
1176
|
-
question: "请确认以上推荐页筛选项整体无误。",
|
|
1177
|
-
value: searchParams,
|
|
1178
|
-
options: FILTER_CONFIRM_OPTIONS
|
|
1179
|
-
});
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
if (!screenParams.criteria) {
|
|
1183
|
-
pending_questions.push({
|
|
1184
|
-
field: "criteria",
|
|
1185
|
-
question: "请用自然语言填写本次筛选 criteria(必填,不支持“严格执行/宽松执行”等预设选项)。",
|
|
1186
|
-
value: null
|
|
1187
|
-
});
|
|
1188
|
-
} else if (needs_criteria_confirmation) {
|
|
1189
|
-
pending_questions.push({
|
|
1190
|
-
field: "criteria",
|
|
1191
|
-
question: "请再次确认筛选 criteria(自然语言描述)是否准确;如需调整请直接改写完整 criteria。",
|
|
1192
|
-
value: screenParams.criteria
|
|
1193
|
-
});
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
if (needs_target_count_confirmation) {
|
|
1197
|
-
pending_questions.push({
|
|
1198
|
-
field: "target_count",
|
|
1199
|
-
question: "本次目标通过人数是多少?可留空表示不设上限。",
|
|
1200
|
-
value: targetCountResolution.proposed_target_count
|
|
1201
|
-
});
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
if (needs_post_action_confirmation) {
|
|
1205
|
-
pending_questions.push({
|
|
1206
|
-
field: "post_action",
|
|
1207
|
-
question: "请确认本次运行对通过人选统一执行的动作。",
|
|
1208
|
-
value: postActionResolution.proposed_post_action,
|
|
1209
|
-
options: [
|
|
1210
|
-
{ label: POST_ACTION_LABELS.greet, value: "greet" },
|
|
1211
|
-
{ label: POST_ACTION_LABELS.none, value: "none" }
|
|
1212
|
-
]
|
|
1213
|
-
});
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
if (needs_max_greet_count_confirmation) {
|
|
1217
|
-
pending_questions.push({
|
|
1218
|
-
field: "max_greet_count",
|
|
1219
|
-
question: maxGreetCountResolution.suspicious_auto_fill
|
|
1220
|
-
? "检测到最大打招呼人数可能是自动默认值,请明确确认本次最多打招呼多少位候选人(必须为正整数)。"
|
|
1221
|
-
: "本次选择直接沟通时,最多打招呼多少位候选人?可留空表示不单独限制打招呼人数。",
|
|
1222
|
-
value: maxGreetCountResolution.proposed_max_greet_count
|
|
1223
|
-
});
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
if (needs_skip_recent_colleague_contacted_confirmation) {
|
|
1227
|
-
pending_questions.push({
|
|
1228
|
-
field: "skip_recent_colleague_contacted",
|
|
1229
|
-
question: "是否跳过最近已被同事联系过的人选?推荐页会检查近14天同事沟通记录。",
|
|
1230
|
-
value: true,
|
|
1231
|
-
options: [
|
|
1232
|
-
{ label: "跳过(推荐)", value: true },
|
|
1233
|
-
{ label: "不跳过", value: false }
|
|
1234
|
-
]
|
|
1235
|
-
});
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
return {
|
|
1239
|
-
searchParams,
|
|
1240
|
-
screenParams,
|
|
1241
|
-
missing_fields,
|
|
1242
|
-
suspicious_fields,
|
|
1243
|
-
needs_filters_confirmation,
|
|
1244
|
-
needs_school_tag_confirmation,
|
|
1245
|
-
needs_degree_confirmation,
|
|
1246
|
-
needs_gender_confirmation,
|
|
1247
|
-
needs_recent_not_view_confirmation,
|
|
1248
|
-
needs_criteria_confirmation,
|
|
1249
|
-
needs_target_count_confirmation,
|
|
1250
|
-
needs_post_action_confirmation,
|
|
1251
|
-
needs_max_greet_count_confirmation,
|
|
1252
|
-
needs_page_confirmation,
|
|
1253
|
-
needs_skip_recent_colleague_contacted_confirmation,
|
|
1254
|
-
criteria_normalized: criteriaResolution.normalized,
|
|
1255
|
-
proposed_target_count: targetCountResolution.proposed_target_count,
|
|
1256
|
-
proposed_post_action: postActionResolution.proposed_post_action,
|
|
1257
|
-
proposed_max_greet_count: maxGreetCountResolution.proposed_max_greet_count,
|
|
1258
|
-
page_scope: pageScopeResolution.page_scope,
|
|
1259
|
-
proposed_page_scope: pageScopeResolution.proposed_page_scope,
|
|
1260
|
-
job_selection_hint: jobSelectionHint || null,
|
|
1261
|
-
pending_questions,
|
|
1262
|
-
review: {
|
|
1263
|
-
extracted_page_scope: pageScopeResolution.proposed_page_scope,
|
|
1264
|
-
extracted_search_params: searchParams,
|
|
1265
|
-
extracted_screen_params: {
|
|
1266
|
-
criteria: screenParams.criteria,
|
|
1267
|
-
criteria_normalized: criteriaResolution.normalized,
|
|
1268
|
-
target_count: targetCountResolution.proposed_target_count,
|
|
1269
|
-
post_action: postActionResolution.proposed_post_action,
|
|
1270
|
-
max_greet_count: maxGreetCountResolution.proposed_max_greet_count,
|
|
1271
|
-
skip_recent_colleague_contacted: screenParams.skip_recent_colleague_contacted,
|
|
1272
|
-
colleague_contact_window_days: screenParams.colleague_contact_window_days
|
|
1273
|
-
},
|
|
1274
|
-
current_page_scope: pageScopeResolution.page_scope,
|
|
1275
|
-
current_search_params: searchParams,
|
|
1276
|
-
current_screen_params: {
|
|
1277
|
-
...screenParams,
|
|
1278
|
-
criteria_normalized: criteriaResolution.normalized
|
|
1279
|
-
},
|
|
1280
|
-
missing_fields,
|
|
1281
|
-
suspicious_fields,
|
|
1282
|
-
pending_questions
|
|
1283
|
-
}
|
|
1284
|
-
};
|
|
1285
|
-
}
|
|
1286
|
-
|
|
1287
|
-
export {
|
|
1288
|
-
ACTIVITY_LEVEL_OPTIONS,
|
|
1289
|
-
DEGREE_OPTIONS,
|
|
1290
|
-
GENDER_OPTIONS,
|
|
1291
|
-
POST_ACTION_LABELS,
|
|
1292
|
-
POST_ACTION_OPTIONS,
|
|
1293
|
-
RECENT_NOT_VIEW_OPTIONS,
|
|
1294
|
-
SCHOOL_TAG_OPTIONS,
|
|
1295
|
-
normalizeActivityLevel
|
|
1296
|
-
};
|
|
1
|
+
const SCHOOL_TAG_OPTIONS = [
|
|
2
|
+
"不限",
|
|
3
|
+
"985",
|
|
4
|
+
"211",
|
|
5
|
+
"双一流院校",
|
|
6
|
+
"留学",
|
|
7
|
+
"国内外名校",
|
|
8
|
+
"公办本科"
|
|
9
|
+
];
|
|
10
|
+
const DEGREE_OPTIONS = [
|
|
11
|
+
"不限",
|
|
12
|
+
"初中及以下",
|
|
13
|
+
"中专/中技",
|
|
14
|
+
"高中",
|
|
15
|
+
"大专",
|
|
16
|
+
"本科",
|
|
17
|
+
"硕士",
|
|
18
|
+
"博士"
|
|
19
|
+
];
|
|
20
|
+
const DEGREE_ORDER = [
|
|
21
|
+
"初中及以下",
|
|
22
|
+
"中专/中技",
|
|
23
|
+
"高中",
|
|
24
|
+
"大专",
|
|
25
|
+
"本科",
|
|
26
|
+
"硕士",
|
|
27
|
+
"博士"
|
|
28
|
+
];
|
|
29
|
+
const GENDER_OPTIONS = ["不限", "男", "女"];
|
|
30
|
+
const RECENT_NOT_VIEW_OPTIONS = ["不限", "近14天没有"];
|
|
31
|
+
const ACTIVITY_LEVEL_OPTIONS = [
|
|
32
|
+
"不限",
|
|
33
|
+
"刚刚活跃",
|
|
34
|
+
"今日活跃",
|
|
35
|
+
"3日内活跃",
|
|
36
|
+
"本周活跃",
|
|
37
|
+
"本月活跃"
|
|
38
|
+
];
|
|
39
|
+
const ACTIVITY_LEVEL_ALIASES = Object.freeze({
|
|
40
|
+
"不限": [
|
|
41
|
+
"不限", "不限制", "无要求", "没有要求", "任意", "全部", "都可以", "均可", "随便", "无所谓",
|
|
42
|
+
"nolimit", "norestriction", "unrestricted", "any", "all", "anytime", "nopreference", "doesntmatter", "whatever"
|
|
43
|
+
],
|
|
44
|
+
"刚刚活跃": [
|
|
45
|
+
"刚刚活跃", "刚才活跃", "刚刚", "刚才", "刚上线", "刚刚上线", "当前在线", "现在在线", "正在在线", "在线", "最近活跃", "近期活跃",
|
|
46
|
+
"高活跃", "高活跃度", "活跃度高", "高度活跃", "非常活跃", "很活跃", "超级活跃", "极其活跃", "最活跃", "高频活跃",
|
|
47
|
+
"activenow", "online", "onlinenow", "justnow", "justactive", "justonline", "currentlyactive", "currentlyonline",
|
|
48
|
+
"recentlyactive", "activerecently", "highactivity", "highlyactive", "veryactive", "superactive", "extremelyactive", "mostactive"
|
|
49
|
+
],
|
|
50
|
+
"今日活跃": [
|
|
51
|
+
"今日活跃", "今天活跃", "今日", "今天", "当日", "当天", "当天活跃", "24小时内", "近24小时", "最近24小时", "过去24小时",
|
|
52
|
+
"today", "activetoday", "todayactive", "daily", "dailyactive", "每日活跃", "sameday", "withinthelastday", "inthelastday", "duringthecurrentday",
|
|
53
|
+
"within24hours", "last24hours", "past24hours"
|
|
54
|
+
],
|
|
55
|
+
"3日内活跃": [
|
|
56
|
+
"3日内活跃", "3天内活跃", "三日内活跃", "三天内活跃", "3日内", "3天内", "三日内", "三天内",
|
|
57
|
+
"近3日", "近3天", "最近3日", "最近3天", "过去3日", "过去3天", "昨日", "昨天", "前天", "最近几天",
|
|
58
|
+
"within3days", "last3days", "past3days", "recent3days", "yesterday", "daybeforeyesterday", "recentfewdays"
|
|
59
|
+
],
|
|
60
|
+
"本周活跃": [
|
|
61
|
+
"本周活跃", "本舟活跃", "这周活跃", "本周", "这周", "这个星期", "本星期", "本礼拜", "这礼拜", "一周内", "近一周", "最近一周", "近7天", "最近7天", "7日内", "7天内",
|
|
62
|
+
"中等活跃", "中度活跃", "一般活跃", "普通活跃", "适度活跃", "活跃度一般", "中等活跃度",
|
|
63
|
+
"thisweek", "currentweek", "thisweekend", "weekend", "withinweek", "withinaweek", "withinthelastweek", "inthelastweek", "inthepastweek",
|
|
64
|
+
"duringthecurrentweek", "overthelastweek", "overthepastweek", "lastweek", "pastweek", "weekly", "recent7days", "last7days", "past7days",
|
|
65
|
+
"mediumactivity", "moderatelyactive", "mediumactive", "fairlyactive", "averageactivity", "averagelyactive", "normallyactive"
|
|
66
|
+
],
|
|
67
|
+
"本月活跃": [
|
|
68
|
+
"本月活跃", "这个月活跃", "本月", "这个月", "当月", "一个月内", "近一个月", "最近一个月", "近30天", "最近30天", "30日内", "30天内",
|
|
69
|
+
"低活跃", "低活跃度", "活跃度低", "活跃度不高", "低度活跃", "不太活跃", "偶尔活跃", "较少活跃", "很少活跃", "低频活跃",
|
|
70
|
+
"thismonth", "currentmonth", "withinmonth", "withinamonth", "withinthelastmonth", "inthelastmonth", "inthepastmonth", "duringthecurrentmonth",
|
|
71
|
+
"overthelastmonth", "overthepastmonth", "lastmonth", "pastmonth", "monthly", "recent30days", "last30days", "past30days",
|
|
72
|
+
"lowactivity", "lowactive", "lessactive", "notveryactive", "occasionallyactive", "rarelyactive", "infrequentlyactive", "seldomactive"
|
|
73
|
+
]
|
|
74
|
+
});
|
|
75
|
+
const FILTER_CONFIRM_OPTIONS = [
|
|
76
|
+
{ label: "筛选项无误,继续", value: "confirm" },
|
|
77
|
+
{ label: "筛选项需要调整", value: "revise" }
|
|
78
|
+
];
|
|
79
|
+
const POST_ACTION_OPTIONS = ["greet", "none"];
|
|
80
|
+
const POST_ACTION_LABELS = {
|
|
81
|
+
greet: "直接沟通",
|
|
82
|
+
none: "什么也不做"
|
|
83
|
+
};
|
|
84
|
+
const PAGE_SCOPE_OPTIONS = ["recommend", "featured", "latest"];
|
|
85
|
+
const PAGE_SCOPE_LABELS = {
|
|
86
|
+
recommend: "推荐",
|
|
87
|
+
featured: "精选",
|
|
88
|
+
latest: "最新"
|
|
89
|
+
};
|
|
90
|
+
const LEADING_NOISE_PATTERNS = [
|
|
91
|
+
/^使用boss-recommend-pipeline skills/i,
|
|
92
|
+
/^使用boss recommend pipeline skills/i,
|
|
93
|
+
/^帮我(?:在boss上)?(?:筛选|处理|看一下|跑一下)/i,
|
|
94
|
+
/^请(?:帮我)?(?:在boss上)?(?:筛选|处理|跑一下)/i,
|
|
95
|
+
/^在推荐页(?:上)?/i,
|
|
96
|
+
/^在boss推荐页(?:上)?/i
|
|
97
|
+
];
|
|
98
|
+
const SCHOOL_TAG_PATTERNS = [
|
|
99
|
+
{ label: "985", pattern: /(?:学校|院校|学历|标签|筛选|要求)?[^。;;\n]{0,12}(?:985)(?!\d)/i },
|
|
100
|
+
{ label: "211", pattern: /(?:学校|院校|学历|标签|筛选|要求)?[^。;;\n]{0,12}(?:211)(?!\d)/i },
|
|
101
|
+
{ label: "双一流院校", pattern: /双一流(?:院校|学校)?/i },
|
|
102
|
+
{ label: "留学", pattern: /留学|留学生|海归/i },
|
|
103
|
+
{ label: "国内外名校", pattern: /国内外名校|海内外名校|海外名校|qs\s*(?:top|前)?\s*\d+|名校/i },
|
|
104
|
+
{ label: "公办本科", pattern: /公办本科/i }
|
|
105
|
+
];
|
|
106
|
+
const DEGREE_PATTERNS = [
|
|
107
|
+
{ label: "初中及以下", pattern: /初中及以下|初中以下/i },
|
|
108
|
+
{ label: "中专/中技", pattern: /中专\s*\/\s*中技|中专中技|中专|中技/i },
|
|
109
|
+
{ label: "高中", pattern: /(?:学历|教育|要求)?[^。;;\n]{0,8}高中/i },
|
|
110
|
+
{ label: "大专", pattern: /(?:学历|教育|要求)?[^。;;\n]{0,8}(?:大专|专科)/i },
|
|
111
|
+
{ label: "本科", pattern: /(?:学历|教育|要求)?[^。;;\n]{0,8}(?:本科|学士)/i },
|
|
112
|
+
{ label: "硕士", pattern: /(?:学历|教育|要求)?[^。;;\n]{0,8}(?:硕士|研究生)/i },
|
|
113
|
+
{ label: "博士", pattern: /(?:学历|教育|要求)?[^。;;\n]{0,8}博士/i }
|
|
114
|
+
];
|
|
115
|
+
const GENDER_PATTERNS = [
|
|
116
|
+
{ label: "男", pattern: /(?:性别|候选人|人选)?[^。;;\n]{0,8}(?:男生|男性|男)/i },
|
|
117
|
+
{ label: "女", pattern: /(?:性别|候选人|人选)?[^。;;\n]{0,8}(?:女生|女性|女)/i }
|
|
118
|
+
];
|
|
119
|
+
const RECENT_NOT_VIEW_POSITIVE_PATTERNS = [
|
|
120
|
+
/近?14天(?:内)?没有/i,
|
|
121
|
+
/近?14天(?:内)?没看过/i,
|
|
122
|
+
/近?14天(?:内)?未查看/i,
|
|
123
|
+
/过滤[^。;;\n]{0,12}14天/i,
|
|
124
|
+
/排除[^。;;\n]{0,12}14天/i
|
|
125
|
+
];
|
|
126
|
+
const RECENT_NOT_VIEW_NEGATIVE_PATTERNS = [
|
|
127
|
+
/不限[^。;;\n]{0,8}14天/i,
|
|
128
|
+
/不过滤[^。;;\n]{0,12}14天/i,
|
|
129
|
+
/保留[^。;;\n]{0,12}14天/i
|
|
130
|
+
];
|
|
131
|
+
const TARGET_COUNT_PATTERNS = [
|
|
132
|
+
/目标筛选数(?:量)?(?:为|是|:|:)?\s*(\d+)/i,
|
|
133
|
+
/目标通过数(?:量)?(?:为|是|:|:)?\s*(\d+)/i,
|
|
134
|
+
/目标(?:处理|筛选|通过)?(?:人数|数量)?(?:为|是|:|:)?\s*(\d+)/i,
|
|
135
|
+
/至少(?:处理|筛选|通过)\s*(\d+)\s*(?:位|人)/i,
|
|
136
|
+
/(?:处理|筛选|通过)\s*(\d+)\s*(?:位|人)/i
|
|
137
|
+
];
|
|
138
|
+
const MAX_GREET_COUNT_PATTERNS = [
|
|
139
|
+
/最大招呼数(?:量)?(?:为|是|:|:)?\s*(\d+)/i,
|
|
140
|
+
/最大(?:打招呼|招呼|沟通|联系)(?:人数|数量|数)?(?:为|是|:|:)?\s*(\d+)/i,
|
|
141
|
+
/最多(?:打招呼|沟通|联系)\s*(\d+)\s*(?:位|人|个)?/i,
|
|
142
|
+
/(?:打招呼|沟通|联系)(?:上限|最多|不超过|至多)(?:为|是|:|:)?\s*(\d+)/i
|
|
143
|
+
];
|
|
144
|
+
const RUN_META_FIELD_LABEL_PATTERN = "(?:页面选择|学校标签|院校标签|学历|学位|性别|是否过滤近14天看过|当前城市筛选|仅推荐本城市|仅推荐期望城市为本城市(?:的牛人)?|current[_\\s-]?city[_\\s-]?only|活跃度|活动度|activity[_\\s-]?level|筛选条件|目标筛选数|目标通过人数|通过筛选后动作|最大招呼数|最大打招呼数|岗位)";
|
|
145
|
+
const CRITERIA_EXPLICIT_MARKER_PATTERN = /筛选条件\s*[::]/i;
|
|
146
|
+
const CRITERIA_EXPLICIT_STOP_PATTERN = new RegExp(`(?:^|[\\s;;])\\s*${RUN_META_FIELD_LABEL_PATTERN}\\s*[::]`, "i");
|
|
147
|
+
const CRITERIA_META_FIELD_PREFIX_PATTERNS = [
|
|
148
|
+
new RegExp(`^${RUN_META_FIELD_LABEL_PATTERN}\\s*(?:[::]|$)`, "i"),
|
|
149
|
+
/^(?:近?14天(?:内)?(?:没有|没看过|未查看)|(?:不过滤|保留|过滤|排除)[^。;;\n]{0,12}14天)\s*(?:[::]|$)?/i,
|
|
150
|
+
/^(?:目标(?:处理|筛选|通过)?(?:人数|数量)?|至少(?:处理|筛选|通过)|(?:处理|筛选|通过)\s*\d+\s*(?:位|人))(?:[::\s]|$)/i,
|
|
151
|
+
/^(?:最多(?:打招呼|沟通|联系)|(?:打招呼|沟通|联系)(?:上限|最多|不超过|至多))(?:[::\s]|$)/i,
|
|
152
|
+
/^(?:(?:通过筛选后)?动作|post[_\s-]?action|max[_\s-]?greet[_\s-]?count|target[_\s-]?count)\s*(?:[::]|$)/i
|
|
153
|
+
];
|
|
154
|
+
const META_CLAUSE_PATTERNS = [
|
|
155
|
+
/^推荐页|^推荐页面|^boss推荐/i,
|
|
156
|
+
/^帮我|^请|^运行|^使用.*skill/i,
|
|
157
|
+
/^启动boss推荐任务/i,
|
|
158
|
+
/^条件如下(?:[::]|$)/i,
|
|
159
|
+
/^(?:符合标准(?:的人选)?(?:都)?(?:的)?(?:动作)?[::]?\s*)?(?:收藏|打招呼|直接沟通|什么也不做|不做任何操作|不操作|仅筛选|只筛选)(?:[::]|$)/i
|
|
160
|
+
];
|
|
161
|
+
const FEATURED_SCOPE_PATTERN = /(?:精选牛人|精选页|精选页面|精选tab|精选标签|tab[^。;;\n]{0,6}精选|精选)/i;
|
|
162
|
+
const LATEST_SCOPE_PATTERN = /(?:最新页|最新页面|最新tab|最新标签|tab[^。;;\n]{0,6}最新|最新)/i;
|
|
163
|
+
const RECOMMEND_SCOPE_PATTERN = /(?:推荐页|推荐页面|推荐tab|推荐标签|tab[^。;;\n]{0,6}推荐|推荐)/i;
|
|
164
|
+
|
|
165
|
+
function normalizeText(input) {
|
|
166
|
+
return String(input || "").replace(/\s+/g, " ").trim();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function normalizeCompactText(input) {
|
|
170
|
+
return normalizeText(input).replace(/\s+/g, "").toLowerCase();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function parsePositiveIntegerValue(value) {
|
|
174
|
+
const parsed = Number.parseInt(String(value ?? ""), 10);
|
|
175
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function uniqueList(items) {
|
|
179
|
+
return Array.from(new Set(items.filter(Boolean)));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function buildTextOptions(values = []) {
|
|
183
|
+
return values.map((value) => ({
|
|
184
|
+
label: value,
|
|
185
|
+
value
|
|
186
|
+
}));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function normalizeSchoolTag(value) {
|
|
190
|
+
const normalized = normalizeText(value);
|
|
191
|
+
if (!normalized) return null;
|
|
192
|
+
const compact = normalizeCompactText(normalized);
|
|
193
|
+
if (normalized === "双一流" || compact === "双一流") return "双一流院校";
|
|
194
|
+
if (["留学", "留学生", "海归", "海外留学"].includes(compact)) return "留学";
|
|
195
|
+
if (
|
|
196
|
+
["国内外名校", "海内外名校", "国内外高校", "海内外高校", "海外名校"].includes(compact)
|
|
197
|
+
|| /^qs(?:前|top)?\d+$/.test(compact)
|
|
198
|
+
) {
|
|
199
|
+
return "国内外名校";
|
|
200
|
+
}
|
|
201
|
+
if (SCHOOL_TAG_OPTIONS.includes(normalized)) return normalized;
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function toSchoolTagInputList(input) {
|
|
206
|
+
if (Array.isArray(input)) {
|
|
207
|
+
return input.map((item) => normalizeText(item)).filter(Boolean);
|
|
208
|
+
}
|
|
209
|
+
const text = normalizeText(input);
|
|
210
|
+
if (!text) return [];
|
|
211
|
+
return text.split(/[,,、/|]/).map((item) => normalizeText(item)).filter(Boolean);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function auditSchoolTagSelections(input) {
|
|
215
|
+
const rawItems = toSchoolTagInputList(input);
|
|
216
|
+
const valid = [];
|
|
217
|
+
const invalid = [];
|
|
218
|
+
for (const item of rawItems) {
|
|
219
|
+
const normalized = normalizeSchoolTag(item);
|
|
220
|
+
if (normalized) {
|
|
221
|
+
valid.push(normalized);
|
|
222
|
+
} else {
|
|
223
|
+
invalid.push(item);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
valid: sortSchoolTagSelections(valid),
|
|
228
|
+
invalid: uniqueList(invalid)
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function sortSchoolTagSelections(values) {
|
|
233
|
+
const order = new Map(SCHOOL_TAG_OPTIONS.map((item, index) => [item, index]));
|
|
234
|
+
const unique = Array.from(
|
|
235
|
+
new Set((values || []).map((item) => normalizeSchoolTag(item)).filter(Boolean))
|
|
236
|
+
);
|
|
237
|
+
if (!unique.length) return [];
|
|
238
|
+
if (unique.includes("不限")) {
|
|
239
|
+
return unique.length === 1
|
|
240
|
+
? ["不限"]
|
|
241
|
+
: unique.filter((item) => item !== "不限").sort((left, right) => order.get(left) - order.get(right));
|
|
242
|
+
}
|
|
243
|
+
return unique.sort((left, right) => order.get(left) - order.get(right));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function normalizeSchoolTagSelections(input) {
|
|
247
|
+
const audited = auditSchoolTagSelections(input);
|
|
248
|
+
return audited.valid.length ? audited.valid : null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function normalizeDegree(value) {
|
|
252
|
+
const normalized = normalizeText(value);
|
|
253
|
+
if (!normalized) return null;
|
|
254
|
+
if (normalized === "专科") return "大专";
|
|
255
|
+
if (normalized === "研究生") return "硕士";
|
|
256
|
+
if (normalized === "中专" || normalized === "中技" || normalized === "中专中技") return "中专/中技";
|
|
257
|
+
return DEGREE_OPTIONS.includes(normalized) ? normalized : null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function sortDegreeSelections(values) {
|
|
261
|
+
return uniqueList(values).sort((left, right) => {
|
|
262
|
+
const leftIndex = DEGREE_ORDER.indexOf(left);
|
|
263
|
+
const rightIndex = DEGREE_ORDER.indexOf(right);
|
|
264
|
+
return leftIndex - rightIndex;
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function expandDegreeAtOrAbove(value) {
|
|
269
|
+
const normalized = normalizeDegree(value);
|
|
270
|
+
if (!normalized || normalized === "不限") return [];
|
|
271
|
+
const startIndex = DEGREE_ORDER.indexOf(normalized);
|
|
272
|
+
if (startIndex === -1) return [];
|
|
273
|
+
return DEGREE_ORDER.slice(startIndex);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function parseDegreeSelectionsFromText(text) {
|
|
277
|
+
const normalizedText = normalizeText(text);
|
|
278
|
+
if (!normalizedText) return [];
|
|
279
|
+
if (
|
|
280
|
+
/(?:学历|学位|教育)(?:要求)?\s*(?:[::]\s*)?(?:不限|不限制|无要求)|(?:不限|不限制)\s*(?:[::]\s*)?(?:学历|学位|教育)(?:要求)?/i
|
|
281
|
+
.test(normalizedText)
|
|
282
|
+
) {
|
|
283
|
+
return ["不限"];
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const selected = [];
|
|
287
|
+
const atOrAbovePattern = /(初中及以下|中专\/中技|中专中技|中专|中技|高中|大专|专科|本科|硕士|研究生|博士)\s*(?:及|或)?以上/g;
|
|
288
|
+
let match;
|
|
289
|
+
while ((match = atOrAbovePattern.exec(normalizedText)) !== null) {
|
|
290
|
+
selected.push(...expandDegreeAtOrAbove(match[1]));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
for (const { label, pattern } of DEGREE_PATTERNS) {
|
|
294
|
+
if (pattern.test(normalizedText)) {
|
|
295
|
+
selected.push(label);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return sortDegreeSelections(selected);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function normalizeDegreeSelections(input) {
|
|
302
|
+
if (Array.isArray(input)) {
|
|
303
|
+
const normalized = sortDegreeSelections(input.map((item) => normalizeDegree(item)).filter(Boolean));
|
|
304
|
+
if (!normalized.length) return null;
|
|
305
|
+
return normalized.includes("不限") ? ["不限"] : normalized;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const text = normalizeText(input);
|
|
309
|
+
if (!text) return null;
|
|
310
|
+
if (text.includes("以上")) {
|
|
311
|
+
const fromText = parseDegreeSelectionsFromText(text);
|
|
312
|
+
if (fromText.length) return fromText;
|
|
313
|
+
}
|
|
314
|
+
const parts = text.split(/[,,、/|]/).map((item) => normalizeDegree(item)).filter(Boolean);
|
|
315
|
+
if (parts.length) {
|
|
316
|
+
const normalized = sortDegreeSelections(parts);
|
|
317
|
+
return normalized.includes("不限") ? ["不限"] : normalized;
|
|
318
|
+
}
|
|
319
|
+
const single = normalizeDegree(text);
|
|
320
|
+
return single ? [single] : null;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function normalizeGender(value) {
|
|
324
|
+
const normalized = normalizeText(value);
|
|
325
|
+
if (!normalized) return null;
|
|
326
|
+
if (normalized === "男性" || normalized === "男生") return "男";
|
|
327
|
+
if (normalized === "女性" || normalized === "女生") return "女";
|
|
328
|
+
return GENDER_OPTIONS.includes(normalized) ? normalized : null;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function normalizeRecentNotView(value) {
|
|
332
|
+
const normalized = normalizeText(value);
|
|
333
|
+
if (!normalized) return null;
|
|
334
|
+
const compact = normalizeCompactText(normalized).replace(/天内/g, "天");
|
|
335
|
+
if (
|
|
336
|
+
/^(?:近)?14天(?:没有|未看|没看过|没看|未查看|未查看过)$/.test(compact)
|
|
337
|
+
|| /^(?:过滤|排除)(?:近)?14天(?:已看|看过)?$/.test(compact)
|
|
338
|
+
) {
|
|
339
|
+
return "近14天没有";
|
|
340
|
+
}
|
|
341
|
+
if (
|
|
342
|
+
/^(?:不限|不限制|无要求|无|全部|都可以)$/.test(compact)
|
|
343
|
+
|| /^(?:不过滤|保留)(?:近)?14天(?:已看|看过)?$/.test(compact)
|
|
344
|
+
) {
|
|
345
|
+
return "不限";
|
|
346
|
+
}
|
|
347
|
+
return RECENT_NOT_VIEW_OPTIONS.includes(normalized) ? normalized : null;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function normalizeActivityIntentText(value) {
|
|
351
|
+
return normalizeText(value)
|
|
352
|
+
.normalize("NFKC")
|
|
353
|
+
.toLowerCase()
|
|
354
|
+
.replace(/[內内]/g, "内")
|
|
355
|
+
.replace(/[週周]/g, "周")
|
|
356
|
+
.replace(/[剛刚]/g, "刚")
|
|
357
|
+
.replace(/[當当]/g, "当")
|
|
358
|
+
.replace(/[無无]/g, "无")
|
|
359
|
+
.replace(/[躍跃]/g, "跃");
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function compactActivityIntentText(value) {
|
|
363
|
+
return normalizeActivityIntentText(value).replace(/[\s_\-—–::,,。;;、/|()[\]{}'"“”‘’]+/g, "");
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function stripActivityIntentWrappers(value) {
|
|
367
|
+
let current = compactActivityIntentText(value);
|
|
368
|
+
for (let index = 0; index < 4; index += 1) {
|
|
369
|
+
const previous = current;
|
|
370
|
+
current = current
|
|
371
|
+
.replace(/^(?:please|showme|iwant|want|setto|select|choose|filterby|activitylevel|activitystatus|activity|active|活跃度单选|活跃度|活动度|请选择|请选|选择|设置为|设为|设置|筛选为|筛选|仅推荐|只推荐|推荐|仅看|只看|只要|仅要|要求|希望|我要|想要|选项为|选项是|为|是|选)/, "")
|
|
372
|
+
.replace(/(?:candidates?|people|users?|talents?|option|active|的牛人|牛人|的候选人|候选人|的人选|人选|的人才|人才|的用户|用户|的人|选项|档位|程度|活跃度|活跃)$/u, "")
|
|
373
|
+
.replace(/^(?:为|是|to|is)/, "");
|
|
374
|
+
if (current === previous) break;
|
|
375
|
+
}
|
|
376
|
+
return current;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function parseChineseActivityNumber(value) {
|
|
380
|
+
const normalized = String(value || "");
|
|
381
|
+
if (/^\d+(?:\.\d+)?$/.test(normalized)) return Number(normalized);
|
|
382
|
+
if (normalized === "半") return 0.5;
|
|
383
|
+
const englishNumbers = {
|
|
384
|
+
half: 0.5,
|
|
385
|
+
one: 1,
|
|
386
|
+
two: 2,
|
|
387
|
+
couple: 2,
|
|
388
|
+
three: 3,
|
|
389
|
+
few: 3,
|
|
390
|
+
four: 4,
|
|
391
|
+
five: 5,
|
|
392
|
+
six: 6,
|
|
393
|
+
seven: 7,
|
|
394
|
+
eight: 8,
|
|
395
|
+
nine: 9,
|
|
396
|
+
ten: 10,
|
|
397
|
+
twenty: 20,
|
|
398
|
+
thirty: 30
|
|
399
|
+
};
|
|
400
|
+
if (Object.prototype.hasOwnProperty.call(englishNumbers, normalized.toLowerCase())) {
|
|
401
|
+
return englishNumbers[normalized.toLowerCase()];
|
|
402
|
+
}
|
|
403
|
+
const digits = { 零: 0, 一: 1, 二: 2, 两: 2, 三: 3, 四: 4, 五: 5, 六: 6, 七: 7, 八: 8, 九: 9 };
|
|
404
|
+
if (normalized === "十") return 10;
|
|
405
|
+
if (normalized.includes("十")) {
|
|
406
|
+
const [tens, ones] = normalized.split("十");
|
|
407
|
+
return (tens ? digits[tens] || 0 : 1) * 10 + (ones ? digits[ones] || 0 : 0);
|
|
408
|
+
}
|
|
409
|
+
return digits[normalized] ?? null;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function activityLevelForRelativeDuration(value) {
|
|
413
|
+
const normalized = normalizeActivityIntentText(value);
|
|
414
|
+
const match = normalized.match(
|
|
415
|
+
/(?:近|最近|过去|过去的|within\s*|last\s*|past\s*)?(\d+(?:\.\d+)?|半|[一二两三四五六七八九十]+|half|one|two|couple|three|few|four|five|six|seven|eight|nine|ten|twenty|thirty)\s*(?:个)?\s*(小时|hours?|hrs?|hr|h|天|日|days?|day|d|周|星期|weeks?|week|w|月|months?|month)(?:内|以内|之内|ago)?/i
|
|
416
|
+
);
|
|
417
|
+
if (!match) return null;
|
|
418
|
+
const amount = parseChineseActivityNumber(match[1]);
|
|
419
|
+
if (!Number.isFinite(amount) || amount < 0) return null;
|
|
420
|
+
const unit = match[2].toLowerCase();
|
|
421
|
+
if (/^(?:小时|hours?|hrs?|hr|h)$/.test(unit)) {
|
|
422
|
+
if (amount <= 2) return "刚刚活跃";
|
|
423
|
+
if (amount <= 24) return "今日活跃";
|
|
424
|
+
return activityLevelForRelativeDuration(`${Math.ceil(amount / 24)}天`);
|
|
425
|
+
}
|
|
426
|
+
if (/^(?:天|日|days?|day|d)$/.test(unit)) {
|
|
427
|
+
if (amount <= 1) return "今日活跃";
|
|
428
|
+
const nominalWindows = [
|
|
429
|
+
{ days: 3, activityLevel: "3日内活跃" },
|
|
430
|
+
{ days: 7, activityLevel: "本周活跃" },
|
|
431
|
+
{ days: 30, activityLevel: "本月活跃" }
|
|
432
|
+
];
|
|
433
|
+
return nominalWindows.reduce((closest, candidate) => {
|
|
434
|
+
const closestDistance = Math.abs(amount - closest.days);
|
|
435
|
+
const candidateDistance = Math.abs(amount - candidate.days);
|
|
436
|
+
if (candidateDistance < closestDistance) return candidate;
|
|
437
|
+
if (candidateDistance === closestDistance && candidate.days > closest.days) return candidate;
|
|
438
|
+
return closest;
|
|
439
|
+
}, nominalWindows[0]).activityLevel;
|
|
440
|
+
}
|
|
441
|
+
if (/^(?:周|星期|weeks?|week|w)$/.test(unit)) {
|
|
442
|
+
return activityLevelForRelativeDuration(`${amount * 7}天`);
|
|
443
|
+
}
|
|
444
|
+
return "本月活跃";
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function activityLevelsForSemanticPhrase(value) {
|
|
448
|
+
const normalized = normalizeActivityIntentText(value);
|
|
449
|
+
const compact = compactActivityIntentText(normalized);
|
|
450
|
+
const englishQualitativeText = normalized
|
|
451
|
+
.replace(/\bnot\s+(?:very|highly|high|super|extremely)\b/g, " low ");
|
|
452
|
+
const highQualitativeText = compact
|
|
453
|
+
.replace(/not(?:very|highly|high|super|extremely)active|nothighactivity/g, "")
|
|
454
|
+
.replace(/不太活跃|很少活跃|较少活跃|偶尔活跃|低频活跃|低度活跃|低活跃|活跃度不高|活跃度低/g, "");
|
|
455
|
+
const hasEnglishActivityContext = /\b(?:activity|active)\b/i.test(englishQualitativeText);
|
|
456
|
+
const hasChineseActivityContext = /活跃/.test(normalized);
|
|
457
|
+
const hasHighQualitativeIntent = (
|
|
458
|
+
/highactivity|highlyactive|veryactive|superactive|extremelyactive|mostactive|高活跃|活跃度高|高度活跃|非常活跃|很活跃|超级活跃|极其活跃|最活跃|高频活跃/.test(highQualitativeText)
|
|
459
|
+
|| (hasEnglishActivityContext && /\b(?:high|highly|very|super|extremely|most)\b/i.test(englishQualitativeText))
|
|
460
|
+
|| (hasChineseActivityContext && /高|非常|超级|极其|最活跃/.test(highQualitativeText))
|
|
461
|
+
);
|
|
462
|
+
const hasMediumQualitativeIntent = (
|
|
463
|
+
/mediumactivity|moderatelyactive|mediumactive|fairlyactive|averageactivity|averagelyactive|normallyactive|中等活跃|中度活跃|一般活跃|普通活跃|适度活跃|活跃度一般/.test(compact)
|
|
464
|
+
|| (hasEnglishActivityContext && /\b(?:medium|moderate|moderately|fair|fairly|average|normal|normally)\b/i.test(englishQualitativeText))
|
|
465
|
+
|| (hasChineseActivityContext && /中等|中度|一般|普通|适度/.test(normalized))
|
|
466
|
+
);
|
|
467
|
+
const hasLowQualitativeIntent = (
|
|
468
|
+
/lowactivity|lowactive|lessactive|notveryactive|nothighactivity|occasionallyactive|rarelyactive|infrequentlyactive|seldomactive|低活跃|活跃度低|活跃度不高|低度活跃|不太活跃|偶尔活跃|较少活跃|很少活跃|低频活跃/.test(compact)
|
|
469
|
+
|| (hasEnglishActivityContext && /\b(?:low|less|rare|rarely|occasional|occasionally|infrequent|infrequently|seldom)\b/i.test(englishQualitativeText))
|
|
470
|
+
|| (hasChineseActivityContext && /低|不太|偶尔|较少|很少|低频/.test(normalized))
|
|
471
|
+
);
|
|
472
|
+
const matches = [];
|
|
473
|
+
if (
|
|
474
|
+
/(?:no|without)(?:activity)?(?:limit|restriction|preference)|notrestricted|notactive|notonline|anyactivity|allactivity|dontcare|doesntmatter|不限|不限制|无要求|没有要求|任意|都可以|均可|无所谓/.test(compact)
|
|
475
|
+
) {
|
|
476
|
+
matches.push("不限");
|
|
477
|
+
}
|
|
478
|
+
if (/justnow|rightnow|currentlyactive|currentlyonline|onlinenow|刚刚|刚才|刚上线|当前在线|现在在线/.test(compact)) {
|
|
479
|
+
matches.push("刚刚活跃");
|
|
480
|
+
}
|
|
481
|
+
if (hasHighQualitativeIntent) {
|
|
482
|
+
matches.push("刚刚活跃");
|
|
483
|
+
}
|
|
484
|
+
if (/activetoday|todayactive|today|sameday|(?:within|during|in|over)?(?:the)?(?:last|past|current)(?:one)?day|今日|今天|当日|当天/.test(compact)) {
|
|
485
|
+
matches.push("今日活跃");
|
|
486
|
+
}
|
|
487
|
+
if (/yesterday|daybeforeyesterday|recentfewdays|昨日|昨天|前天|最近几天/.test(compact)) {
|
|
488
|
+
matches.push("3日内活跃");
|
|
489
|
+
}
|
|
490
|
+
if (/(?:this|current)week|(?:within|during|in|over)?(?:the)?(?:last|past)(?:one)?week|thisweekend|weekend|本周|这周|这个星期|本星期/.test(compact)) {
|
|
491
|
+
matches.push("本周活跃");
|
|
492
|
+
}
|
|
493
|
+
if (hasMediumQualitativeIntent) {
|
|
494
|
+
matches.push("本周活跃");
|
|
495
|
+
}
|
|
496
|
+
if (/(?:this|current)month|(?:within|during|in|over)?(?:the)?(?:last|past)(?:one)?month|本月|这个月|当月/.test(compact)) {
|
|
497
|
+
matches.push("本月活跃");
|
|
498
|
+
}
|
|
499
|
+
if (hasLowQualitativeIntent) {
|
|
500
|
+
matches.push("本月活跃");
|
|
501
|
+
}
|
|
502
|
+
return uniqueList(matches);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function activityEditDistance(left, right) {
|
|
506
|
+
const a = String(left || "");
|
|
507
|
+
const b = String(right || "");
|
|
508
|
+
const previous = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
509
|
+
for (let leftIndex = 1; leftIndex <= a.length; leftIndex += 1) {
|
|
510
|
+
const current = [leftIndex];
|
|
511
|
+
for (let rightIndex = 1; rightIndex <= b.length; rightIndex += 1) {
|
|
512
|
+
current[rightIndex] = Math.min(
|
|
513
|
+
current[rightIndex - 1] + 1,
|
|
514
|
+
previous[rightIndex] + 1,
|
|
515
|
+
previous[rightIndex - 1] + (a[leftIndex - 1] === b[rightIndex - 1] ? 0 : 1)
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
previous.splice(0, previous.length, ...current);
|
|
519
|
+
}
|
|
520
|
+
return previous[b.length];
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function resolveActivityLevelIntent(value) {
|
|
524
|
+
const normalized = normalizeActivityIntentText(value);
|
|
525
|
+
if (!normalized) return { value: "不限", recognized: false, method: "fallback" };
|
|
526
|
+
const compact = compactActivityIntentText(normalized);
|
|
527
|
+
const stripped = stripActivityIntentWrappers(compact);
|
|
528
|
+
const candidates = uniqueList([compact, stripped]);
|
|
529
|
+
|
|
530
|
+
for (const [activityLevel, aliases] of Object.entries(ACTIVITY_LEVEL_ALIASES)) {
|
|
531
|
+
const compactAliases = aliases.map((alias) => compactActivityIntentText(alias));
|
|
532
|
+
if (candidates.some((candidate) => compactAliases.includes(candidate))) {
|
|
533
|
+
return { value: activityLevel, recognized: true, method: "alias" };
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const durationLevel = activityLevelForRelativeDuration(normalized)
|
|
538
|
+
|| activityLevelForRelativeDuration(stripped);
|
|
539
|
+
const semanticLevels = activityLevelsForSemanticPhrase(normalized);
|
|
540
|
+
const inferredLevels = uniqueList([...semanticLevels, durationLevel]);
|
|
541
|
+
if (inferredLevels.length === 1) {
|
|
542
|
+
return {
|
|
543
|
+
value: inferredLevels[0],
|
|
544
|
+
recognized: true,
|
|
545
|
+
method: durationLevel ? "duration" : "semantic"
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
if (inferredLevels.length > 1) {
|
|
549
|
+
return { value: "不限", recognized: false, method: "ambiguous" };
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const fuzzyMatches = [];
|
|
553
|
+
for (const [activityLevel, aliases] of Object.entries(ACTIVITY_LEVEL_ALIASES)) {
|
|
554
|
+
for (const alias of aliases) {
|
|
555
|
+
const compactAlias = compactActivityIntentText(alias);
|
|
556
|
+
if (compactAlias.length < 4) continue;
|
|
557
|
+
for (const candidate of candidates) {
|
|
558
|
+
if (candidate.length < 4 || Math.abs(candidate.length - compactAlias.length) > 1) continue;
|
|
559
|
+
const distance = activityEditDistance(candidate, compactAlias);
|
|
560
|
+
if (distance <= 1) fuzzyMatches.push({ activityLevel, distance });
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
if (fuzzyMatches.length) {
|
|
565
|
+
const minimumDistance = Math.min(...fuzzyMatches.map((item) => item.distance));
|
|
566
|
+
const closestLevels = uniqueList(
|
|
567
|
+
fuzzyMatches.filter((item) => item.distance === minimumDistance).map((item) => item.activityLevel)
|
|
568
|
+
);
|
|
569
|
+
if (closestLevels.length === 1) {
|
|
570
|
+
return { value: closestLevels[0], recognized: true, method: "fuzzy" };
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return { value: "不限", recognized: false, method: "fallback" };
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function normalizeActivityLevel(value) {
|
|
578
|
+
return resolveActivityLevelIntent(value).value;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function normalizeBooleanOverride(value) {
|
|
582
|
+
if (typeof value === "boolean") return value;
|
|
583
|
+
if (typeof value === "number") return value !== 0;
|
|
584
|
+
const normalized = normalizeText(value).toLowerCase();
|
|
585
|
+
if (!normalized) return null;
|
|
586
|
+
if (["true", "yes", "y", "1", "on", "enable", "enabled", "需要", "是", "开启", "启用", "勾选", "选中"].includes(normalized)) return true;
|
|
587
|
+
if (["false", "no", "n", "0", "off", "disable", "disabled", "不需要", "否", "关闭", "不启用", "不勾选", "未选中"].includes(normalized)) return false;
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function normalizePostAction(value) {
|
|
592
|
+
const normalized = normalizeText(value).toLowerCase();
|
|
593
|
+
if (!normalized) return null;
|
|
594
|
+
if (["greet", "chat", "打招呼", "直接沟通", "沟通"].includes(normalized)) return "greet";
|
|
595
|
+
if (["none", "noop", "no-op", "什么也不做", "不做任何操作", "不操作", "仅筛选", "只筛选"].includes(normalized)) {
|
|
596
|
+
return "none";
|
|
597
|
+
}
|
|
598
|
+
return null;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function normalizePageScope(value) {
|
|
602
|
+
const normalized = normalizeText(value).toLowerCase();
|
|
603
|
+
if (!normalized) return null;
|
|
604
|
+
if (["recommend", "推荐", "推荐页", "推荐页面"].includes(normalized)) return "recommend";
|
|
605
|
+
if (["featured", "精选", "精选页", "精选页面", "精选牛人"].includes(normalized)) return "featured";
|
|
606
|
+
if (["latest", "最新", "最新页", "最新页面"].includes(normalized)) return "latest";
|
|
607
|
+
return PAGE_SCOPE_OPTIONS.includes(normalized) ? normalized : null;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function extractPageScope(text) {
|
|
611
|
+
if (FEATURED_SCOPE_PATTERN.test(text)) return "featured";
|
|
612
|
+
if (LATEST_SCOPE_PATTERN.test(text)) return "latest";
|
|
613
|
+
if (RECOMMEND_SCOPE_PATTERN.test(text)) return "recommend";
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function sanitizeInstruction(text) {
|
|
618
|
+
let current = normalizeText(text);
|
|
619
|
+
for (const pattern of LEADING_NOISE_PATTERNS) {
|
|
620
|
+
current = current.replace(pattern, "").trim();
|
|
621
|
+
}
|
|
622
|
+
return current;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function extractSchoolTags(text) {
|
|
626
|
+
const matches = [];
|
|
627
|
+
for (const { label, pattern } of SCHOOL_TAG_PATTERNS) {
|
|
628
|
+
if (pattern.test(text)) {
|
|
629
|
+
matches.push(label);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
return uniqueList(matches);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function extractGender(text) {
|
|
636
|
+
for (const { label, pattern } of GENDER_PATTERNS) {
|
|
637
|
+
if (pattern.test(text)) {
|
|
638
|
+
return label;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return null;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function extractDegrees(text) {
|
|
645
|
+
return parseDegreeSelectionsFromText(text);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function extractRecentNotView(text) {
|
|
649
|
+
for (const pattern of RECENT_NOT_VIEW_NEGATIVE_PATTERNS) {
|
|
650
|
+
if (pattern.test(text)) {
|
|
651
|
+
return "不限";
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
for (const pattern of RECENT_NOT_VIEW_POSITIVE_PATTERNS) {
|
|
655
|
+
if (pattern.test(text)) {
|
|
656
|
+
return "近14天没有";
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function extractStructuredRunMetaValue(rawText, labelPattern) {
|
|
663
|
+
const normalized = String(rawText || "").replace(/\r\n/g, "\n");
|
|
664
|
+
const match = normalized.match(new RegExp(
|
|
665
|
+
`(?:^|[\\s;;])\\s*(?:${labelPattern})\\s*[::]\\s*([\\s\\S]*?)(?=(?:[\\s;;]\\s*${RUN_META_FIELD_LABEL_PATTERN}\\s*[::])|$)`,
|
|
666
|
+
"i"
|
|
667
|
+
));
|
|
668
|
+
return match ? normalizeText(String(match[1] || "").replace(/[。;;]+$/, "")) : null;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function extractActivityLevel(rawText) {
|
|
672
|
+
const structured = extractStructuredRunMetaValue(rawText, "(?:活跃度|活动度|activity[_\\s-]?level)");
|
|
673
|
+
if (structured !== null) {
|
|
674
|
+
return {
|
|
675
|
+
explicit: true,
|
|
676
|
+
raw: structured || "(空值)",
|
|
677
|
+
value: normalizeActivityLevel(structured)
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
const normalized = normalizeText(rawText);
|
|
681
|
+
for (const value of ACTIVITY_LEVEL_OPTIONS.filter((item) => item !== "不限")) {
|
|
682
|
+
if (normalized.includes(value)) {
|
|
683
|
+
return { explicit: false, raw: value, value };
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
const activityClauses = String(rawText || "")
|
|
687
|
+
.split(/[,,。;;\n]+/)
|
|
688
|
+
.map((item) => normalizeText(item))
|
|
689
|
+
.filter((item) => /活[跃躍越]|在线|上[线線]|activity|active|online/i.test(item));
|
|
690
|
+
for (const clause of activityClauses) {
|
|
691
|
+
const resolved = resolveActivityLevelIntent(clause);
|
|
692
|
+
if (resolved.recognized) {
|
|
693
|
+
return { explicit: false, raw: clause, value: resolved.value };
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
return { explicit: false, raw: null, value: null };
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function extractCurrentCityOnly(rawText) {
|
|
700
|
+
const structured = extractStructuredRunMetaValue(
|
|
701
|
+
rawText,
|
|
702
|
+
"(?:current[_\\s-]?city[_\\s-]?only|当前城市筛选|仅推荐本城市|仅推荐期望城市为本城市(?:的牛人)?)"
|
|
703
|
+
);
|
|
704
|
+
const structuredValue = normalizeBooleanOverride(structured);
|
|
705
|
+
if (structured !== null) return structuredValue;
|
|
706
|
+
|
|
707
|
+
const normalized = normalizeText(rawText);
|
|
708
|
+
if (
|
|
709
|
+
/(?:不限|不限制)期望城市|(?:关闭|取消|禁用|去掉|取消勾选)\s*(?:当前城市筛选|仅推荐本城市|仅推荐期望城市为本城市(?:的牛人)?|只推荐期望城市(?:是|为)?本城市)/i.test(normalized)
|
|
710
|
+
) {
|
|
711
|
+
return false;
|
|
712
|
+
}
|
|
713
|
+
if (
|
|
714
|
+
/\bcurrent[_\s-]?city[_\s-]?only\b|仅推荐期望城市为本城市(?:的牛人)?|只推荐期望城市(?:是|为)?本城市|仅推荐本城市/i.test(normalized)
|
|
715
|
+
) {
|
|
716
|
+
return true;
|
|
717
|
+
}
|
|
718
|
+
return null;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function extractTargetCount(text) {
|
|
722
|
+
for (const pattern of TARGET_COUNT_PATTERNS) {
|
|
723
|
+
const match = text.match(pattern);
|
|
724
|
+
if (match?.[1]) {
|
|
725
|
+
const count = Number.parseInt(match[1], 10);
|
|
726
|
+
if (Number.isFinite(count) && count > 0) {
|
|
727
|
+
return count;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
return null;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function extractMaxGreetCount(text) {
|
|
735
|
+
for (const pattern of MAX_GREET_COUNT_PATTERNS) {
|
|
736
|
+
const match = text.match(pattern);
|
|
737
|
+
if (match?.[1]) {
|
|
738
|
+
const count = Number.parseInt(match[1], 10);
|
|
739
|
+
if (Number.isFinite(count) && count > 0) {
|
|
740
|
+
return count;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
return null;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function extractJobSelectionHint(text) {
|
|
748
|
+
const normalized = String(text || "").replace(/\r\n/g, "\n");
|
|
749
|
+
const match = normalized.match(new RegExp(
|
|
750
|
+
`(?:^|[\\s;;])\\s*(?:岗位|职位|job)\\s*[::]\\s*([\\s\\S]*?)(?=(?:[\\s;;]\\s*${RUN_META_FIELD_LABEL_PATTERN}\\s*[::])|$)`,
|
|
751
|
+
"i"
|
|
752
|
+
));
|
|
753
|
+
if (!match?.[1]) return null;
|
|
754
|
+
return normalizeText(String(match[1] || "").replace(/[。;;]+$/, "").trim());
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function sanitizeClause(clause) {
|
|
758
|
+
let current = normalizeText(clause);
|
|
759
|
+
for (const pattern of LEADING_NOISE_PATTERNS) {
|
|
760
|
+
current = current.replace(pattern, "").trim();
|
|
761
|
+
}
|
|
762
|
+
current = current
|
|
763
|
+
.replace(/^符合标准的人选(?:都)?/i, "")
|
|
764
|
+
.replace(/^人选(?:需要|要求)?/i, "")
|
|
765
|
+
.replace(/^候选人(?:需要|要求)?/i, "")
|
|
766
|
+
.replace(/^要求/i, "")
|
|
767
|
+
.trim();
|
|
768
|
+
return current;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function isMetaClause(clause) {
|
|
772
|
+
const normalized = sanitizeClause(clause);
|
|
773
|
+
if (!normalized) return true;
|
|
774
|
+
const withoutNumbering = normalized.replace(/^\d+\s*[))]\s*/, "").trim();
|
|
775
|
+
if (!withoutNumbering) return true;
|
|
776
|
+
if (CRITERIA_META_FIELD_PREFIX_PATTERNS.some((pattern) => pattern.test(withoutNumbering))) return true;
|
|
777
|
+
if (META_CLAUSE_PATTERNS.some((pattern) => pattern.test(withoutNumbering))) return true;
|
|
778
|
+
const activityCandidate = stripActivityIntentWrappers(withoutNumbering);
|
|
779
|
+
const activityIntent = resolveActivityLevelIntent(withoutNumbering);
|
|
780
|
+
const isKnownActivityAlias = Object.values(ACTIVITY_LEVEL_ALIASES)
|
|
781
|
+
.flat()
|
|
782
|
+
.map((item) => stripActivityIntentWrappers(item))
|
|
783
|
+
.includes(activityCandidate);
|
|
784
|
+
const isRelativeActivityDuration = /^(?:近|最近|过去|过去的|(?:(?:within|during|in|over)?(?:the)?(?:last|past)?))?(?:\d+(?:\.\d+)?|半|[一二两三四五六七八九十]+|half|one|two|couple|three|few|four|five|six|seven|eight|nine|ten|twenty|thirty)(?:个)?(?:小时|hours?|hrs?|hr|h|天|日|days?|day|d|周|星期|weeks?|week|w|月|months?|month)(?:内|以内|之内|ago)?$/i
|
|
785
|
+
.test(activityCandidate);
|
|
786
|
+
if (
|
|
787
|
+
activityIntent.recognized
|
|
788
|
+
&& (isKnownActivityAlias || isRelativeActivityDuration || activityIntent.method === "fuzzy")
|
|
789
|
+
) {
|
|
790
|
+
return true;
|
|
791
|
+
}
|
|
792
|
+
return false;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
function splitRawCriteriaClauses(text) {
|
|
796
|
+
const normalized = String(text || "").replace(/\r\n/g, "\n").trim();
|
|
797
|
+
if (!normalized) return [];
|
|
798
|
+
const firstNumberedIndex = normalized.search(/\d+\s*[))]/);
|
|
799
|
+
if (firstNumberedIndex === -1) {
|
|
800
|
+
return normalized
|
|
801
|
+
.split(/[;;\n]+/)
|
|
802
|
+
.map((item) => String(item || "").trim())
|
|
803
|
+
.filter(Boolean);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
const prefix = normalized
|
|
807
|
+
.slice(0, firstNumberedIndex)
|
|
808
|
+
.replace(/[;;,,。]+$/, "")
|
|
809
|
+
.trim();
|
|
810
|
+
const numberedClauses = normalized
|
|
811
|
+
.slice(firstNumberedIndex)
|
|
812
|
+
.split(/(?=\d+\s*[))])/)
|
|
813
|
+
.map((item) => String(item || "").trim())
|
|
814
|
+
.filter(Boolean);
|
|
815
|
+
|
|
816
|
+
return prefix ? [prefix, ...numberedClauses] : numberedClauses;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
function normalizeRawCriteriaClauses(clauses = []) {
|
|
820
|
+
const filtered = clauses
|
|
821
|
+
.map((item) => String(item || "").replace(/^[;;,,。]+/, "").replace(/[;;,,。]+$/, "").trim())
|
|
822
|
+
.filter(Boolean)
|
|
823
|
+
.filter((item) => !isMetaClause(item));
|
|
824
|
+
const unique = uniqueList(filtered);
|
|
825
|
+
if (!unique.length) return null;
|
|
826
|
+
return unique.reduce((acc, clause) => {
|
|
827
|
+
if (!acc) return clause;
|
|
828
|
+
if (/[::]$/.test(acc) && /^\d+\s*[))]/.test(clause)) {
|
|
829
|
+
return `${acc}${clause}`;
|
|
830
|
+
}
|
|
831
|
+
return `${acc};${clause}`;
|
|
832
|
+
}, "");
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
function normalizeCriteriaClauses(clauses = []) {
|
|
836
|
+
const filtered = clauses
|
|
837
|
+
.map((item) => sanitizeClause(item))
|
|
838
|
+
.map((item) => item.replace(/^[;;,,。]+/, "").replace(/[;;,,。]+$/, "").trim())
|
|
839
|
+
.filter(Boolean)
|
|
840
|
+
.filter((item) => !isMetaClause(item));
|
|
841
|
+
const unique = uniqueList(filtered.map((item) => normalizeText(item)));
|
|
842
|
+
if (!unique.length) return null;
|
|
843
|
+
return unique.reduce((acc, clause) => {
|
|
844
|
+
if (!acc) return clause;
|
|
845
|
+
if (/[::]$/.test(acc) && /^\d+\s*[))]/.test(clause)) {
|
|
846
|
+
return `${acc}${clause}`;
|
|
847
|
+
}
|
|
848
|
+
return `${acc};${clause}`;
|
|
849
|
+
}, "");
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
function extractExplicitCriteriaBlock(text) {
|
|
853
|
+
const normalizedText = String(text || "").replace(/\r\n/g, "\n");
|
|
854
|
+
const markerMatch = normalizedText.match(CRITERIA_EXPLICIT_MARKER_PATTERN);
|
|
855
|
+
if (!markerMatch) return {
|
|
856
|
+
raw: null,
|
|
857
|
+
normalized: null
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
let block = normalizedText.slice(markerMatch.index + markerMatch[0].length);
|
|
861
|
+
const stopMatch = block.match(CRITERIA_EXPLICIT_STOP_PATTERN);
|
|
862
|
+
if (stopMatch && stopMatch.index > 0) {
|
|
863
|
+
block = block.slice(0, stopMatch.index);
|
|
864
|
+
}
|
|
865
|
+
const rawClauses = splitRawCriteriaClauses(block);
|
|
866
|
+
return {
|
|
867
|
+
raw: normalizeRawCriteriaClauses(rawClauses),
|
|
868
|
+
normalized: normalizeCriteriaClauses(rawClauses)
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function buildFallbackCriteria(text) {
|
|
873
|
+
const clauses = sanitizeInstruction(text)
|
|
874
|
+
.split(/[,,。;;\n]/)
|
|
875
|
+
.map((item) => String(item || "").trim())
|
|
876
|
+
.filter(Boolean);
|
|
877
|
+
return {
|
|
878
|
+
raw: normalizeRawCriteriaClauses(clauses),
|
|
879
|
+
normalized: normalizeCriteriaClauses(clauses)
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
function buildCriteria({ instruction, rawInstruction, overrideCriteria }) {
|
|
884
|
+
const rawOverride = String(overrideCriteria || "").trim();
|
|
885
|
+
const normalizedOverride = normalizeText(rawOverride);
|
|
886
|
+
if (normalizedOverride) {
|
|
887
|
+
return {
|
|
888
|
+
raw: rawOverride || normalizedOverride,
|
|
889
|
+
normalized: normalizedOverride,
|
|
890
|
+
source: "override"
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
const explicitCriteria = extractExplicitCriteriaBlock(rawInstruction || instruction);
|
|
895
|
+
if (explicitCriteria.raw) {
|
|
896
|
+
return {
|
|
897
|
+
...explicitCriteria,
|
|
898
|
+
source: "explicit"
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
const fallbackCriteria = buildFallbackCriteria(rawInstruction || instruction);
|
|
903
|
+
return {
|
|
904
|
+
...fallbackCriteria,
|
|
905
|
+
source: fallbackCriteria.raw ? "fallback" : null
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
function resolvePostAction({ instruction, confirmation, overrides, finalConfirmed = false }) {
|
|
910
|
+
const confirmed = confirmation?.post_action_confirmed === true;
|
|
911
|
+
const confirmationValue = normalizePostAction(confirmation?.post_action_value);
|
|
912
|
+
const overrideValue = normalizePostAction(overrides?.post_action);
|
|
913
|
+
const instructionValue =
|
|
914
|
+
/打招呼|直接沟通|沟通/.test(instruction)
|
|
915
|
+
? "greet"
|
|
916
|
+
: /什么也不做|不做任何操作|不操作|仅筛选|只筛选/.test(instruction)
|
|
917
|
+
? "none"
|
|
918
|
+
: null;
|
|
919
|
+
const proposed = overrideValue || confirmationValue || instructionValue || null;
|
|
920
|
+
|
|
921
|
+
return {
|
|
922
|
+
post_action: (confirmed || finalConfirmed) && proposed ? proposed : null,
|
|
923
|
+
proposed_post_action: proposed,
|
|
924
|
+
needs_post_action_confirmation: !proposed
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
function resolveTargetCount({ instruction, confirmation, overrides, finalConfirmed = false }) {
|
|
929
|
+
const confirmed = confirmation?.target_count_confirmed === true;
|
|
930
|
+
const overrideValue = parsePositiveIntegerValue(overrides?.target_count);
|
|
931
|
+
const confirmationValue = parsePositiveIntegerValue(confirmation?.target_count_value);
|
|
932
|
+
const instructionValue = extractTargetCount(instruction);
|
|
933
|
+
const proposed = overrideValue || confirmationValue || instructionValue || null;
|
|
934
|
+
const resolved = (confirmed || finalConfirmed)
|
|
935
|
+
? (overrideValue || confirmationValue || instructionValue || null)
|
|
936
|
+
: null;
|
|
937
|
+
|
|
938
|
+
return {
|
|
939
|
+
target_count: resolved,
|
|
940
|
+
proposed_target_count: proposed,
|
|
941
|
+
needs_target_count_confirmation: false
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
function resolveMaxGreetCount({ instruction, confirmation, overrides, postActionResolution, finalConfirmed = false }) {
|
|
946
|
+
const actionHint = postActionResolution.post_action || postActionResolution.proposed_post_action;
|
|
947
|
+
if (actionHint !== "greet") {
|
|
948
|
+
return {
|
|
949
|
+
max_greet_count: null,
|
|
950
|
+
proposed_max_greet_count: null,
|
|
951
|
+
needs_max_greet_count_confirmation: false,
|
|
952
|
+
suspicious_auto_fill: false
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
const overrideValue = parsePositiveIntegerValue(overrides?.max_greet_count);
|
|
957
|
+
const confirmationValue = parsePositiveIntegerValue(confirmation?.max_greet_count_value);
|
|
958
|
+
const instructionValue = extractMaxGreetCount(instruction);
|
|
959
|
+
const proposed = confirmationValue || overrideValue || instructionValue || null;
|
|
960
|
+
const resolved = confirmationValue || overrideValue || instructionValue || null;
|
|
961
|
+
|
|
962
|
+
return {
|
|
963
|
+
max_greet_count: resolved,
|
|
964
|
+
proposed_max_greet_count: proposed,
|
|
965
|
+
needs_max_greet_count_confirmation: false,
|
|
966
|
+
suspicious_auto_fill: false
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
function resolvePageScope({ instruction, confirmation, overrides, finalConfirmed = false }) {
|
|
971
|
+
const confirmed = confirmation?.page_confirmed === true;
|
|
972
|
+
const confirmationValue = normalizePageScope(confirmation?.page_value);
|
|
973
|
+
const overrideValue = normalizePageScope(overrides?.page_scope);
|
|
974
|
+
const instructionValue = extractPageScope(instruction);
|
|
975
|
+
const proposed = overrideValue || confirmationValue || instructionValue || "recommend";
|
|
976
|
+
return {
|
|
977
|
+
page_scope: (confirmed && confirmationValue) || finalConfirmed ? proposed : null,
|
|
978
|
+
proposed_page_scope: proposed,
|
|
979
|
+
needs_page_confirmation: !proposed
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
function collectSuspiciousFields({ invalidOverrideSchoolTags }) {
|
|
984
|
+
const suspicious = [];
|
|
985
|
+
if (Array.isArray(invalidOverrideSchoolTags) && invalidOverrideSchoolTags.length > 0) {
|
|
986
|
+
suspicious.push({
|
|
987
|
+
field: "school_tag",
|
|
988
|
+
value: invalidOverrideSchoolTags,
|
|
989
|
+
reason: `已忽略无效学校标签:${invalidOverrideSchoolTags.join(" / ")};仅保留可识别选项。`
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
return suspicious;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
export function parseRecommendInstruction({ instruction, confirmation, overrides }) {
|
|
996
|
+
const rawInstruction = String(instruction || "");
|
|
997
|
+
const text = normalizeText(rawInstruction);
|
|
998
|
+
const finalConfirmed = confirmation?.final_confirmed === true;
|
|
999
|
+
const detectedSchoolTags = extractSchoolTags(text);
|
|
1000
|
+
const detectedDegrees = extractDegrees(text);
|
|
1001
|
+
const schoolTagAudit = auditSchoolTagSelections(overrides?.school_tag);
|
|
1002
|
+
const overrideSchoolTag = schoolTagAudit.valid.length > 0 ? schoolTagAudit.valid : null;
|
|
1003
|
+
const confirmationSchoolTag = normalizeSchoolTagSelections(confirmation?.school_tag_value);
|
|
1004
|
+
const overrideDegrees = normalizeDegreeSelections(overrides?.degree);
|
|
1005
|
+
const confirmationDegrees = normalizeDegreeSelections(confirmation?.degree_value);
|
|
1006
|
+
const overrideGender = normalizeGender(overrides?.gender);
|
|
1007
|
+
const confirmationGender = normalizeGender(confirmation?.gender_value);
|
|
1008
|
+
const overrideRecentNotView = normalizeRecentNotView(overrides?.recent_not_view);
|
|
1009
|
+
const confirmationRecentNotView = normalizeRecentNotView(confirmation?.recent_not_view_value);
|
|
1010
|
+
const hasCurrentCityOnlyOverride = Object.prototype.hasOwnProperty.call(overrides || {}, "current_city_only");
|
|
1011
|
+
const currentCityOnly = hasCurrentCityOnlyOverride
|
|
1012
|
+
? normalizeBooleanOverride(overrides?.current_city_only) ?? false
|
|
1013
|
+
: extractCurrentCityOnly(rawInstruction) ?? false;
|
|
1014
|
+
const hasActivityLevelOverride = Object.prototype.hasOwnProperty.call(overrides || {}, "activity_level");
|
|
1015
|
+
const overrideActivityLevel = hasActivityLevelOverride
|
|
1016
|
+
? normalizeActivityLevel(overrides?.activity_level)
|
|
1017
|
+
: null;
|
|
1018
|
+
const instructionActivityLevel = extractActivityLevel(rawInstruction);
|
|
1019
|
+
const activityLevel = hasActivityLevelOverride
|
|
1020
|
+
? overrideActivityLevel
|
|
1021
|
+
: instructionActivityLevel.value || "不限";
|
|
1022
|
+
const overrideCriteria = overrides?.criteria;
|
|
1023
|
+
const criteriaResolution = buildCriteria({
|
|
1024
|
+
instruction: text,
|
|
1025
|
+
rawInstruction,
|
|
1026
|
+
overrideCriteria
|
|
1027
|
+
});
|
|
1028
|
+
const jobSelectionHint = normalizeText(
|
|
1029
|
+
overrides?.job
|
|
1030
|
+
|| confirmation?.job_value
|
|
1031
|
+
|| extractJobSelectionHint(rawInstruction)
|
|
1032
|
+
|| ""
|
|
1033
|
+
);
|
|
1034
|
+
const pageScopeResolution = resolvePageScope({ instruction: text, confirmation, overrides, finalConfirmed });
|
|
1035
|
+
const hasSkipRecentColleagueOverride = Object.prototype.hasOwnProperty.call(
|
|
1036
|
+
overrides || {},
|
|
1037
|
+
"skip_recent_colleague_contacted"
|
|
1038
|
+
);
|
|
1039
|
+
const confirmationSkipRecentColleagueContacted = normalizeBooleanOverride(
|
|
1040
|
+
confirmation?.skip_recent_colleague_contacted_value
|
|
1041
|
+
);
|
|
1042
|
+
const skipRecentColleagueContacted = hasSkipRecentColleagueOverride
|
|
1043
|
+
? normalizeBooleanOverride(overrides?.skip_recent_colleague_contacted) ?? true
|
|
1044
|
+
: confirmationSkipRecentColleagueContacted ?? true;
|
|
1045
|
+
|
|
1046
|
+
const inferredSchoolTag = detectedSchoolTags.length > 0
|
|
1047
|
+
? sortSchoolTagSelections(detectedSchoolTags)
|
|
1048
|
+
: ["不限"];
|
|
1049
|
+
const searchParams = {
|
|
1050
|
+
school_tag: overrideSchoolTag || confirmationSchoolTag || inferredSchoolTag,
|
|
1051
|
+
degree: (
|
|
1052
|
+
(Array.isArray(overrideDegrees) && overrideDegrees.length > 0
|
|
1053
|
+
? overrideDegrees
|
|
1054
|
+
: Array.isArray(confirmationDegrees) && confirmationDegrees.length > 0
|
|
1055
|
+
? confirmationDegrees
|
|
1056
|
+
: Array.isArray(detectedDegrees) && detectedDegrees.length > 0
|
|
1057
|
+
? detectedDegrees
|
|
1058
|
+
: ["不限"])
|
|
1059
|
+
),
|
|
1060
|
+
gender: overrideGender || confirmationGender || extractGender(text) || "不限",
|
|
1061
|
+
recent_not_view: overrideRecentNotView || confirmationRecentNotView || extractRecentNotView(text) || "不限",
|
|
1062
|
+
current_city_only: currentCityOnly,
|
|
1063
|
+
activity_level: activityLevel
|
|
1064
|
+
};
|
|
1065
|
+
const screenParams = {
|
|
1066
|
+
criteria: criteriaResolution.raw || criteriaResolution.normalized || null,
|
|
1067
|
+
target_count: null,
|
|
1068
|
+
post_action: null,
|
|
1069
|
+
max_greet_count: null,
|
|
1070
|
+
skip_recent_colleague_contacted: skipRecentColleagueContacted,
|
|
1071
|
+
colleague_contact_window_days: 14
|
|
1072
|
+
};
|
|
1073
|
+
const targetCountResolution = resolveTargetCount({ instruction: text, confirmation, overrides, finalConfirmed });
|
|
1074
|
+
screenParams.target_count = targetCountResolution.target_count;
|
|
1075
|
+
const postActionResolution = resolvePostAction({ instruction: text, confirmation, overrides, finalConfirmed });
|
|
1076
|
+
screenParams.post_action = postActionResolution.post_action;
|
|
1077
|
+
const maxGreetCountResolution = resolveMaxGreetCount({
|
|
1078
|
+
instruction: text,
|
|
1079
|
+
confirmation,
|
|
1080
|
+
overrides,
|
|
1081
|
+
postActionResolution,
|
|
1082
|
+
finalConfirmed
|
|
1083
|
+
});
|
|
1084
|
+
screenParams.max_greet_count = maxGreetCountResolution.max_greet_count;
|
|
1085
|
+
|
|
1086
|
+
const missing_fields = [];
|
|
1087
|
+
if (!screenParams.criteria) {
|
|
1088
|
+
missing_fields.push("criteria");
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
const suspicious_fields = collectSuspiciousFields({
|
|
1092
|
+
invalidOverrideSchoolTags: schoolTagAudit.invalid
|
|
1093
|
+
});
|
|
1094
|
+
const hasResolvedSchoolTagValue = Array.isArray(searchParams.school_tag) && searchParams.school_tag.length > 0;
|
|
1095
|
+
const hasResolvedDegreeValue = Array.isArray(searchParams.degree) && searchParams.degree.length > 0;
|
|
1096
|
+
const hasResolvedGenderValue = Boolean(searchParams.gender);
|
|
1097
|
+
const hasResolvedRecentNotViewValue = Boolean(searchParams.recent_not_view);
|
|
1098
|
+
const needs_school_tag_confirmation = !hasResolvedSchoolTagValue;
|
|
1099
|
+
const needs_degree_confirmation = !hasResolvedDegreeValue;
|
|
1100
|
+
const needs_gender_confirmation = !hasResolvedGenderValue;
|
|
1101
|
+
const needs_recent_not_view_confirmation = !hasResolvedRecentNotViewValue;
|
|
1102
|
+
const needs_filters_confirmation = (
|
|
1103
|
+
needs_school_tag_confirmation
|
|
1104
|
+
|| needs_degree_confirmation
|
|
1105
|
+
|| needs_gender_confirmation
|
|
1106
|
+
|| needs_recent_not_view_confirmation
|
|
1107
|
+
);
|
|
1108
|
+
const needs_criteria_confirmation = !screenParams.criteria;
|
|
1109
|
+
const needs_target_count_confirmation = targetCountResolution.needs_target_count_confirmation;
|
|
1110
|
+
const needs_post_action_confirmation = postActionResolution.needs_post_action_confirmation;
|
|
1111
|
+
const needs_max_greet_count_confirmation = maxGreetCountResolution.needs_max_greet_count_confirmation;
|
|
1112
|
+
const needs_page_confirmation = pageScopeResolution.needs_page_confirmation;
|
|
1113
|
+
const needs_skip_recent_colleague_contacted_confirmation = (
|
|
1114
|
+
!finalConfirmed
|
|
1115
|
+
&& !hasSkipRecentColleagueOverride
|
|
1116
|
+
&& confirmationSkipRecentColleagueContacted === null
|
|
1117
|
+
&& confirmation?.skip_recent_colleague_contacted_confirmed !== true
|
|
1118
|
+
);
|
|
1119
|
+
const pending_questions = [];
|
|
1120
|
+
|
|
1121
|
+
if (needs_page_confirmation) {
|
|
1122
|
+
pending_questions.push({
|
|
1123
|
+
field: "page_scope",
|
|
1124
|
+
question: "请确认本次在推荐里的哪个页面执行筛选:推荐 / 精选 / 最新。",
|
|
1125
|
+
value: pageScopeResolution.proposed_page_scope,
|
|
1126
|
+
options: [
|
|
1127
|
+
{ label: PAGE_SCOPE_LABELS.recommend, value: "recommend" },
|
|
1128
|
+
{ label: PAGE_SCOPE_LABELS.featured, value: "featured" },
|
|
1129
|
+
{ label: PAGE_SCOPE_LABELS.latest, value: "latest" }
|
|
1130
|
+
]
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
if (needs_school_tag_confirmation) {
|
|
1135
|
+
const schoolTagQuestion = detectedSchoolTags.length > 1
|
|
1136
|
+
? `检测到学校标签:${detectedSchoolTags.join(" / ")}。请确认学校标签筛选(可多选)。`
|
|
1137
|
+
: "请确认学校标签筛选(可多选)。";
|
|
1138
|
+
pending_questions.push({
|
|
1139
|
+
field: "school_tag",
|
|
1140
|
+
question: schoolTagQuestion,
|
|
1141
|
+
value: searchParams.school_tag,
|
|
1142
|
+
options: buildTextOptions(SCHOOL_TAG_OPTIONS)
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
if (needs_degree_confirmation) {
|
|
1147
|
+
pending_questions.push({
|
|
1148
|
+
field: "degree",
|
|
1149
|
+
question: "请确认学历筛选(可多选)。",
|
|
1150
|
+
value: searchParams.degree,
|
|
1151
|
+
options: buildTextOptions(DEGREE_OPTIONS)
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
if (needs_gender_confirmation) {
|
|
1156
|
+
pending_questions.push({
|
|
1157
|
+
field: "gender",
|
|
1158
|
+
question: "请确认性别筛选。",
|
|
1159
|
+
value: searchParams.gender,
|
|
1160
|
+
options: buildTextOptions(GENDER_OPTIONS)
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
if (needs_recent_not_view_confirmation) {
|
|
1165
|
+
pending_questions.push({
|
|
1166
|
+
field: "recent_not_view",
|
|
1167
|
+
question: "请确认是否过滤近14天内已看过的人选。",
|
|
1168
|
+
value: searchParams.recent_not_view,
|
|
1169
|
+
options: buildTextOptions(RECENT_NOT_VIEW_OPTIONS)
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
if (needs_filters_confirmation && pending_questions.every((item) => item.field !== "filters")) {
|
|
1174
|
+
pending_questions.push({
|
|
1175
|
+
field: "filters",
|
|
1176
|
+
question: "请确认以上推荐页筛选项整体无误。",
|
|
1177
|
+
value: searchParams,
|
|
1178
|
+
options: FILTER_CONFIRM_OPTIONS
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
if (!screenParams.criteria) {
|
|
1183
|
+
pending_questions.push({
|
|
1184
|
+
field: "criteria",
|
|
1185
|
+
question: "请用自然语言填写本次筛选 criteria(必填,不支持“严格执行/宽松执行”等预设选项)。",
|
|
1186
|
+
value: null
|
|
1187
|
+
});
|
|
1188
|
+
} else if (needs_criteria_confirmation) {
|
|
1189
|
+
pending_questions.push({
|
|
1190
|
+
field: "criteria",
|
|
1191
|
+
question: "请再次确认筛选 criteria(自然语言描述)是否准确;如需调整请直接改写完整 criteria。",
|
|
1192
|
+
value: screenParams.criteria
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
if (needs_target_count_confirmation) {
|
|
1197
|
+
pending_questions.push({
|
|
1198
|
+
field: "target_count",
|
|
1199
|
+
question: "本次目标通过人数是多少?可留空表示不设上限。",
|
|
1200
|
+
value: targetCountResolution.proposed_target_count
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
if (needs_post_action_confirmation) {
|
|
1205
|
+
pending_questions.push({
|
|
1206
|
+
field: "post_action",
|
|
1207
|
+
question: "请确认本次运行对通过人选统一执行的动作。",
|
|
1208
|
+
value: postActionResolution.proposed_post_action,
|
|
1209
|
+
options: [
|
|
1210
|
+
{ label: POST_ACTION_LABELS.greet, value: "greet" },
|
|
1211
|
+
{ label: POST_ACTION_LABELS.none, value: "none" }
|
|
1212
|
+
]
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
if (needs_max_greet_count_confirmation) {
|
|
1217
|
+
pending_questions.push({
|
|
1218
|
+
field: "max_greet_count",
|
|
1219
|
+
question: maxGreetCountResolution.suspicious_auto_fill
|
|
1220
|
+
? "检测到最大打招呼人数可能是自动默认值,请明确确认本次最多打招呼多少位候选人(必须为正整数)。"
|
|
1221
|
+
: "本次选择直接沟通时,最多打招呼多少位候选人?可留空表示不单独限制打招呼人数。",
|
|
1222
|
+
value: maxGreetCountResolution.proposed_max_greet_count
|
|
1223
|
+
});
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
if (needs_skip_recent_colleague_contacted_confirmation) {
|
|
1227
|
+
pending_questions.push({
|
|
1228
|
+
field: "skip_recent_colleague_contacted",
|
|
1229
|
+
question: "是否跳过最近已被同事联系过的人选?推荐页会检查近14天同事沟通记录。",
|
|
1230
|
+
value: true,
|
|
1231
|
+
options: [
|
|
1232
|
+
{ label: "跳过(推荐)", value: true },
|
|
1233
|
+
{ label: "不跳过", value: false }
|
|
1234
|
+
]
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
return {
|
|
1239
|
+
searchParams,
|
|
1240
|
+
screenParams,
|
|
1241
|
+
missing_fields,
|
|
1242
|
+
suspicious_fields,
|
|
1243
|
+
needs_filters_confirmation,
|
|
1244
|
+
needs_school_tag_confirmation,
|
|
1245
|
+
needs_degree_confirmation,
|
|
1246
|
+
needs_gender_confirmation,
|
|
1247
|
+
needs_recent_not_view_confirmation,
|
|
1248
|
+
needs_criteria_confirmation,
|
|
1249
|
+
needs_target_count_confirmation,
|
|
1250
|
+
needs_post_action_confirmation,
|
|
1251
|
+
needs_max_greet_count_confirmation,
|
|
1252
|
+
needs_page_confirmation,
|
|
1253
|
+
needs_skip_recent_colleague_contacted_confirmation,
|
|
1254
|
+
criteria_normalized: criteriaResolution.normalized,
|
|
1255
|
+
proposed_target_count: targetCountResolution.proposed_target_count,
|
|
1256
|
+
proposed_post_action: postActionResolution.proposed_post_action,
|
|
1257
|
+
proposed_max_greet_count: maxGreetCountResolution.proposed_max_greet_count,
|
|
1258
|
+
page_scope: pageScopeResolution.page_scope,
|
|
1259
|
+
proposed_page_scope: pageScopeResolution.proposed_page_scope,
|
|
1260
|
+
job_selection_hint: jobSelectionHint || null,
|
|
1261
|
+
pending_questions,
|
|
1262
|
+
review: {
|
|
1263
|
+
extracted_page_scope: pageScopeResolution.proposed_page_scope,
|
|
1264
|
+
extracted_search_params: searchParams,
|
|
1265
|
+
extracted_screen_params: {
|
|
1266
|
+
criteria: screenParams.criteria,
|
|
1267
|
+
criteria_normalized: criteriaResolution.normalized,
|
|
1268
|
+
target_count: targetCountResolution.proposed_target_count,
|
|
1269
|
+
post_action: postActionResolution.proposed_post_action,
|
|
1270
|
+
max_greet_count: maxGreetCountResolution.proposed_max_greet_count,
|
|
1271
|
+
skip_recent_colleague_contacted: screenParams.skip_recent_colleague_contacted,
|
|
1272
|
+
colleague_contact_window_days: screenParams.colleague_contact_window_days
|
|
1273
|
+
},
|
|
1274
|
+
current_page_scope: pageScopeResolution.page_scope,
|
|
1275
|
+
current_search_params: searchParams,
|
|
1276
|
+
current_screen_params: {
|
|
1277
|
+
...screenParams,
|
|
1278
|
+
criteria_normalized: criteriaResolution.normalized
|
|
1279
|
+
},
|
|
1280
|
+
missing_fields,
|
|
1281
|
+
suspicious_fields,
|
|
1282
|
+
pending_questions
|
|
1283
|
+
}
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
export {
|
|
1288
|
+
ACTIVITY_LEVEL_OPTIONS,
|
|
1289
|
+
DEGREE_OPTIONS,
|
|
1290
|
+
GENDER_OPTIONS,
|
|
1291
|
+
POST_ACTION_LABELS,
|
|
1292
|
+
POST_ACTION_OPTIONS,
|
|
1293
|
+
RECENT_NOT_VIEW_OPTIONS,
|
|
1294
|
+
SCHOOL_TAG_OPTIONS,
|
|
1295
|
+
normalizeActivityLevel
|
|
1296
|
+
};
|