2023-06-09 02:03:57 +02:00
|
|
|
const { Model } = require("./model");
|
2023-06-18 23:23:02 +02:00
|
|
|
const { STORE_KEYS } = require("./stores");
|
2023-06-16 16:01:13 +02:00
|
|
|
const { Settings } = require("./settings");
|
2023-06-18 23:23:02 +02:00
|
|
|
const { log, deltaTime } = require("../js/misc");
|
2023-06-08 13:48:08 +02:00
|
|
|
|
2023-06-09 02:03:57 +02:00
|
|
|
class Items extends Model {
|
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
this._items = [];
|
|
|
|
this._filteredItems = [];
|
|
|
|
this._lookup = {};
|
|
|
|
}
|
2023-06-08 13:48:08 +02:00
|
|
|
|
2023-06-09 02:03:57 +02:00
|
|
|
get items() {
|
|
|
|
return this._items;
|
|
|
|
}
|
|
|
|
|
|
|
|
get filteredItems() {
|
|
|
|
return this._filteredItems;
|
2023-06-08 13:48:08 +02:00
|
|
|
}
|
|
|
|
|
2023-06-10 22:34:28 +02:00
|
|
|
set filteredItems(newItems) {
|
|
|
|
this._filteredItems = newItems;
|
|
|
|
this.notify();
|
|
|
|
}
|
|
|
|
|
2023-06-09 02:03:57 +02:00
|
|
|
get lookup() {
|
|
|
|
return this._lookup;
|
|
|
|
}
|
2023-06-08 13:48:08 +02:00
|
|
|
|
2023-06-26 00:03:18 +02:00
|
|
|
async load(progress) {
|
2023-06-16 16:01:13 +02:00
|
|
|
const settings = new Settings();
|
2023-06-18 23:23:02 +02:00
|
|
|
let start = performance.now();
|
|
|
|
const compressedItemsPerStore = [];
|
2023-06-25 00:07:27 +02:00
|
|
|
|
2023-06-18 23:23:02 +02:00
|
|
|
for (const store of STORE_KEYS) {
|
|
|
|
compressedItemsPerStore.push(
|
|
|
|
new Promise(async (resolve) => {
|
|
|
|
let start = performance.now();
|
|
|
|
try {
|
|
|
|
const response = await fetch(`data/latest-canonical.${store}.compressed.json`);
|
|
|
|
const json = await response.json();
|
|
|
|
log(`Loader - loading compressed items for ${store} took ${deltaTime(start)} secs`);
|
|
|
|
start = performance.now();
|
2023-06-21 18:38:09 +02:00
|
|
|
let items = exports.decompress(json);
|
2023-06-18 23:23:02 +02:00
|
|
|
log(`Loader - Decompressing items for ${store} took ${deltaTime(start)} secs`);
|
|
|
|
resolve(items);
|
|
|
|
} catch (e) {
|
|
|
|
log(`Loader - error while loading compressed items for ${store} ${e.message}`);
|
|
|
|
resolve([]);
|
|
|
|
}
|
2023-06-26 00:03:18 +02:00
|
|
|
if (progress) progress();
|
2023-06-18 23:23:02 +02:00
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
2023-06-25 00:07:27 +02:00
|
|
|
|
2023-06-18 23:23:02 +02:00
|
|
|
let items = [].concat(...(await Promise.all(compressedItemsPerStore)));
|
|
|
|
log(`Loader - loaded ${items.length} items took ${deltaTime(start).toFixed(4)} secs`);
|
|
|
|
|
|
|
|
const result = this.processItems(items);
|
|
|
|
log(`Loader - total loading took ${deltaTime(start).toFixed(4)} secs`);
|
|
|
|
|
2023-07-12 12:54:59 +02:00
|
|
|
this._items = result.items.filter((item) => item.priceHistory.length > 0);
|
2023-06-18 23:23:02 +02:00
|
|
|
this._lookup = result.lookup;
|
|
|
|
}
|
|
|
|
|
|
|
|
processItems(items) {
|
|
|
|
const lookup = {};
|
|
|
|
const start = performance.now();
|
|
|
|
const interns = new Map();
|
|
|
|
const intern = (value) => {
|
|
|
|
if (interns.has(value)) {
|
|
|
|
return interns.get(value);
|
|
|
|
} else {
|
|
|
|
interns.set(value, value);
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const getters = {
|
|
|
|
unitPrice: {
|
|
|
|
get() {
|
|
|
|
const unitPriceFactor = this.unit == "g" || this.unit == "ml" ? 1000 : 1;
|
|
|
|
return (this.price / this.quantity) * unitPriceFactor;
|
|
|
|
},
|
|
|
|
},
|
|
|
|
numPrices: {
|
|
|
|
get() {
|
|
|
|
return this.priceHistory.length;
|
|
|
|
},
|
|
|
|
},
|
|
|
|
date: {
|
|
|
|
get() {
|
|
|
|
return this.priceHistory[0].date;
|
|
|
|
},
|
|
|
|
},
|
|
|
|
priceOldest: {
|
|
|
|
get() {
|
|
|
|
return this.priceHistory[this.priceHistory.length - 1].price;
|
|
|
|
},
|
|
|
|
},
|
|
|
|
dateOldest: {
|
|
|
|
get() {
|
|
|
|
return this.priceHistory[this.priceHistory.length - 1].date;
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2023-07-12 12:35:58 +02:00
|
|
|
for (let i = 1; i <= 3; i++) {
|
2023-06-18 23:23:02 +02:00
|
|
|
(getters[`price${i}`] = {
|
|
|
|
get() {
|
|
|
|
return this.priceHistory[i] ? this.priceHistory[i].price : 0;
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
(getters[`date${i}`] = {
|
|
|
|
get() {
|
|
|
|
return this.priceHistory[i] ? this.priceHistory[i].date : null;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
items.forEach((item) => {
|
|
|
|
lookup[item.store + item.id] = item;
|
|
|
|
for (const getter in getters) {
|
|
|
|
Object.defineProperty(item, getter, getters[getter]);
|
|
|
|
}
|
|
|
|
|
|
|
|
item.store = intern(item.store);
|
|
|
|
item.id = intern(item.id);
|
|
|
|
item.name = intern(item.name);
|
|
|
|
item.category = intern(item.category);
|
2023-07-11 15:36:40 +02:00
|
|
|
item.price = item.price;
|
2023-06-18 23:23:02 +02:00
|
|
|
for (const price of item.priceHistory) {
|
|
|
|
price.date = intern(price.date);
|
2023-07-11 15:36:40 +02:00
|
|
|
price.price = price.price;
|
2023-06-18 23:23:02 +02:00
|
|
|
}
|
2023-07-11 15:36:40 +02:00
|
|
|
item.priceHistory = item.priceHistory.filter((price) => price.price > 0);
|
2023-06-18 23:23:02 +02:00
|
|
|
item.unit = intern(item.unit);
|
2023-07-11 15:36:40 +02:00
|
|
|
item.quantity = item.quantity;
|
2023-06-18 23:23:02 +02:00
|
|
|
|
|
|
|
item.search = item.name + " " + item.quantity + " " + item.unit;
|
|
|
|
item.search = intern(item.search.toLowerCase().replace(",", "."));
|
|
|
|
|
|
|
|
const unitPriceFactor = item.unit == "g" || item.unit == "ml" ? 1000 : 1;
|
|
|
|
for (let i = 0; i < item.priceHistory.length; i++) {
|
|
|
|
const price = item.priceHistory[i];
|
|
|
|
price.unitPrice = (price.price / item.quantity) * unitPriceFactor;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
items.sort((a, b) => {
|
|
|
|
if (a.store < b.store) {
|
|
|
|
return -1;
|
|
|
|
} else if (a.store > b.store) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (a.name < b.name) {
|
|
|
|
return -1;
|
|
|
|
} else if (a.name > b.name) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
});
|
|
|
|
|
|
|
|
log(`Loader - processing ${items.length} items took ${deltaTime(start).toFixed(4)} secs`);
|
|
|
|
return { items, lookup };
|
|
|
|
}
|
2023-06-21 18:25:21 +02:00
|
|
|
}
|
2023-06-18 23:23:02 +02:00
|
|
|
|
2023-06-21 18:25:21 +02:00
|
|
|
exports.Items = Items;
|
|
|
|
|
|
|
|
exports.decompress = (compressedItems) => {
|
|
|
|
const storeLookup = compressedItems.stores;
|
|
|
|
const data = compressedItems.data;
|
|
|
|
const dates = compressedItems.dates;
|
|
|
|
const numItems = compressedItems.n;
|
|
|
|
const items = new Array(numItems);
|
|
|
|
let i = 0;
|
|
|
|
for (let l = 0; l < numItems; l++) {
|
|
|
|
const store = storeLookup[data[i++]];
|
|
|
|
const id = data[i++];
|
2023-06-29 17:21:29 +02:00
|
|
|
const name = data[i++].replace("M & M", "M&M");
|
2023-06-21 18:25:21 +02:00
|
|
|
const category = data[i++];
|
|
|
|
const unavailable = data[i++] == 1;
|
|
|
|
const numPrices = data[i++];
|
|
|
|
const prices = new Array(numPrices);
|
|
|
|
for (let j = 0; j < numPrices; j++) {
|
|
|
|
const date = dates[data[i++]];
|
|
|
|
const price = data[i++];
|
|
|
|
prices[j] = {
|
|
|
|
date: date.substring(0, 4) + "-" + date.substring(4, 6) + "-" + date.substring(6, 8),
|
|
|
|
price,
|
2023-06-18 23:23:02 +02:00
|
|
|
};
|
2023-06-12 10:32:42 +02:00
|
|
|
}
|
2023-06-21 18:25:21 +02:00
|
|
|
const unit = data[i++];
|
|
|
|
const quantity = data[i++];
|
|
|
|
const isWeighted = data[i++] == 1;
|
|
|
|
const bio = data[i++] == 1;
|
|
|
|
const url = data[i++];
|
|
|
|
|
|
|
|
items[l] = {
|
|
|
|
store,
|
|
|
|
id,
|
|
|
|
name,
|
|
|
|
category,
|
|
|
|
unavailable,
|
|
|
|
price: prices[0].price,
|
|
|
|
priceHistory: prices,
|
|
|
|
isWeighted,
|
|
|
|
unit,
|
|
|
|
quantity,
|
|
|
|
bio,
|
|
|
|
url,
|
|
|
|
};
|
2023-06-12 10:32:42 +02:00
|
|
|
}
|
2023-06-21 18:25:21 +02:00
|
|
|
return items;
|
|
|
|
};
|