groceries: stats page with nightly snapshot (battles, basket, promos, CPI, range)
All checks were successful
Deploy production / deploy (push) Successful in 1m19s
All checks were successful
Deploy production / deploy (push) Successful in 1m19s
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
date,
|
||||
index,
|
||||
integer,
|
||||
jsonb,
|
||||
numeric,
|
||||
real,
|
||||
text,
|
||||
@@ -277,6 +278,51 @@ export const grocerySynonyms = groceriesSchema.table(
|
||||
]
|
||||
);
|
||||
|
||||
/**
|
||||
* Nightly analytics for /trackers/groceries/stats (computeStats in
|
||||
* src/lib/server/groceries/stats.ts, run after import + regroup + taxonomy under the import lock).
|
||||
* One row per day per scope ('food' = edible only, 'all' = incl. drugstore/household). `data` holds
|
||||
* the overall section plus one per top category; `series` (daily chained price index) is only kept
|
||||
* on recent rows, older rows are pruned to the overall section so history stays small.
|
||||
*/
|
||||
export const groceryStatsSnapshots = groceriesSchema.table(
|
||||
'stats_snapshots',
|
||||
{
|
||||
id: id(),
|
||||
computedOn: date('computed_on').notNull(),
|
||||
scope: text('scope').notNull(),
|
||||
computedAt: timestamptz('computed_at').notNull().defaultNow(),
|
||||
durationMs: integer('duration_ms'),
|
||||
data: jsonb('data').notNull(),
|
||||
series: jsonb('series')
|
||||
},
|
||||
(t) => [
|
||||
uniqueIndex('stats_snapshots_day_scope_key').on(t.computedOn, t.scope),
|
||||
check('stats_snapshots_scope_check', sql`${t.scope} IN ('food','all')`)
|
||||
]
|
||||
);
|
||||
|
||||
/**
|
||||
* Pack size history (a row only when an item's size changes), written by computeStats so
|
||||
* shrinkflation (smaller pack, same or higher price) can be detected. item_prices has no sizes.
|
||||
*/
|
||||
export const groceryItemSizes = groceriesSchema.table(
|
||||
'item_sizes',
|
||||
{
|
||||
id: id(),
|
||||
itemId: integer('item_id')
|
||||
.notNull()
|
||||
.references(() => groceryItems.id, { onDelete: 'cascade' }),
|
||||
observedOn: date('observed_on').notNull(),
|
||||
unit: text('unit').$type<GroceryUnit>(),
|
||||
unitAmount: numeric('unit_amount', { precision: 12, scale: 4, mode: 'number' }),
|
||||
sizeText: text('size_text'),
|
||||
priceCents: integer('price_cents'),
|
||||
createdAt: timestamptz('created_at').notNull().defaultNow()
|
||||
},
|
||||
(t) => [uniqueIndex('item_sizes_item_day_key').on(t.itemId, t.observedOn)]
|
||||
);
|
||||
|
||||
export type GroceryStore = typeof groceryStores.$inferSelect;
|
||||
export type GroceryGroup = typeof groceryGroups.$inferSelect;
|
||||
export type GroceryItem = typeof groceryItems.$inferSelect;
|
||||
|
||||
Reference in New Issue
Block a user