(function attachEvidenceGuidance(global) { function searchableEvidenceItems(submission) { return (submission?.evidence || []).filter((item) => ["naver", "google", "llm", "failure"].includes(item.source)); } function directEvidenceItems(submission) { return (submission?.evidence || []).filter((item) => ["full", "partial", "page"].includes(item.matchType) || item.contributed); } function realSearchableEvidenceItems(submission) { return searchableEvidenceItems(submission).filter((item) => item.source !== "failure"); } function externalProviderStates(submission) { // The backend always injects internal:"ok", so it must be excluded — otherwise // every submission looks like it has had an external search attempted. return Object.entries(submission?.providerState || {}) .filter(([provider]) => provider !== "internal") .map(([, status]) => status); } function hasSearchAttempt(submission) { const providerStates = externalProviderStates(submission); return Boolean( submission?.queryHistory?.length || searchableEvidenceItems(submission).length || providerStates.some((status) => ["ok", "covered", "empty", "failed"].includes(status)), ); } function evidenceNeedsFollowup(submission) { if (!submission || !hasSearchAttempt(submission)) return false; // Use the failure-excluded count so this gate agrees with the count used in // evidenceFollowupReasons; counting failure items here suppressed legitimate // followups that the reasons function would have reported. return directEvidenceItems(submission).length === 0 || realSearchableEvidenceItems(submission).length < 2; } function evidenceFollowupReasons(submission) { if (!submission || !hasSearchAttempt(submission)) return []; const reasons = []; const directCount = directEvidenceItems(submission).length; const searchableCount = realSearchableEvidenceItems(submission).length; const providerStates = Object.values(submission.providerState || {}); if (directCount === 0) reasons.push("직접 매칭 또는 원문 페이지 근거가 없습니다."); if (searchableCount < 2) reasons.push("검색 근거가 2건 미만입니다."); if (providerStates.includes("empty")) reasons.push("외부 검색 tool이 빈 결과를 반환했습니다."); if (providerStates.includes("failed")) reasons.push("외부 검색 tool 실패 이력이 있습니다."); return reasons; } function normalizedQuerySeed(submission) { return String(submission?.title || submission?.id || "") .replace(/\s+/g, " ") .trim(); } function suggestedEvidenceQueries(submission) { const seed = normalizedQuerySeed(submission); if (!seed) return []; const existingQueries = new Set((submission.queryHistory || []).map((query) => String(query.query || "").trim().toLowerCase())); return [seed, `${seed} 저작권`, `${seed} 공식`, `${seed} 이미지 출처`] .filter((query, index, list) => query && list.indexOf(query) === index) .filter((query) => !existingQueries.has(query.toLowerCase())) .slice(0, 4); } global.OperatorEvidenceGuidance = { searchableEvidenceItems, directEvidenceItems, hasSearchAttempt, evidenceNeedsFollowup, evidenceFollowupReasons, normalizedQuerySeed, suggestedEvidenceQueries, }; })(window);