openvibers/openvibe.deals Commits
Search documents say each thing once (#200)
Changes
@@@ -58,20 +58,33 @@ function createIndexing({ config, store, publication, outbox, catalog, indexnow}, { traceparent });}++ // A result's snippet reads the summary and the body together, so the body holds only what the title and summary do++ // not: an offer's body repeated its title twice ("FuelCell 796V4 Tennis Shoes\nFuelCell 796V4 Tennis Shoes\n…",++ // 2026-11-02) and an offer with no description had no summary at all.++ const same = (a, b) => String(a || '').trim().toLowerCase() === String(b || '').trim().toLowerCase();++ const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];++ const day = (t) => { const d = new Date(t); return Number.isNaN(d.getTime()) ? '' : `${MONTHS[d.getUTCMonth()]} ${d.getUTCDate()}, ${d.getUTCFullYear()}`; };++ const money = (price, currency) => (currency === 'USD' || !currency ? `$${Number(price).toFixed(2)}` : `${Number(price).toFixed(2)} ${currency}`);++ function offerLine(v) {++ const store = v.store && (v.store.name || v.store.domain);++ if (!v.latest || v.latest.price == null) return `Price not stated${store ? `, at ${store}` : ''}.`;++ return `${money(v.latest.price, v.latest.currency)}${store ? ` at ${store}` : ''}, as of ${day(v.latest.observed_at)}.`;++ }++function offerDocument(v) {const r = v.root;const provenance = v.sources.filter((s) => s.ref_service === 'sources' && !s.removed_at).slice(0, 40).map((s) => ({service: 'sources', type: 'item', id: s.ref_id, ...(Number.isInteger(s.ref_revision) ? { revision: s.ref_revision } : {}),url: s.url || undefined, retrievedAt: s.retrieved_at || undefined, label: s.source_key || undefined,}));-- const priceText = v.latest && v.latest.price != null ? `${v.latest.price}${v.latest.currency ? ` ${v.latest.currency}` : ''} as of ${iso(v.latest.observed_at)}` : 'price not stated';return hooks.buildIndexDocument({owner: 'deals', type: 'offer', id: r.id, revision: 0,state: r.status === 'disabled' ? 'unpublished' : 'published', visibility: 'public',canonicalUrl: publication.abs(publication.offerPath(r)),title: r.title,-- summary: r.description ? r.description.slice(0, 1000) : null,-- body: [r.title, r.description, v.product && v.product.name, v.store && (v.store.name || v.store.domain), priceText].filter(Boolean).join('\n'),++ summary: r.description ? r.description.slice(0, 1000) : offerLine(v) || null,++ body: [r.description && r.description.length > 1000 ? r.description : '', r.description ? offerLine(v) : '',++ v.product && !same(v.product.name, r.title) ? v.product.name : ''].filter(Boolean).join('\n'),facets: {status: r.status, freshness: v.freshness.state, store: v.store ? v.store.domain : null, category: r.category,currency: v.latest && v.latest.currency ? v.latest.currency : null, product: v.product ? v.product.slug : null,@@@ -97,12 +110,14 @@ function createIndexing({ config, store, publication, outbox, catalog, indexnowasync function indexProduct(product) {if (!product) return null;const pv = await publication.productView(product);-- const lines = pv.offers.filter((v) => v.root.status === 'active').slice(0, 20).map((v) => `${v.root.title}${v.latest && v.latest.price != null ? ` — ${v.latest.price} ${v.latest.currency || ''} as of ${iso(v.latest.observed_at)}` : ''}`);++ // One line a store: its price and day, with the offer's own title only where it differs from the product's++ // (every line began with the same name and an ISO time: 19 of 19 product snippets repeated, 2026-11-02).++ const lines = pv.offers.filter((v) => v.root.status === 'active').slice(0, 20).map((v) => [same(v.root.title, product.name) ? '' : v.root.title, offerLine(v)].filter(Boolean).join(': '));const doc = hooks.buildIndexDocument({owner: 'deals', type: 'product', id: product.id, revision: 0, state: 'published', visibility: 'public',canonicalUrl: publication.abs(publication.productPath(product)), title: product.name,summary: product.description || null,-- body: [product.name, product.brand, ...pv.aliases.map((a) => a.value), ...lines].filter(Boolean).join('\n'),++ body: [product.brand, ...pv.aliases.map((a) => a.value).filter((a) => !same(a, product.name)), ...lines].filter(Boolean).join('\n'),facets: { brand: product.brand || null, category: product.category || null, offers: pv.offers.filter((v) => v.root.status === 'active').length },authorship: { mode: 'human' },decision: pv.decision,@@@ -68,6 +68,11 @@ const { sourceItem } = require('./helpers/mocks');assert.strictEqual(doc.facets.freshness, 'stale');assert.strictEqual(doc.indexability.decision, 'noindex');assert.ok(doc.revision > before[before.length - 1].payload.revision);++ // The document says what the page says once: the title stays out of the body, and an offer without a description++ // is summed up by its price and store (2026-11-02: bodies repeated the title twice, summaries were empty).++ assert.ok(!String(doc.body || '').toLowerCase().includes(String(doc.title).toLowerCase()), `the body does not repeat the title: ${doc.body}`);++ assert.ok(doc.summary, 'a summary');++ assert.ok(!/T\d\d:\d\d/.test(`${doc.summary} ${doc.body || ''}`), 'no ISO timestamps in the text');await t.ctx.worker.tick();assert.strictEqual((await t.events('deals.index_document.upserted')).filter((e) => e.payload.id === offer.id).length, after.length);});@@@ -86,7 +86,9 @@ const { sourceItem } = require('./helpers/mocks');const item = feed.json().items.find((i) => i.url.endsWith(noPrice.slug));assert.match(item.summary, /Price not stated/);const doc = (await t.events('deals.index_document.upserted')).filter((e) => e.payload.id === noPrice.id).pop().payload;-- assert.match(doc.body, /price not stated/);++ // The summary carries the price line when the offer has no description; either way it is "not stated", never 0.++ assert.match(`${doc.summary || ''} ${doc.body || ''}`, /price not stated/i);++ assert.doesNotMatch(`${doc.summary || ''} ${doc.body || ''}`, /\$0\.00|\b0 USD/);assert.strictEqual(doc.facets.currency, undefined);});
@@ -58,20 +58,33 @@ function createIndexing({ config, store, publication, outbox, catalog, indexnow
@@ -58,20 +58,33 @@ function createIndexing({ config, store, publication, outbox, catalog, indexnow
}, { traceparent });
}, { traceparent });
}
}
+ // A result's snippet reads the summary and the body together, so the body holds only what the title and summary do
+ // not: an offer's body repeated its title twice ("FuelCell 796V4 Tennis Shoes\nFuelCell 796V4 Tennis Shoes\n…",
+ // 2026-11-02) and an offer with no description had no summary at all.
+ const same = (a, b) => String(a || '').trim().toLowerCase() === String(b || '').trim().toLowerCase();
+ const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+ const day = (t) => { const d = new Date(t); return Number.isNaN(d.getTime()) ? '' : `${MONTHS[d.getUTCMonth()]} ${d.getUTCDate()}, ${d.getUTCFullYear()}`; };
+ const money = (price, currency) => (currency === 'USD' || !currency ? `$${Number(price).toFixed(2)}` : `${Number(price).toFixed(2)} ${currency}`);
+ function offerLine(v) {
+ const store = v.store && (v.store.name || v.store.domain);
+ if (!v.latest || v.latest.price == null) return `Price not stated${store ? `, at ${store}` : ''}.`;
+ return `${money(v.latest.price, v.latest.currency)}${store ? ` at ${store}` : ''}, as of ${day(v.latest.observed_at)}.`;
+ }
+
function offerDocument(v) {
function offerDocument(v) {
const r = v.root;
const r = v.root;
const provenance = v.sources.filter((s) => s.ref_service === 'sources' && !s.removed_at).slice(0, 40).map((s) => ({
const provenance = v.sources.filter((s) => s.ref_service === 'sources' && !s.removed_at).slice(0, 40).map((s) => ({
service: 'sources', type: 'item', id: s.ref_id, ...(Number.isInteger(s.ref_revision) ? { revision: s.ref_revision } : {}),
service: 'sources', type: 'item', id: s.ref_id, ...(Number.isInteger(s.ref_revision) ? { revision: s.ref_revision } : {}),
url: s.url || undefined, retrievedAt: s.retrieved_at || undefined, label: s.source_key || undefined,
url: s.url || undefined, retrievedAt: s.retrieved_at || undefined, label: s.source_key || undefined,
}));
}));
- const priceText = v.latest && v.latest.price != null ? `${v.latest.price}${v.latest.currency ? ` ${v.latest.currency}` : ''} as of ${iso(v.latest.observed_at)}` : 'price not stated';
return hooks.buildIndexDocument({
return hooks.buildIndexDocument({
owner: 'deals', type: 'offer', id: r.id, revision: 0,
owner: 'deals', type: 'offer', id: r.id, revision: 0,
state: r.status === 'disabled' ? 'unpublished' : 'published', visibility: 'public',
state: r.status === 'disabled' ? 'unpublished' : 'published', visibility: 'public',
canonicalUrl: publication.abs(publication.offerPath(r)),
canonicalUrl: publication.abs(publication.offerPath(r)),
title: r.title,
title: r.title,
- summary: r.description ? r.description.slice(0, 1000) : null,
- body: [r.title, r.description, v.product && v.product.name, v.store && (v.store.name || v.store.domain), priceText].filter(Boolean).join('\n'),
+ summary: r.description ? r.description.slice(0, 1000) : offerLine(v) || null,
+ body: [r.description && r.description.length > 1000 ? r.description : '', r.description ? offerLine(v) : '',
+ v.product && !same(v.product.name, r.title) ? v.product.name : ''].filter(Boolean).join('\n'),
facets: {
facets: {
status: r.status, freshness: v.freshness.state, store: v.store ? v.store.domain : null, category: r.category,
status: r.status, freshness: v.freshness.state, store: v.store ? v.store.domain : null, category: r.category,
currency: v.latest && v.latest.currency ? v.latest.currency : null, product: v.product ? v.product.slug : null,
currency: v.latest && v.latest.currency ? v.latest.currency : null, product: v.product ? v.product.slug : null,
@@ -97,12 +110,14 @@ function createIndexing({ config, store, publication, outbox, catalog, indexnow
@@ -97,12 +110,14 @@ function createIndexing({ config, store, publication, outbox, catalog, indexnow
async function indexProduct(product) {
async function indexProduct(product) {
if (!product) return null;
if (!product) return null;
const pv = await publication.productView(product);
const pv = await publication.productView(product);
- const lines = pv.offers.filter((v) => v.root.status === 'active').slice(0, 20).map((v) => `${v.root.title}${v.latest && v.latest.price != null ? ` — ${v.latest.price} ${v.latest.currency || ''} as of ${iso(v.latest.observed_at)}` : ''}`);
+ // One line a store: its price and day, with the offer's own title only where it differs from the product's
+ // (every line began with the same name and an ISO time: 19 of 19 product snippets repeated, 2026-11-02).
+ const lines = pv.offers.filter((v) => v.root.status === 'active').slice(0, 20).map((v) => [same(v.root.title, product.name) ? '' : v.root.title, offerLine(v)].filter(Boolean).join(': '));
const doc = hooks.buildIndexDocument({
const doc = hooks.buildIndexDocument({
owner: 'deals', type: 'product', id: product.id, revision: 0, state: 'published', visibility: 'public',
owner: 'deals', type: 'product', id: product.id, revision: 0, state: 'published', visibility: 'public',
canonicalUrl: publication.abs(publication.productPath(product)), title: product.name,
canonicalUrl: publication.abs(publication.productPath(product)), title: product.name,
summary: product.description || null,
summary: product.description || null,
- body: [product.name, product.brand, ...pv.aliases.map((a) => a.value), ...lines].filter(Boolean).join('\n'),
+ body: [product.brand, ...pv.aliases.map((a) => a.value).filter((a) => !same(a, product.name)), ...lines].filter(Boolean).join('\n'),
facets: { brand: product.brand || null, category: product.category || null, offers: pv.offers.filter((v) => v.root.status === 'active').length },
facets: { brand: product.brand || null, category: product.category || null, offers: pv.offers.filter((v) => v.root.status === 'active').length },
authorship: { mode: 'human' },
authorship: { mode: 'human' },
decision: pv.decision,
decision: pv.decision,
@@ -68,6 +68,11 @@ const { sourceItem } = require('./helpers/mocks');
@@ -68,6 +68,11 @@ const { sourceItem } = require('./helpers/mocks');
assert.strictEqual(doc.facets.freshness, 'stale');
assert.strictEqual(doc.facets.freshness, 'stale');
assert.strictEqual(doc.indexability.decision, 'noindex');
assert.strictEqual(doc.indexability.decision, 'noindex');
assert.ok(doc.revision > before[before.length - 1].payload.revision);
assert.ok(doc.revision > before[before.length - 1].payload.revision);
+ // The document says what the page says once: the title stays out of the body, and an offer without a description
+ // is summed up by its price and store (2026-11-02: bodies repeated the title twice, summaries were empty).
+ assert.ok(!String(doc.body || '').toLowerCase().includes(String(doc.title).toLowerCase()), `the body does not repeat the title: ${doc.body}`);
+ assert.ok(doc.summary, 'a summary');
+ assert.ok(!/T\d\d:\d\d/.test(`${doc.summary} ${doc.body || ''}`), 'no ISO timestamps in the text');
await t.ctx.worker.tick();
await t.ctx.worker.tick();
assert.strictEqual((await t.events('deals.index_document.upserted')).filter((e) => e.payload.id === offer.id).length, after.length);
assert.strictEqual((await t.events('deals.index_document.upserted')).filter((e) => e.payload.id === offer.id).length, after.length);
});
});
@@ -86,7 +86,9 @@ const { sourceItem } = require('./helpers/mocks');
@@ -86,7 +86,9 @@ const { sourceItem } = require('./helpers/mocks');
const item = feed.json().items.find((i) => i.url.endsWith(noPrice.slug));
const item = feed.json().items.find((i) => i.url.endsWith(noPrice.slug));
assert.match(item.summary, /Price not stated/);
assert.match(item.summary, /Price not stated/);
const doc = (await t.events('deals.index_document.upserted')).filter((e) => e.payload.id === noPrice.id).pop().payload;
const doc = (await t.events('deals.index_document.upserted')).filter((e) => e.payload.id === noPrice.id).pop().payload;
- assert.match(doc.body, /price not stated/);
+ // The summary carries the price line when the offer has no description; either way it is "not stated", never 0.
+ assert.match(`${doc.summary || ''} ${doc.body || ''}`, /price not stated/i);
+ assert.doesNotMatch(`${doc.summary || ''} ${doc.body || ''}`, /\$0\.00|\b0 USD/);
assert.strictEqual(doc.facets.currency, undefined);
assert.strictEqual(doc.facets.currency, undefined);
});
});