@reconcrap/boss-recommend-mcp 2.0.14 → 2.0.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reconcrap/boss-recommend-mcp",
3
- "version": "2.0.14",
3
+ "version": "2.0.15",
4
4
  "description": "Unified MCP pipeline for recommend-page filtering and screening on Boss Zhipin",
5
5
  "keywords": [
6
6
  "boss",
@@ -203,6 +203,115 @@ function createSkippedDetailResult(cardCandidate, reason, error = null) {
203
203
  };
204
204
  }
205
205
 
206
+ const CHAT_FULL_CV_DOM_MIN_TEXT_LENGTH = 500;
207
+ const CHAT_FULL_CV_DOM_MIN_SECTION_TEXT_LENGTH = 180;
208
+ const CHAT_FULL_CV_SECTION_PATTERNS = Object.freeze([
209
+ /教育(?:经历|背景|经验)?/i,
210
+ /工作(?:经历|经验)?/i,
211
+ /项目(?:经历|经验)?/i,
212
+ /实习(?:经历|经验)?/i,
213
+ /科研(?:经历|经验)?/i,
214
+ /论文|会议|专利/i,
215
+ /个人(?:优势|总结|介绍|评价)/i,
216
+ /专业技能|技能(?:特长|标签)?/i,
217
+ /求职(?:期望|意向)/i,
218
+ /校园经历|在校经历|竞赛|证书/i
219
+ ]);
220
+
221
+ function detailTextForFullCvCheck(detailResult = {}) {
222
+ return [
223
+ detailResult?.detail?.popup_text,
224
+ detailResult?.detail?.content_text,
225
+ detailResult?.detail?.resume_iframe_text
226
+ ].filter(Boolean).join("\n\n");
227
+ }
228
+
229
+ function resumeSectionMatchCount(text = "") {
230
+ const normalized = normalizeText(text);
231
+ if (!normalized) return 0;
232
+ return CHAT_FULL_CV_SECTION_PATTERNS
233
+ .filter((pattern) => pattern.test(normalized))
234
+ .length;
235
+ }
236
+
237
+ function hasResumeLikeDomText(text = "") {
238
+ const normalized = normalizeText(text);
239
+ if (normalized.length >= CHAT_FULL_CV_DOM_MIN_TEXT_LENGTH) return true;
240
+ return normalized.length >= CHAT_FULL_CV_DOM_MIN_SECTION_TEXT_LENGTH
241
+ && resumeSectionMatchCount(normalized) >= 2;
242
+ }
243
+
244
+ function networkProfileTextLength(profileResult = {}) {
245
+ return normalizeText(profileResult?.profile?.text || "").length;
246
+ }
247
+
248
+ function isFullCvNetworkProfile(profileResult = {}) {
249
+ if (!profileResult?.ok) return false;
250
+ const sourceKeys = profileResult.profile?.source_keys || {};
251
+ const textLength = networkProfileTextLength(profileResult);
252
+ const sectionCount = resumeSectionMatchCount(profileResult.profile?.text || "");
253
+
254
+ if (sourceKeys.geek_detail_info || sourceKeys.geek_detail) return true;
255
+ if (sourceKeys.network_html_text) {
256
+ return textLength >= CHAT_FULL_CV_DOM_MIN_TEXT_LENGTH
257
+ || sectionCount >= 2;
258
+ }
259
+ if (sourceKeys.chat_history_resume) {
260
+ const educationCount = Number(sourceKeys.education_count) || 0;
261
+ const workCount = Number(sourceKeys.work_count) || 0;
262
+ return (educationCount + workCount) > 0
263
+ && (textLength >= CHAT_FULL_CV_DOM_MIN_SECTION_TEXT_LENGTH || sectionCount >= 2);
264
+ }
265
+ return false;
266
+ }
267
+
268
+ function hasUsableImageEvidence(imageEvidence = null) {
269
+ if (!imageEvidence || imageEvidence.ok === false) return false;
270
+ return Boolean(
271
+ (Array.isArray(imageEvidence.llm_file_paths) && imageEvidence.llm_file_paths.length)
272
+ || (Array.isArray(imageEvidence.file_paths) && imageEvidence.file_paths.length)
273
+ || Number(imageEvidence.llm_screenshot_count) > 0
274
+ || Number(imageEvidence.unique_screenshot_count) > 0
275
+ || Number(imageEvidence.screenshot_count) > 0
276
+ || Number(imageEvidence.capture_count) > 0
277
+ );
278
+ }
279
+
280
+ export function summarizeChatFullCvEvidence({
281
+ detailResult = null,
282
+ contentWait = null,
283
+ imageEvidence = null
284
+ } = {}) {
285
+ const parsedProfiles = (detailResult?.parsed_network_profiles || []).filter((item) => item?.ok);
286
+ const fullNetworkProfiles = parsedProfiles.filter(isFullCvNetworkProfile);
287
+ const profileOnlyCount = Math.max(0, parsedProfiles.length - fullNetworkProfiles.length);
288
+ const detailText = detailTextForFullCvCheck(detailResult);
289
+ const domTextLength = detailText.length;
290
+ const domSectionCount = resumeSectionMatchCount(detailText);
291
+ const domFullCv = Boolean(contentWait?.ok) && hasResumeLikeDomText(detailText);
292
+ const imageFullCv = hasUsableImageEvidence(imageEvidence);
293
+ const source = fullNetworkProfiles.length
294
+ ? "network"
295
+ : domFullCv
296
+ ? "dom"
297
+ : imageFullCv
298
+ ? "image"
299
+ : null;
300
+ return {
301
+ full_cv_acquired: Boolean(source),
302
+ source,
303
+ network_full_cv_count: fullNetworkProfiles.length,
304
+ network_profile_only_count: profileOnlyCount,
305
+ parsed_network_profile_count: parsedProfiles.length,
306
+ dom_full_cv: domFullCv,
307
+ dom_text_length: domTextLength,
308
+ dom_section_count: domSectionCount,
309
+ content_wait_ok: Boolean(contentWait?.ok),
310
+ image_full_cv: imageFullCv,
311
+ image_summary: summarizeImageEvidence(imageEvidence)
312
+ };
313
+ }
314
+
206
315
  async function resolveFreshChatCardNodeId(client, {
207
316
  fallbackNodeId,
208
317
  candidate,
@@ -773,11 +882,12 @@ export async function runChatWorkflow({
773
882
  });
774
883
  addTiming(timings, "late_network_retry_ms", detailResult.network_parse_retry_elapsed_ms);
775
884
  parsedNetworkProfileCount = countParsedNetworkProfiles(detailResult);
776
- if (parsedNetworkProfileCount > 0) {
885
+ const networkEvidence = summarizeChatFullCvEvidence({ detailResult, contentWait });
886
+ if (networkEvidence.network_full_cv_count > 0) {
777
887
  contentWait = {
778
888
  ok: true,
779
889
  skipped: true,
780
- reason: "network_profile_parsed_before_dom_wait",
890
+ reason: "network_full_cv_parsed_before_dom_wait",
781
891
  elapsed_ms: 0,
782
892
  text_length: 0
783
893
  };
@@ -819,8 +929,9 @@ export async function runChatWorkflow({
819
929
  let imageEvidence = null;
820
930
  let llmResult = null;
821
931
  const captureNodeId = captureNodeIdFromResumeState(resumeState);
932
+ let fullCvEvidence = summarizeChatFullCvEvidence({ detailResult, contentWait });
822
933
  const shouldCaptureImage = normalizedDetailSource === "image"
823
- || (normalizedDetailSource === "cascade" && parsedNetworkProfileCount < 1);
934
+ || (normalizedDetailSource === "cascade" && !fullCvEvidence.full_cv_acquired);
824
935
  if (shouldCaptureImage) {
825
936
  if (captureNodeId) {
826
937
  detailStep = "capture_image_fallback";
@@ -861,12 +972,20 @@ export async function runChatWorkflow({
861
972
  }
862
973
  }));
863
974
  source = "image";
975
+ fullCvEvidence = summarizeChatFullCvEvidence({
976
+ detailResult,
977
+ contentWait,
978
+ imageEvidence
979
+ });
864
980
  recordCvImageFallback(cvAcquisitionState, {
981
+ reason: fullCvEvidence.network_profile_only_count > 0
982
+ ? "profile_only_network_image_fallback"
983
+ : "network_miss_image_fallback",
865
984
  parsedNetworkProfileCount,
866
985
  waitResult: networkWait,
867
986
  imageEvidence
868
987
  });
869
- if (callLlmOnImage) {
988
+ if (callLlmOnImage && fullCvEvidence.full_cv_acquired) {
870
989
  detailStep = "llm_image_screening";
871
990
  if (!llmConfig) {
872
991
  llmResult = createMissingLlmConfigResult();
@@ -888,14 +1007,39 @@ export async function runChatWorkflow({
888
1007
  }
889
1008
  } else {
890
1009
  source = "missing_capture_node";
1010
+ fullCvEvidence = summarizeChatFullCvEvidence({
1011
+ detailResult,
1012
+ contentWait,
1013
+ imageEvidence
1014
+ });
891
1015
  recordCvNetworkMiss(cvAcquisitionState, {
892
1016
  reason: "network_miss_no_capture_node",
893
1017
  parsedNetworkProfileCount,
894
1018
  waitResult: networkWait
895
1019
  });
896
1020
  }
897
- } else if (parsedNetworkProfileCount > 0) {
1021
+ } else if (fullCvEvidence.network_full_cv_count > 0) {
1022
+ source = "network";
898
1023
  recordCvNetworkHit(cvAcquisitionState, {
1024
+ reason: "full_cv_network_profile",
1025
+ parsedNetworkProfileCount,
1026
+ waitResult: networkWait
1027
+ });
1028
+ } else if (fullCvEvidence.dom_full_cv) {
1029
+ source = "dom";
1030
+ if (normalizedDetailSource !== "dom") {
1031
+ recordCvNetworkMiss(cvAcquisitionState, {
1032
+ reason: parsedNetworkProfileCount > 0
1033
+ ? "profile_only_network_dom_fallback"
1034
+ : "network_miss_dom_fallback",
1035
+ parsedNetworkProfileCount,
1036
+ waitResult: networkWait
1037
+ });
1038
+ }
1039
+ } else if (parsedNetworkProfileCount > 0) {
1040
+ source = "profile_only_network";
1041
+ recordCvNetworkMiss(cvAcquisitionState, {
1042
+ reason: "profile_only_network_not_full_cv",
899
1043
  parsedNetworkProfileCount,
900
1044
  waitResult: networkWait
901
1045
  });
@@ -909,25 +1053,29 @@ export async function runChatWorkflow({
909
1053
  }
910
1054
 
911
1055
  if (useLlmScreening && !llmResult) {
912
- detailStep = "llm_screening";
913
- if (!llmConfig) {
914
- llmResult = createMissingLlmConfigResult();
1056
+ if (!fullCvEvidence.full_cv_acquired) {
1057
+ detailUnavailableReason = "full_cv_not_acquired";
915
1058
  } else {
916
- try {
917
- const llmTimingKey = imageEvidence?.file_paths?.length
918
- ? "vision_model_ms"
919
- : "text_model_ms";
920
- llmResult = await measureTiming(timings, llmTimingKey, () => callScreeningLlm({
921
- candidate: detailResult.candidate,
922
- criteria,
923
- config: llmConfig,
924
- timeoutMs: llmTimeoutMs,
925
- imageEvidence,
926
- maxImages: llmImageLimit,
927
- imageDetail: llmImageDetail
928
- }));
929
- } catch (error) {
930
- llmResult = createFailedLlmResult(error);
1059
+ detailStep = "llm_screening";
1060
+ if (!llmConfig) {
1061
+ llmResult = createMissingLlmConfigResult();
1062
+ } else {
1063
+ try {
1064
+ const llmTimingKey = imageEvidence?.file_paths?.length
1065
+ ? "vision_model_ms"
1066
+ : "text_model_ms";
1067
+ llmResult = await measureTiming(timings, llmTimingKey, () => callScreeningLlm({
1068
+ candidate: detailResult.candidate,
1069
+ criteria,
1070
+ config: llmConfig,
1071
+ timeoutMs: llmTimeoutMs,
1072
+ imageEvidence,
1073
+ maxImages: llmImageLimit,
1074
+ imageDetail: llmImageDetail
1075
+ }));
1076
+ } catch (error) {
1077
+ llmResult = createFailedLlmResult(error);
1078
+ }
931
1079
  }
932
1080
  }
933
1081
  }
@@ -955,7 +1103,8 @@ export async function runChatWorkflow({
955
1103
  text_length: contentWait.text_length
956
1104
  },
957
1105
  parsed_network_profile_count: parsedNetworkProfileCount,
958
- image_evidence: summarizeImageEvidence(imageEvidence)
1106
+ image_evidence: summarizeImageEvidence(imageEvidence),
1107
+ full_cv_evidence: fullCvEvidence
959
1108
  };
960
1109
  }
961
1110
  } catch (error) {
@@ -979,22 +1128,9 @@ export async function runChatWorkflow({
979
1128
  runControl.setPhase("chat:screening");
980
1129
  let cardOnlyLlmResult = null;
981
1130
  if (useLlmScreening && !detailUnavailableReason && !detailResult?.llm_result) {
982
- if (!llmConfig) {
983
- cardOnlyLlmResult = createMissingLlmConfigResult();
984
- } else {
985
- try {
986
- cardOnlyLlmResult = await measureTiming(timings, "text_model_ms", () => callScreeningLlm({
987
- candidate: screeningCandidate,
988
- criteria,
989
- config: llmConfig,
990
- timeoutMs: llmTimeoutMs,
991
- maxImages: llmImageLimit,
992
- imageDetail: llmImageDetail
993
- }));
994
- } catch (error) {
995
- cardOnlyLlmResult = createFailedLlmResult(error);
996
- }
997
- }
1131
+ detailUnavailableReason = detailResult
1132
+ ? "full_cv_not_acquired"
1133
+ : "detail_not_opened_full_cv_required";
998
1134
  }
999
1135
  const effectiveLlmResult = detailResult?.llm_result || cardOnlyLlmResult;
1000
1136
  const screening = detailUnavailableReason