| |
| |
| |
| |
| |
| |
| |
|
|
| import { SYNTHETIC_GENERATION_ORIGINS, SHADOW_GENERATION_ORIGINS } from './_forecast-scorecard.mjs'; |
|
|
| export const DEFAULT_MIN_DISTINCT_DOMAINS = 4; |
| export const DEFAULT_MAX_SYNTHETIC_SHARE = 0.5; |
| |
| |
| |
| |
| export const NON_REAL_FUNNEL_ORIGINS = [...SYNTHETIC_GENERATION_ORIGINS, ...SHADOW_GENERATION_ORIGINS]; |
|
|
| export function assessFunnelDiversity(predictions, options = {}) { |
| const minDistinctDomains = options.minDistinctDomains ?? DEFAULT_MIN_DISTINCT_DOMAINS; |
| const maxSyntheticShare = options.maxSyntheticShare ?? DEFAULT_MAX_SYNTHETIC_SHARE; |
| const syntheticOrigins = new Set(options.syntheticOrigins ?? NON_REAL_FUNNEL_ORIGINS); |
|
|
| const list = Array.isArray(predictions) ? predictions.filter(Boolean) : []; |
| const total = list.length; |
| const domains = new Set(); |
| let syntheticCount = 0; |
| for (const pred of list) { |
| if (pred.domain) domains.add(pred.domain); |
| const origin = pred.generationOrigin || 'legacy_detector'; |
| if (syntheticOrigins.has(origin)) syntheticCount++; |
| } |
|
|
| const domainCount = domains.size; |
| const syntheticShare = total ? syntheticCount / total : 0; |
|
|
| const reasons = []; |
| if (total > 0 && domainCount < minDistinctDomains) { |
| reasons.push(`only ${domainCount} distinct domain(s) (min ${minDistinctDomains})`); |
| } |
| if (total > 0 && syntheticShare > maxSyntheticShare) { |
| reasons.push(`synthetic share ${round(syntheticShare)} exceeds ${maxSyntheticShare}`); |
| } |
| |
| |
| const collapsed = reasons.length > 0; |
|
|
| return { |
| total, |
| domainCount, |
| domains: [...domains].sort(), |
| syntheticCount, |
| syntheticShare: round(syntheticShare), |
| minDistinctDomains, |
| maxSyntheticShare, |
| collapsed, |
| reasons, |
| }; |
| } |
|
|
| function round(value) { |
| if (!Number.isFinite(value)) return value; |
| return Math.round(value * 1_000_000) / 1_000_000; |
| } |
|
|