groceries stats: fix array/json params for the app client (live store filter 500), CPI axis, header spacing
All checks were successful
Deploy production / deploy (push) Successful in 1m11s
All checks were successful
Deploy production / deploy (push) Successful in 1m11s
This commit is contained in:
@@ -41,6 +41,8 @@ type Tx = TransactionSql;
|
|||||||
const ALL = 'all';
|
const ALL = 'all';
|
||||||
// Leaves under food tops that are not edible (the taxonomy flags them food via their top).
|
// Leaves under food tops that are not edible (the taxonomy flags them food via their top).
|
||||||
const NON_FOOD_LEAVES = ['baby-kind--luiers', 'baby-kind--verzorging', 'baby-kind--accessoires'];
|
const NON_FOOD_LEAVES = ['baby-kind--luiers', 'baby-kind--verzorging', 'baby-kind--accessoires'];
|
||||||
|
// Passed as an array literal: the app's postgres client doesn't serialise JS arrays here.
|
||||||
|
const NON_FOOD_ARR = `{${NON_FOOD_LEAVES.join(',')}}`;
|
||||||
|
|
||||||
// Own (private-label) brands per store, as a regex on lower(brand). Superunie labels are shared
|
// Own (private-label) brands per store, as a regex on lower(brand). Superunie labels are shared
|
||||||
// by its members (Dirk, Hoogvliet, Vomar, PLUS). `unbranded`: items without a brand are the
|
// by its members (Dirk, Hoogvliet, Vomar, PLUS). `unbranded`: items without a brand are the
|
||||||
@@ -327,10 +329,10 @@ async function prepare(tx: Tx, scope: StatsScope, stores: number[] | null) {
|
|||||||
left join groceries.groups g on g.id = i.group_id
|
left join groceries.groups g on g.id = i.group_id
|
||||||
left join groceries.categories c on c.id = i.category_id
|
left join groceries.categories c on c.id = i.category_id
|
||||||
left join groceries.categories tc on tc.id = coalesce(c.parent_id, c.id)
|
left join groceries.categories tc on tc.id = coalesce(c.parent_id, c.id)
|
||||||
left join json_to_recordset(${tx.json(OWN_BRANDS as never)}::json)
|
left join json_to_recordset(${JSON.stringify(OWN_BRANDS)}::text::json)
|
||||||
as ob(slug text, rx text, unbranded boolean) on ob.slug = s.slug
|
as ob(slug text, rx text, unbranded boolean) on ob.slug = s.slug
|
||||||
where i.available and i.price_cents > 0 and i.unit_price_cents > 0 and i.unit is not null
|
where i.available and i.price_cents > 0 and i.unit_price_cents > 0 and i.unit is not null
|
||||||
and (${scope} = 'all' or (i.is_food and coalesce(c.slug, '') <> all(${NON_FOOD_LEAVES})
|
and (${scope} = 'all' or (i.is_food and coalesce(c.slug, '') <> all(${NON_FOOD_ARR}::text[])
|
||||||
and i.name !~* 'e-?gift|cadeaukaart|giftcard|gift card'))
|
and i.name !~* 'e-?gift|cadeaukaart|giftcard|gift card'))
|
||||||
and (${storeArr}::int[] is null or i.store_id = any(${storeArr}::int[]))
|
and (${storeArr}::int[] is null or i.store_id = any(${storeArr}::int[]))
|
||||||
and i.unit_price_cents >= case i.unit when 'piece' then 1 else 10 end
|
and i.unit_price_cents >= case i.unit when 'piece' then 1 else 10 end
|
||||||
@@ -717,7 +719,7 @@ async function history(tx: Tx, scope: StatsScope, stores: number[] | null) {
|
|||||||
join groceries.stores s on s.id = i.store_id and s.archived_at is null
|
join groceries.stores s on s.id = i.store_id and s.archived_at is null
|
||||||
left join groceries.categories c on c.id = i.category_id
|
left join groceries.categories c on c.id = i.category_id
|
||||||
left join groceries.categories tc on tc.id = coalesce(c.parent_id, c.id)
|
left join groceries.categories tc on tc.id = coalesce(c.parent_id, c.id)
|
||||||
where (${scope} = 'all' or (i.is_food and coalesce(c.slug, '') <> all(${NON_FOOD_LEAVES})
|
where (${scope} = 'all' or (i.is_food and coalesce(c.slug, '') <> all(${NON_FOOD_ARR}::text[])
|
||||||
and i.name !~* 'e-?gift|cadeaukaart|giftcard|gift card'))
|
and i.name !~* 'e-?gift|cadeaukaart|giftcard|gift card'))
|
||||||
and (${storeArr}::int[] is null or i.store_id = any(${storeArr}::int[]))`;
|
and (${storeArr}::int[] is null or i.store_id = any(${storeArr}::int[]))`;
|
||||||
await tx`create unique index on it (id)`;
|
await tx`create unique index on it (id)`;
|
||||||
@@ -904,7 +906,7 @@ async function history(tx: Tx, scope: StatsScope, stores: number[] | null) {
|
|||||||
async function basket(tx: Tx) {
|
async function basket(tx: Tx) {
|
||||||
const rows = await tx`
|
const rows = await tx`
|
||||||
with st as (
|
with st as (
|
||||||
select * from json_to_recordset(${tx.json(STAPLES as never)}::json)
|
select * from json_to_recordset(${JSON.stringify(STAPLES)}::text::json)
|
||||||
as x(key text, rx text, nrx text, units text, qty float8, cats text, min float8, max float8,
|
as x(key text, rx text, nrx text, units text, qty float8, cats text, min float8, max float8,
|
||||||
"pieceKg" float8)
|
"pieceKg" float8)
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -394,7 +394,12 @@
|
|||||||
tooltip: { trigger: 'axis', valueFormatter: (v: number) => (v == null ? '–' : v.toFixed(2)) },
|
tooltip: { trigger: 'axis', valueFormatter: (v: number) => (v == null ? '–' : v.toFixed(2)) },
|
||||||
legend: legend(t),
|
legend: legend(t),
|
||||||
xAxis: { type: 'category', data: (s?.days ?? []).map(fmtDay), axisLabel: { color: t.muted } },
|
xAxis: { type: 'category', data: (s?.days ?? []).map(fmtDay), axisLabel: { color: t.muted } },
|
||||||
yAxis: axisVal(t),
|
yAxis: {
|
||||||
|
...axisVal(t),
|
||||||
|
scale: true,
|
||||||
|
minInterval: 0.1,
|
||||||
|
axisLabel: { color: t.muted, formatter: (v: number) => v.toFixed(1) }
|
||||||
|
},
|
||||||
series: ids.map((k) => ({
|
series: ids.map((k) => ({
|
||||||
name: sname(Number(k)),
|
name: sname(Number(k)),
|
||||||
type: 'line',
|
type: 'line',
|
||||||
@@ -588,8 +593,8 @@
|
|||||||
<p class="mt-2 flex flex-wrap items-center gap-1.5 text-sm text-[var(--site-muted)]">
|
<p class="mt-2 flex flex-wrap items-center gap-1.5 text-sm text-[var(--site-muted)]">
|
||||||
<Lock size={14} aria-hidden="true" />
|
<Lock size={14} aria-hidden="true" />
|
||||||
{#if ready && meta}
|
{#if ready && meta}
|
||||||
Snapshot of {fmtDay(ready.day)} · {historyDays} day{historyDays === 1 ? '' : 's'} of price history{#if ready.durationMs}
|
Snapshot of {fmtDay(ready.day)} · {historyDays} day{historyDays === 1 ? '' : 's'} of price history
|
||||||
· computed in {(ready.durationMs / 1000).toFixed(1)} s{/if}
|
{#if ready.durationMs}· computed in {(ready.durationMs / 1000).toFixed(1)} s{/if}
|
||||||
{:else}Who's cheapest, how often, and by how much.{/if}
|
{:else}Who's cheapest, how often, and by how much.{/if}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user