little cleanup
This commit is contained in:
parent
2e4ec7b24e
commit
bbf483e445
40
README.md
40
README.md
@ -2,9 +2,45 @@
|
||||
|
||||
Campus Cup AKMC Data Traveloptimizer
|
||||
|
||||
## Installation
|
||||
## Backend
|
||||
|
||||
### Requirements
|
||||
- MariaDB or MySQL
|
||||
- node `10.12` or higher
|
||||
- Configure database in `.env`-file or environment variables. See `.env` for reference
|
||||
- Set API-Key for meteostat.net in `.env`-file or environment variable
|
||||
- import `setup.sql` for sample data
|
||||
|
||||
### Start
|
||||
- Run `$(cd backend && npm run start)`
|
||||
- call http://localhost:3000/v1/update/climate to fetch climate data for sample entries.
|
||||
|
||||
### Search
|
||||
Customize your search with query parameters. For now, only climate parameters are supported. If you omit climate queries, all climate parameters will be randomized.
|
||||
|
||||
Following queries are supperted by now:
|
||||
- from=YYYY-MM-DD _(required)_
|
||||
- to=YYYY-MM-DD _(required)_
|
||||
- temperature=NUMBER,NUMBER
|
||||
- raindays=NUMBER,NUMBER
|
||||
- sunhours=NUMBER,NUMBER
|
||||
- percipitation=NUMBER,NUMBER
|
||||
|
||||
__Examples:__
|
||||
http://localhost:3000/v1/search?from=2020-06-14&to=2020-07-29&temperature=27,29&raindays=8,12&sunhours=250,300
|
||||
http://localhost:3000/v1/search?from=2020-06-14&to=2020-07-29
|
||||
|
||||
|
||||
### More
|
||||
To get more search results, add more entries with meteostat station IDs to the `regions` table in the database
|
||||
|
||||
|
||||
|
||||
## Frontend
|
||||
|
||||
### Installation
|
||||
- Install node 10.15.3
|
||||
- Run "(cd frontend && npm i)"
|
||||
|
||||
# Start dev server
|
||||
### Start dev server
|
||||
- Run "(cd frontend && npm run start)"
|
||||
|
||||
@ -4,6 +4,8 @@ var sampledata = require('./sampledata')
|
||||
var db = require('./mysql')
|
||||
var score = require('./score')
|
||||
var transformer = require('./transformer')
|
||||
var climate = require('./climate')
|
||||
var moment = require('moment')
|
||||
|
||||
const app = express()
|
||||
const port = 3000
|
||||
@ -34,7 +36,29 @@ const samplePresets = [
|
||||
app.get('/', (req, res) => res.send('Hello Timo!'))
|
||||
app.get('/v1/regions', (req, res) => res.json(sampleRegions))
|
||||
app.get('/v1/presets', (req, res) => res.json(samplePresets))
|
||||
app.get('/v1/search', (req, res) => {
|
||||
app.get('/v1/search', searchHandler)
|
||||
app.get('/v1/update/climate', climateUpdateHandler)
|
||||
|
||||
app.listen(port, () => console.log(`Travopti backend listening at http://localhost:${port}`))
|
||||
|
||||
function climateUpdateHandler(req, res) {
|
||||
let parameter = []
|
||||
if (req.query.startDate) parameter.push(req.query.startDate)
|
||||
if (req.query.endDate) parameter.push(req.query.endDate)
|
||||
climate.update(...parameter).then(x => {
|
||||
res.send(x)
|
||||
}).catch(e => {
|
||||
let result = {
|
||||
message: 'error during update process. check backend logs.',
|
||||
error: e
|
||||
}
|
||||
res.send(result)
|
||||
}
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
function searchHandler(req, res) {
|
||||
let response = {}
|
||||
|
||||
response.meta = {
|
||||
@ -42,7 +66,7 @@ app.get('/v1/search', (req, res) => {
|
||||
query: req.query,
|
||||
headers: req.headers
|
||||
}
|
||||
|
||||
|
||||
console.log('log params')
|
||||
console.log(req.query.from)
|
||||
console.log(req.query.to)
|
||||
@ -57,15 +81,14 @@ app.get('/v1/search', (req, res) => {
|
||||
search(req.query.from, req.query.to, validQueries).then(searchResults => {
|
||||
response.data = searchResults
|
||||
res.json(response)
|
||||
}).catch(e => {
|
||||
console.log(e)
|
||||
res.json(e.message)
|
||||
})
|
||||
})
|
||||
|
||||
app.listen(port, () => console.log(`Travopti backend listening at http://localhost:${port}`))
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
async function search(from, to, queries) {
|
||||
console.log('search')
|
||||
// get Min and Max values for each Parameter
|
||||
const minMax = await getClimateMinMax()
|
||||
// randomize if empty queries
|
||||
@ -79,23 +102,28 @@ async function search(from, to, queries) {
|
||||
queries.raindays = `${r},${r + 5}`
|
||||
queries.sunhours = `${s},${s + 50}`
|
||||
}
|
||||
console.log('search')
|
||||
// validate regex: YYYY-MM-DD
|
||||
let re = /([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))/i;
|
||||
if (!from.match(re) || !to.match(re)) throw new Error("invalid parameter: " + from + " " + to)
|
||||
if (!from.match(re) || !to.match(re)) throw new Error("ERR: invalid parameter:",from,to)
|
||||
// check for valid period
|
||||
if (moment(from, 'YYYY-MM-DD').isAfter(moment(to, 'YYYY-MM-DD'))) {
|
||||
console.log(moment(from, 'YYYY-MM-DD'))
|
||||
console.log(moment(to, 'YYYY-MM-DD'))
|
||||
console.log(moment(from, 'YYYY-MM-DD').isAfter(moment(to, 'YYYY-MM-DD')))
|
||||
throw new Error("ERR: from is before to date.")
|
||||
}
|
||||
|
||||
// -- Prepare search --
|
||||
// calculate average if traveldates are in more than one month
|
||||
let monthFrom = Number(from.split("-")[1])
|
||||
let monthTo = Number(to.split("-")[1])
|
||||
|
||||
let travelPeriods = []
|
||||
let virtDays = 0
|
||||
if (monthFrom === monthTo) {
|
||||
let element = {
|
||||
month: monthFrom,
|
||||
days: Number(to.split("-")[2]) - Number(from.split("-")[2])
|
||||
}
|
||||
virtDays = element.days
|
||||
travelPeriods.push(element)
|
||||
} else {
|
||||
for (let index = monthFrom; index <= monthTo; index++) {
|
||||
@ -116,7 +144,6 @@ async function search(from, to, queries) {
|
||||
days: 30
|
||||
}
|
||||
}
|
||||
virtDays += element.days
|
||||
travelPeriods.push(element)
|
||||
}
|
||||
}
|
||||
@ -142,51 +169,11 @@ async function search(from, to, queries) {
|
||||
minMax: minMax
|
||||
}
|
||||
}
|
||||
// return { TODO___scores: detailScores }
|
||||
// return { TODO___scores: calculateAverageScore(detailScores, Object.keys(queries)) }
|
||||
}
|
||||
|
||||
function calculateAverageScore(scorePeriods, types) {
|
||||
const days = scorePeriods.reduce((total, period) => total += period.days)
|
||||
let totalScores = {}
|
||||
|
||||
let finalRegionObj = {
|
||||
|
||||
}
|
||||
|
||||
scorePeriods.forEach(element => {
|
||||
|
||||
types.forEach(type => {
|
||||
element.scores[type].forEach(regionScore => {
|
||||
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function transformToRegionObjects(scorePeriods) {
|
||||
let response = []
|
||||
// create region objects
|
||||
scorePeriods[0].climate.forEach(element => {
|
||||
let obj = {
|
||||
region_id: element.region_id,
|
||||
country_id: element.country_id,
|
||||
name: element.name,
|
||||
}
|
||||
response.push(obj)
|
||||
})
|
||||
//
|
||||
|
||||
}
|
||||
|
||||
function calculateScores(type, regionDataRows, searchLowParam, searchMaxParam, minMax) {
|
||||
console.log('calculateScores for', type)
|
||||
// console.log(searchLowParam)
|
||||
// console.log(searchMaxParam)
|
||||
// console.log(minMax)
|
||||
//console.log(regionDataRows)
|
||||
let result = regionDataRows.map(x => {
|
||||
// console.log(x.temperature_mean)
|
||||
const sc = Math.round(score.calculateScoreRange(minMax.min[type], minMax.max[type], multiplier[type], x[type], searchLowParam, searchMaxParam) * 100) / 100
|
||||
|
||||
return {
|
||||
|
||||
@ -5,7 +5,9 @@ const axios = require('axios')
|
||||
const rangeStartDate = '2010-01'
|
||||
const rangeEndDate = '2018-12'
|
||||
|
||||
async function main() {
|
||||
exports.update = async function (startDate = rangeStartDate, endDate = rangeEndDate) {
|
||||
console.log('update climate with:', startDate, endDate);
|
||||
|
||||
const connection = await mysql.createConnection({
|
||||
host: process.env.DB_HOST,
|
||||
user: process.env.DB_USER,
|
||||
@ -26,39 +28,42 @@ async function main() {
|
||||
await writeToDatabase(connection, final2)
|
||||
|
||||
connection.end();
|
||||
let response = 'database update complete. see backend logs for info.'
|
||||
console.log(response)
|
||||
return response
|
||||
}
|
||||
|
||||
async function createClimateObject(src) {
|
||||
let response
|
||||
try {
|
||||
response = await axios.get(`https://api.meteostat.net/v1/climate/normals?station=${src.meteostat_id}&key=${process.env.METEOSTAT_API_KEY}`)
|
||||
} catch (error) {
|
||||
console.log("skipping: couldn't find results for following region: ")
|
||||
console.log(src.region + " with meteostat_id " + src.meteostat_id)
|
||||
return []
|
||||
}
|
||||
if (!response.data.data) {
|
||||
console.log("skipping: no data for station meteostat_id " + src.meteostat_id + " (" + src.region + ")")
|
||||
return []
|
||||
}
|
||||
let results = []
|
||||
for (let index = 1; index <= 12; index++) {
|
||||
let result = {
|
||||
region: src.region,
|
||||
region_id: src.id,
|
||||
month: index,
|
||||
temperature: Object.values(response.data.data.temperature)[index - 1] ? Object.values(response.data.data.temperature)[index - 1] : null,
|
||||
temperature_min: Object.values(response.data.data.temperature_min)[index - 1] ? Object.values(response.data.data.temperature_min)[index - 1] : null,
|
||||
temperature_max: Object.values(response.data.data.temperature_max)[index - 1] ? Object.values(response.data.data.temperature_max)[index - 1] : null,
|
||||
precipitation: Object.values(response.data.data.precipitation)[index - 1] ? Object.values(response.data.data.precipitation)[index - 1] : null,
|
||||
sunshine: Object.values(response.data.data.sunshine)[index - 1] ? Object.values(response.data.data.sunshine)[index - 1] : null,
|
||||
}
|
||||
results.push(result)
|
||||
}
|
||||
return results
|
||||
}
|
||||
// async function createClimateObject(src) {
|
||||
// let response
|
||||
// try {
|
||||
// response = await axios.get(`https://api.meteostat.net/v1/climate/normals?station=${src.meteostat_id}&key=${process.env.METEOSTAT_API_KEY}`)
|
||||
// } catch (error) {
|
||||
// console.log("skipping: couldn't find results for following region: ")
|
||||
// console.log(src.region + " with meteostat_id " + src.meteostat_id)
|
||||
// return []
|
||||
// }
|
||||
// if (!response.data.data) {
|
||||
// console.log("skipping: no data for station meteostat_id " + src.meteostat_id + " (" + src.region + ")")
|
||||
// return []
|
||||
// }
|
||||
// let results = []
|
||||
// for (let index = 1; index <= 12; index++) {
|
||||
// let result = {
|
||||
// region: src.region,
|
||||
// region_id: src.id,
|
||||
// month: index,
|
||||
// temperature: Object.values(response.data.data.temperature)[index - 1] ? Object.values(response.data.data.temperature)[index - 1] : null,
|
||||
// temperature_min: Object.values(response.data.data.temperature_min)[index - 1] ? Object.values(response.data.data.temperature_min)[index - 1] : null,
|
||||
// temperature_max: Object.values(response.data.data.temperature_max)[index - 1] ? Object.values(response.data.data.temperature_max)[index - 1] : null,
|
||||
// precipitation: Object.values(response.data.data.precipitation)[index - 1] ? Object.values(response.data.data.precipitation)[index - 1] : null,
|
||||
// sunshine: Object.values(response.data.data.sunshine)[index - 1] ? Object.values(response.data.data.sunshine)[index - 1] : null,
|
||||
// }
|
||||
// results.push(result)
|
||||
// }
|
||||
// return results
|
||||
// }
|
||||
|
||||
async function createClimateObjectFrom(src, startDate = '2010-01', endDate = '2018-12') {
|
||||
async function createClimateObjectFrom(src, startDate, endDate) {
|
||||
let response
|
||||
try {
|
||||
response = await axios.get(`https://api.meteostat.net/v1/history/monthly?station=${src.meteostat_id}&start=${startDate}&end=${endDate}&key=${process.env.METEOSTAT_API_KEY}`)
|
||||
@ -92,17 +97,17 @@ async function createClimateObjectFrom(src, startDate = '2010-01', endDate = '20
|
||||
return results
|
||||
}
|
||||
|
||||
async function writeToDatabase(dbConnection, src) {
|
||||
src.forEach(async (element) => {
|
||||
async function writeToDatabase(dbConnection, climateObjArr) {
|
||||
climateObjArr.forEach(async (element) => {
|
||||
//console.log(element)
|
||||
try {
|
||||
if (!element.year) {
|
||||
await dbConnection.execute(`
|
||||
INSERT INTO region_climate (region_id, year, month, temperature_mean, temperature_mean_min, temperature_mean_max, percipitation, sunshine)
|
||||
REPLACE INTO region_climate (region_id, year, month, temperature_mean, temperature_mean_min, temperature_mean_max, percipitation, sunshine)
|
||||
VALUES (${element.region_id}, 0, ${element.month}, ${element.temperature}, ${element.temperature_min}, ${element.temperature_max}, ${element.precipitation}, ${element.sunshine});`)
|
||||
} else {
|
||||
await dbConnection.execute(`
|
||||
INSERT INTO region_climate (region_id, year, month, temperature_mean, temperature_mean_min, temperature_mean_max, percipitation, sunshine, humidity, raindays)
|
||||
REPLACE INTO region_climate (region_id, year, month, temperature_mean, temperature_mean_min, temperature_mean_max, percipitation, sunshine, humidity, raindays)
|
||||
VALUES (${element.region_id}, ${element.year}, ${element.month}, ${element.temperature}, ${element.temperature_min}, ${element.temperature_max}, ${element.precipitation}, ${element.sunshine}, ${element.humidity}, ${element.raindays});`)
|
||||
}
|
||||
} catch (error) {
|
||||
@ -116,6 +121,4 @@ async function writeToDatabase(dbConnection, src) {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
main()
|
||||
}
|
||||
63
backend/package-lock.json
generated
63
backend/package-lock.json
generated
@ -112,11 +112,6 @@
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
|
||||
"dev": true
|
||||
},
|
||||
"bignumber.js": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz",
|
||||
"integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A=="
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz",
|
||||
@ -349,11 +344,6 @@
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||
},
|
||||
"crypto-random-string": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
|
||||
@ -787,11 +777,6 @@
|
||||
"integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==",
|
||||
"dev": true
|
||||
},
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"json-buffer": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz",
|
||||
@ -911,22 +896,16 @@
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||
"dev": true
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.26.0",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.26.0.tgz",
|
||||
"integrity": "sha512-oIixUO+OamkUkwjhAVE18rAMfRJNsNe/Stid/gwHSOfHrOtw9EhAY2AHvdKZ/k/MggcYELFCJz/Sn2pL8b8JMw=="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"mysql": {
|
||||
"version": "2.18.1",
|
||||
"resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz",
|
||||
"integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==",
|
||||
"requires": {
|
||||
"bignumber.js": "9.0.0",
|
||||
"readable-stream": "2.3.7",
|
||||
"safe-buffer": "5.1.2",
|
||||
"sqlstring": "2.3.1"
|
||||
}
|
||||
},
|
||||
"mysql2": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-2.1.0.tgz",
|
||||
@ -1103,11 +1082,6 @@
|
||||
"integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=",
|
||||
"dev": true
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||
},
|
||||
"proxy-addr": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
|
||||
@ -1180,20 +1154,6 @@
|
||||
"strip-json-comments": "~2.0.1"
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||
"integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.3",
|
||||
"isarray": "~1.0.0",
|
||||
"process-nextick-args": "~2.0.0",
|
||||
"safe-buffer": "~5.1.1",
|
||||
"string_decoder": "~1.1.1",
|
||||
"util-deprecate": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz",
|
||||
@ -1375,14 +1335,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"requires": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
|
||||
@ -1519,11 +1471,6 @@
|
||||
"prepend-http": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
"dotenv": "^8.2.0",
|
||||
"express": "^4.17.1",
|
||||
"lodash": "^4.17.15",
|
||||
"mysql": "^2.18.1",
|
||||
"moment": "^2.26.0",
|
||||
"mysql2": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
var sampledata = {}
|
||||
|
||||
sampledata.search_response_model = [
|
||||
{
|
||||
region_id: 1,
|
||||
name: 'Chicago',
|
||||
country: 'USA',
|
||||
avg_score: 0.0,
|
||||
scores: {
|
||||
temperature: 0.0,
|
||||
humidity: 0.0,
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
module.exports = sampledata;
|
||||
Loading…
Reference in New Issue
Block a user