mirror of
https://github.com/badlogic/heissepreise.git
synced 2024-06-22 08:25:52 +02:00
Style changes, Ingest Dossier Data from xls converted to csv see https://www.dossier.at/dossiers/supermaerkte/quellen/anatomie-eines-supermarkts-die-methodik/
This commit is contained in:
parent
42b89fa325
commit
ce8e0fb102
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -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
9
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
167
site/style.css
167
site/style.css
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -375,7 +375,15 @@ function showChart(canvasDom, items) {
|
|||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
aspectRation: 16 / 9
|
||||
aspectRation: 16 / 9,
|
||||
scales: {
|
||||
y: {
|
||||
title: {
|
||||
display: true,
|
||||
text: "EURO"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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 }],
|
||||
|
|
79
stuff.js
79
stuff.js
|
@ -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");
|
Loading…
Reference in New Issue
Block a user