diff --git a/scripts/ad-networks/niche-sort.mjs b/scripts/ad-networks/niche-sort.mjs index 94dfacee..5e1ab18b 100644 --- a/scripts/ad-networks/niche-sort.mjs +++ b/scripts/ad-networks/niche-sort.mjs @@ -24,7 +24,9 @@ export function validateNicheBatch(input, output) { if (seen.has(row.domain)) throw new Error(`Duplicate result: ${row.domain}`); seen.add(row.domain); rows.push(row); } - if (seen.size !== domains.size) throw new Error(`Domain set mismatch: expected ${domains.size}, received ${seen.size}`); + // The model sometimes drops a site from a long batch. Keep the answers it gave: a missing site + // isn't checkpointed, so the next run asks again. An empty answer is still a failed call. + if (!seen.size) throw new Error(`No niche results for a batch of ${domains.size}`); return rows; } @@ -145,7 +147,7 @@ export async function sortNiches(options, dependencies = {}) { } // Also retry revalidation when a previous run committed before it failed. await (dependencies.revalidate ?? revalidateAdNetworks)(options); - return { applied: true, environment: options.environment, classified: input.length, imported, withheld, synced: true, revalidated: true }; + return { applied: true, environment: options.environment, classified, imported, withheld, synced: true, revalidated: true }; } catch (error) { error.jobResult = { classified, imported, withheld, synced: imported > 0, revalidated: false }; throw error; } finally { diff --git a/tests/pages/ad-networks/niche-sort.test.mjs b/tests/pages/ad-networks/niche-sort.test.mjs index 41f79dfe..5c0b019d 100644 --- a/tests/pages/ad-networks/niche-sort.test.mjs +++ b/tests/pages/ad-networks/niche-sort.test.mjs @@ -9,10 +9,13 @@ import { adNetworkNicheShares, hasAdNetworkNicheCoverage } from '../../../lib/ad const input = [{ domain: 'example.com' }, { domain: 'example.org' }]; const result = (domain, niche = 'food', confidence = 'high') => ({ domain, niche, confidence }); const lines = rows => rows.map(row => JSON.stringify(row)).join('\n'); -test('model validation requires the exact input set once each, known labels, and confidence', () => { +test('model validation takes only input domains once each, with known labels and confidence', () => { const correct = [result('example.com'), result('example.org', null, 'low')]; assert.deepEqual(validateNicheBatch(input, lines(correct)), correct); - for (const bad of [[correct[0]], [correct[0], correct[0]], [...correct, result('extra.com')], + // A dropped site is left for the next run; an empty answer fails. + assert.deepEqual(validateNicheBatch(input, lines([correct[0]])), [correct[0]]); + assert.throws(() => validateNicheBatch(input, ''), /No niche results/); + for (const bad of [[correct[0], correct[0]], [...correct, result('extra.com')], [result('example.com', 'invented'), correct[1]], [result('example.com', 'food', 1.5), correct[1]], [{ ...correct[0], comment: 'extra' }, correct[1]], [result('https://example.com'), correct[1]]]) { assert.throws(() => validateNicheBatch(input, lines(bad)));