This commit is contained in:
Mario Zechner 2023-05-27 12:33:18 +02:00
parent 42b89fa325
commit ce8e0fb102
11 changed files with 234 additions and 49 deletions

4
.gitignore vendored
View File

@ -6,3 +6,7 @@ dist
heissepreise.zip
site/latest-canonical.json
latest-canonical.json
access.log
billa-2020.csv
report.html
spar-2020.csv

9
package-lock.json generated
View File

@ -12,6 +12,7 @@
"axios": "^1.4.0",
"compression": "^1.7.4",
"express": "^4.18.2",
"n-readlines": "^1.0.1",
"nodemon": "^2.0.22"
},
"bin": {
@ -1441,6 +1442,14 @@
"node": ">= 6"
}
},
"node_modules/n-readlines": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/n-readlines/-/n-readlines-1.0.1.tgz",
"integrity": "sha512-z4SyAIVgMy7CkgsoNw7YVz40v0g4+WWvvqy8+ZdHrCtgevcEO758WQyrYcw3XPxcLxF+//RszTz/rO48nzD0wQ==",
"engines": {
"node": ">=6.x.x"
}
},
"node_modules/napi-build-utils": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",

View File

@ -20,6 +20,7 @@
"axios": "^1.4.0",
"compression": "^1.7.4",
"express": "^4.18.2",
"n-readlines": "^1.0.1",
"nodemon": "^2.0.22"
},
"devDependencies": {

View File

@ -11,12 +11,12 @@
<body>
<div class="column">
<h2>Heisse Preise</h2>
<div class="filters">
<a href="index.html">Produktsuche</a>
<a href="changes.html">Tagespreisänderungen</a>
<a href="carts.html">Warenkörbe</a>
</div>
<h2>Heisse Preise</h2>
<div id="cart" class="cart">
<h3 id="cartname"></h3>
<canvas id="chart"></canvas>

View File

@ -11,15 +11,13 @@
<body>
<div class="column">
<h2>Heisse Preise</h2>
<div class="filters">
<a href="index.html">Produktsuche</a>
<a href="changes.html">Tagespreisänderungen</a>
<a href="carts.html"><strong>Warenkörbe</strong></a>
</div>
<h2>Heisse Preise</h2>
<div class="filters">
<button id="newcart">Neuer Warenkorb</button>
</div>
<button id="newcart">Neuer Warenkorb</button>
<table id="carts"></table>
<table id="result"></table>
</div>

View File

@ -11,12 +11,12 @@
<body>
<div class="column">
<h2>Heisse Preise</h2>
<div class="filters">
<a href="index.html">Produktsuche</a>
<a href="changes.html"><strong>Tagespreisänderungen</strong></a>
<a href="carts.html">Warenkörbe</a>
</div>
<h2>Heisse Preise</h2>
<div class="filters">
<label>Preisänderungen am <select id="dates"></select></label>
</div>

View File

@ -11,12 +11,12 @@
<body>
<div class="column" style="max-width: 1000px">
<h2>Heisse Preise</h2>
<div class="filters">
<a href="index.html"><strong>Produktsuche</strong></a>
<a href="changes.html">Tagespreisänderungen</a>
<a href="carts.html">Warenkörbe</a>
</div>
<h2>Heisse Preise</h2>
<canvas id="chart" style="display: none;"></canvas>
<div id="search" class="column">
</div>

View File

@ -1,11 +1,17 @@
* {
box-sizing: border-box;
box-sizing: border-box;
}
body {
margin: 0;
padding: 1em;
font-family: sans-serif;
font-family: Helvetica;
background: #f5f5f5;
color: #393939;
}
a {
color: #c9543a;
}
.column {
@ -15,6 +21,7 @@ body {
align-items: center;
margin: 0 auto;
width: 100%;
max-width: 1000px;
}
.search {
@ -28,33 +35,97 @@ body {
margin-bottom: 1em;
}
input {
border: 2px solid;
padding: 0.4em;
border-color: #ee907b;
accent-color: #c9543a;
}
input[type="number"] {
max-width: 3em;
max-width: 6em;
border: 2px solid;
padding: 0.4em;
border-color: #ee907b;
}
h2 {
background-color: #c9543a;
color: white;
width: 100%;
text-transform: uppercase;
text-align: center;
padding: 0.5em;
margin-top: 0;
}
table {
border-collapse: collapse;
border: 1px solid;
border: 0px;
margin-top: 1em;
}
th {
border: 1px solid;
padding: 0.2em;
background: #aaa;
text-align: left;
background: #c9543a;
color: white;
border: 0px;
}
th:nth-child(1) {
text-align: center;
}
th:nth-child(3) {
text-align: right;
padding: 0.7em;
}
th:nth-child(4) {
text-align: center;
padding: 0.6em;
}
tr {
border-collapse: collapse;
border: 1px solid #ddd;
border-left: 0px;
border-right: 0px;
}
td {
padding: 0.2em;
}
th {
text-align: left;
td a {
color: #393939;
text-decoration: none;
}
td:nth-child(1) {
text-align: center;
padding: 0.5em;
}
td:nth-child(2) {
background-color: white;
padding: 0.4em;
}
td:nth-child(3) {
background-color: white;
font-size: small;
text-align: right;
padding-right: 0.7em;
}
td:nth-child(4) {
padding-left: 0.5em;
}
td:nth-child(4):before {
content: "€ "
}
.itemname {
@ -88,51 +159,67 @@ th {
@media screen and (max-width: 600px) {
table {
border: 0;
border: 0;
}
table thead {
border: none;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
border: none;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
table tr {
display: block;
margin-bottom: .625em;
display: block;
margin-bottom: .625em;
}
table td {
border-bottom: 1px solid #ddd;
padding-left: calc(65px + 1.2em);
position: relative;
display: block;
border-bottom: 1px solid #ddd;
padding: 0 0 0 0 !important;
padding-left: calc(65px + 1.2em) !important;
position: relative;
display: block;
}
table td::before {
/*
* aria-label has no advantage, it won't be read inside a table
content: attr(aria-label);
*/
content: attr(data-label);
background-color: #aaa;
border: 1px solid;
position: absolute;
padding: .2em .4em;
width: 65px;
height: calc(100% - .4em);
left: -1px;
top: -1px;
text-align: right;
font-weight: bold;
/*
* aria-label has no advantage, it won't be read inside a table
content: attr(aria-label);
*/
content: attr(data-label);
background-color: #c9543a;
color: white;
border: 0px;
position: absolute;
padding: .1em .4em;
width: 65px;
height: calc(100%);
left: -1px;
top: -1px;
text-align: right;
font-weight: bold;
font-size: medium;
}
td:nth-child(1) {
text-align: center;
font-size: medium;
padding: 0.5em;
}
td:nth-child(3) {
background-color: white;
font-size: medium;
text-align: left;
padding-right: 0.7em;
}
table td:last-child {
border-bottom: 0;
border-bottom: 0;
}
}
}

View File

@ -375,7 +375,15 @@ function showChart(canvasDom, items) {
},
options: {
responsive: true,
aspectRation: 16 / 9
aspectRation: 16 / 9,
scales: {
y: {
title: {
display: true,
text: "EURO"
}
}
}
}
});
}

View File

@ -14,6 +14,7 @@ exports.getCanonical = function(item, today) {
}
return {
id: item.masterValues["code-internal"],
sparId: item.masterValues["product-number"],
name: item.masterValues.title + " " + item.masterValues["short-description"],
price,
priceHistory: [{ date: today, price }],

View File

@ -150,4 +150,81 @@ function fixSparHistoricalData(dataDir) {
}
}
// momentumCartConversion();
const nReadlines = require('n-readlines');
function convertDossierData(dataDir, file) {
console.log(`Converting ${file}`);
const lookup = {};
for (item of JSON.parse(fs.readFileSync(`${dataDir}/latest-canonical.json`))) {
lookup[item.store + item.id] = item;
if (item.sparId)
lookup[item.store + "-" + item.sparId] = item;
}
const lines = new nReadlines(file);
const itemsPerDate = {};
let line = null;
const store = file.indexOf("spar") == 0 ? "spar" : "billa";
lines.next()
let itemsTotal = 0;
let notFound = 0;
while(line = lines.next()) {
itemsTotal++;
const tokens = line.toString("utf-8").split(";");
const dateTokens = tokens[0].split(".");
const date = "20" + dateTokens[2] + "-" + dateTokens[1] + "-" + dateTokens[0];
const producer = tokens[5];
const name = tokens[3];
const unit = tokens[6];
const price = Number.parseFloat(tokens[7].replace("€", "").trim().replace(",", "."));
const id = tokens[4].replace("ARTIKELNUMMER: ", "").replace("Art. Nr.: ", "");
let item = lookup[store + id];
if (!item)
item = lookup[store + "-" + id]
if (!item) {
// console.log("Couldn't find item " + name);
notFound++;
continue;
}
let items = itemsPerDate[date];
if (!items) itemsPerDate[date] = items = [];
if (store == "spar") {
items.push({
masterValues: {
"code-internal": item.id,
"product-number": id,
price,
title: producer,
"short-description": name,
"short-description-3": unit,
bioLevel: ""
}
});
} else {
items.push({
data: {
articleId: id,
name: name,
price: {
final: price
},
grammagePriceFactor: 1,
grammage: unit,
}
})
}
}
console.log("total: " + itemsTotal);
console.log("not found: " + notFound);
const dates = Object.keys(itemsPerDate).sort((a, b) => b.localeCompare(a));
for (date of dates) {
fs.writeFileSync(`${dataDir}/${store}-${date}.json`, JSON.stringify(itemsPerDate[date], null, 2));
}
console.log(`Wrote files for ${file}`);
}
// momentumCartConversion();
convertDossierData("data", "spar-2020.csv");
convertDossierData("data", "billa-2020.csv");