mirror of
https://github.com/samsonjs/spirit-tracker.git
synced 2026-04-27 15:07:43 +00:00
feat: Better sale priorities
This commit is contained in:
parent
a91406ba11
commit
7e6fa42c53
1 changed files with 46 additions and 29 deletions
|
|
@ -164,7 +164,7 @@ export function renderSearch($app) {
|
||||||
|
|
||||||
const normStore = (s) => String(s || "").trim().toLowerCase();
|
const normStore = (s) => String(s || "").trim().toLowerCase();
|
||||||
|
|
||||||
// Treat "price_change" as down/up if we can infer direction
|
// Normalize kind
|
||||||
let kind = String(r?.kind || "");
|
let kind = String(r?.kind || "");
|
||||||
if (kind === "price_change") {
|
if (kind === "price_change") {
|
||||||
const o = parsePriceToNumber(r?.oldPrice || "");
|
const o = parsePriceToNumber(r?.oldPrice || "");
|
||||||
|
|
@ -182,7 +182,7 @@ export function renderSearch($app) {
|
||||||
const storeCount = agg?.stores?.size || 0;
|
const storeCount = agg?.stores?.size || 0;
|
||||||
const isNewUnique = isNew && storeCount <= 1;
|
const isNewUnique = isNew && storeCount <= 1;
|
||||||
|
|
||||||
// For sales: demote if this store is NOT the cheapest available now (per aggregate index)
|
// Cheapest checks (use aggregate index)
|
||||||
const newPriceNum = kind === "price_down" || kind === "price_up" ? parsePriceToNumber(r?.newPrice || "") : null;
|
const newPriceNum = kind === "price_down" || kind === "price_up" ? parsePriceToNumber(r?.newPrice || "") : null;
|
||||||
const bestPriceNum = Number.isFinite(agg?.cheapestPriceNum) ? agg.cheapestPriceNum : null;
|
const bestPriceNum = Number.isFinite(agg?.cheapestPriceNum) ? agg.cheapestPriceNum : null;
|
||||||
|
|
||||||
|
|
@ -190,35 +190,51 @@ export function renderSearch($app) {
|
||||||
const priceMatchesBest =
|
const priceMatchesBest =
|
||||||
Number.isFinite(newPriceNum) && Number.isFinite(bestPriceNum) ? Math.abs(newPriceNum - bestPriceNum) <= EPS : false;
|
Number.isFinite(newPriceNum) && Number.isFinite(bestPriceNum) ? Math.abs(newPriceNum - bestPriceNum) <= EPS : false;
|
||||||
|
|
||||||
const storeIsBest = normStore(storeLabelRaw) && normStore(bestStoreRaw) && normStore(storeLabelRaw) === normStore(bestStoreRaw);
|
const storeIsBest =
|
||||||
|
normStore(storeLabelRaw) && normStore(bestStoreRaw) && normStore(storeLabelRaw) === normStore(bestStoreRaw);
|
||||||
|
|
||||||
const saleIsCheapestHere = kind === "price_down" && storeIsBest && priceMatchesBest;
|
const saleIsCheapestHere = kind === "price_down" && storeIsBest && priceMatchesBest;
|
||||||
const saleIsTiedCheapest = kind === "price_down" && !storeIsBest && priceMatchesBest;
|
const saleIsTiedCheapest = kind === "price_down" && !storeIsBest && priceMatchesBest;
|
||||||
|
const saleIsCheapest = saleIsCheapestHere || saleIsTiedCheapest;
|
||||||
|
|
||||||
|
// Bucketed scoring (higher = earlier)
|
||||||
let score = 0;
|
let score = 0;
|
||||||
|
|
||||||
if (kind === "price_down") {
|
// Helper for sales buckets
|
||||||
if (saleIsCheapestHere) {
|
function saleBucketScore(isCheapest, pct) {
|
||||||
score = 6500 + (pctOff || 0);
|
const p = Number.isFinite(pct) ? pct : 0;
|
||||||
} else if (saleIsTiedCheapest) {
|
|
||||||
score = 5900 + Math.floor((pctOff || 0) * 0.5);
|
if (isCheapest) {
|
||||||
|
if (p >= 20) return 9000 + p; // Bucket #1
|
||||||
|
if (p >= 10) return 7000 + p; // Bucket #3
|
||||||
|
if (p > 0) return 6000 + p; // Bucket #4
|
||||||
|
return 5900; // weird but keep below real pct
|
||||||
} else {
|
} else {
|
||||||
score = 2400 + Math.min(25, Math.max(0, pctOff || 0));
|
if (p >= 20) return 4500 + p; // Bucket #5 (below NEW unique)
|
||||||
|
if (p >= 10) return 1500 + p; // Bucket #8
|
||||||
|
if (p > 0) return 1200 + p; // Bucket #9
|
||||||
|
return 1000; // bottom-ish
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kind === "price_down") {
|
||||||
|
score = saleBucketScore(saleIsCheapest, pctOff);
|
||||||
} else if (isNewUnique) {
|
} else if (isNewUnique) {
|
||||||
score = 6000;
|
score = 8000; // Bucket #2
|
||||||
} else if (kind === "restored") {
|
|
||||||
score = 5200;
|
|
||||||
} else if (kind === "removed") {
|
} else if (kind === "removed") {
|
||||||
score = 3000;
|
score = 3000; // Bucket #6
|
||||||
} else if (kind === "price_up") {
|
} else if (kind === "price_up") {
|
||||||
score = 2000 + Math.min(99, Math.max(0, pctUp || 0));
|
score = 2000 + Math.min(99, Math.max(0, pctUp || 0)); // Bucket #7
|
||||||
} else if (kind === "new") {
|
} else if (kind === "new") {
|
||||||
score = 1000;
|
score = 1100; // Bucket #10
|
||||||
|
} else if (kind === "restored") {
|
||||||
|
// not in your bucket list, but keep it reasonably high (below NEW unique, above removals)
|
||||||
|
score = 5000;
|
||||||
} else {
|
} else {
|
||||||
score = 0;
|
score = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tie-breaks: within bucket prefer bigger % for sales, then recency
|
||||||
let tie = 0;
|
let tie = 0;
|
||||||
if (kind === "price_down") tie = (pctOff || 0) * 100000 + tsValue(r);
|
if (kind === "price_down") tie = (pctOff || 0) * 100000 + tsValue(r);
|
||||||
else if (kind === "price_up") tie = (pctUp || 0) * 100000 + tsValue(r);
|
else if (kind === "price_up") tie = (pctUp || 0) * 100000 + tsValue(r);
|
||||||
|
|
@ -227,6 +243,7 @@ export function renderSearch($app) {
|
||||||
return { sku, kind, pctOff, storeCount, isNewUnique, score, tie };
|
return { sku, kind, pctOff, storeCount, isNewUnique, score, tie };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function renderRecent(recent, canonicalSkuFn) {
|
function renderRecent(recent, canonicalSkuFn) {
|
||||||
const items = Array.isArray(recent?.items) ? recent.items : [];
|
const items = Array.isArray(recent?.items) ? recent.items : [];
|
||||||
if (!items.length) {
|
if (!items.length) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue