mirror of
https://github.com/samsonjs/spirit-tracker.git
synced 2026-03-25 09:25:51 +00:00
UX Improvements
This commit is contained in:
parent
23dbd34006
commit
340f81aa65
1 changed files with 32 additions and 4 deletions
|
|
@ -47,6 +47,31 @@ function isBcStoreLabel(label) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function weightedMeanByDuration(pointsMap, sortedDates) {
|
||||||
|
// pointsMap: Map(dateStr -> number|null)
|
||||||
|
// sortedDates: ["YYYY-MM-DD", ...] sorted asc
|
||||||
|
let wsum = 0;
|
||||||
|
let wtot = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < sortedDates.length; i++) {
|
||||||
|
const d0 = sortedDates[i];
|
||||||
|
const v = pointsMap.get(d0);
|
||||||
|
if (!Number.isFinite(v)) continue;
|
||||||
|
|
||||||
|
const t0 = Date.parse(d0 + "T00:00:00Z");
|
||||||
|
const d1 = sortedDates[i + 1];
|
||||||
|
const t1 = d1 ? Date.parse(d1 + "T00:00:00Z") : t0 + 24 * 3600 * 1000;
|
||||||
|
|
||||||
|
// weight in days (min 1 day)
|
||||||
|
const w = Math.max(1, Math.round((t1 - t0) / (24 * 3600 * 1000)));
|
||||||
|
wsum += v * w;
|
||||||
|
wtot += w;
|
||||||
|
}
|
||||||
|
|
||||||
|
return wtot ? wsum / wtot : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function meanFinite(arr) {
|
function meanFinite(arr) {
|
||||||
if (!Array.isArray(arr)) return null;
|
if (!Array.isArray(arr)) return null;
|
||||||
let sum = 0,
|
let sum = 0,
|
||||||
|
|
@ -879,8 +904,11 @@ export async function renderItem($app, skuInput) {
|
||||||
// --- Compute marker values ---
|
// --- Compute marker values ---
|
||||||
// Province medians: per-store mean over time, then median across stores (>=3 stores)
|
// Province medians: per-store mean over time, then median across stores (>=3 stores)
|
||||||
const storeMeans = seriesSorted
|
const storeMeans = seriesSorted
|
||||||
.map((s) => ({ label: s.label, mean: meanFinite(s.values) }))
|
.map((s) => ({
|
||||||
.filter((x) => Number.isFinite(x.mean));
|
label: s.label,
|
||||||
|
mean: weightedMeanByDuration(s.points, labels), // duration-weighted
|
||||||
|
}))
|
||||||
|
.filter((x) => Number.isFinite(x.mean));
|
||||||
|
|
||||||
const bcMeans = storeMeans.filter((x) => isBcStoreLabel(x.label));
|
const bcMeans = storeMeans.filter((x) => isBcStoreLabel(x.label));
|
||||||
const abMeans = storeMeans.filter((x) => !isBcStoreLabel(x.label));
|
const abMeans = storeMeans.filter((x) => !isBcStoreLabel(x.label));
|
||||||
|
|
@ -889,12 +917,12 @@ export async function renderItem($app, skuInput) {
|
||||||
|
|
||||||
if (bcMeans.length >= 3) {
|
if (bcMeans.length >= 3) {
|
||||||
const y = medianFinite(bcMeans.map((x) => x.mean));
|
const y = medianFinite(bcMeans.map((x) => x.mean));
|
||||||
if (Number.isFinite(y)) markers.push({ y: Math.round(y), text: "BC Median" });
|
if (Number.isFinite(y)) markers.push({ y: Math.round(y), text: "BC" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (abMeans.length >= 3) {
|
if (abMeans.length >= 3) {
|
||||||
const y = medianFinite(abMeans.map((x) => x.mean));
|
const y = medianFinite(abMeans.map((x) => x.mean));
|
||||||
if (Number.isFinite(y)) markers.push({ y: Math.round(y), text: "AB Median" });
|
if (Number.isFinite(y)) markers.push({ y: Math.round(y), text: "Alberta" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Target price: pick 3 lowest per-store mins (distinct stores), then average (>=3 stores)
|
// Target price: pick 3 lowest per-store mins (distinct stores), then average (>=3 stores)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue