travopti/backend/app.js
2020-06-17 21:50:20 +02:00

277 lines
8.9 KiB
JavaScript

const express = require('express')
const moment = require('moment')
const _ = require('lodash')
const db = require('./models/mysql')
const score = require('./models/score')
const transformer = require('./models/transformer')
const climate = require('./models/climate')
const base = require('./models/base64')
const app = express()
const port = 3000
//const multiplier_temp = 5
const multiplier = {
temperature_mean_max: 5,
percipitation: 3.5,
raindays: 3,
sunhours: 2.5,
}
const samplePresets = [
{
id: 29837,
parameter: "temperature",
label: "warm",
values: [22, 25]
}
]
app.get('/', (req, res) => res.send('Hello Timo!'))
app.get('/v1/regions', (req, res) => getAllRegions().then(x => res.json({ data: x })))
app.get('/v1/presets', (req, res) => res.json({ data: samplePresets}))
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 = {
params: req.params,
query: req.query,
headers: req.headers
}
let q = req.query.q ? base.base64ToObj(req.query.q) : req.query
console.log('Q:', q)
let queryObj = {}
if (q.temperature) queryObj['temperature_mean_max'] = q.temperature
if (q.percipitation) queryObj['percipitation'] = q.percipitation
if (q.raindays) queryObj['raindays'] = q.raindays
if (q.sunhours) queryObj['sunhours'] = q.sunhours
scoreAndSearch(q.from, q.to, queryObj).then(searchResults => {
response.data = searchResults
res.json(response)
}).catch(e => {
console.log(e)
res.json(e.message)
})
}
async function scoreAndSearch(from, to, queries) {
// TODO break funtion into parts when implementing non-climate queries and modularize (new file)
console.log('search')
// get Min and Max values for each Parameter
const minMax = await getClimateMinMax()
// randomize if empty queries
if (_.isEmpty(queries)) {
let t = _.round(_.random(minMax.min.temperature_mean_max, minMax.max.temperature_mean_max-5),0)
let p = _.round(_.random(minMax.min.percipitation, minMax.max.percipitation - 50), 0)
let r = _.round(_.random(minMax.min.raindays, minMax.max.raindays - 5), 0)
let s = _.round(_.random(minMax.min.sunhours, minMax.max.sunhours - 50), 0)
queries.temperature_mean_max = `${t},${t + 5}`
queries.percipitation = `${p},${p + 50}`
queries.raindays = `${r},${r + 5}`
queries.sunhours = `${s},${s + 50}`
}
queries = oldToNewQuerySyntax(queries)
console.log(queries)
// TODO simplify and remove support for old query syntaax
let monthFrom = 0
let monthTo = 0
let dayFrom = 0
let dayTo = 0
if (_.isNumber(from) && _.isNumber(to)) {
let dateFrom = moment(from).toDate()
let dateTo = moment(to).toDate()
monthFrom = dateFrom.getMonth()
monthTo = dateTo.getMonth()
dayFrom = dateFrom.getDay()
dayTo = dateTo.getDay()
if (moment(dateFrom).add(23, 'hours').isAfter(moment(dateTo))) throw new Error("ERR: 'to' must be at least one day after 'from'.")
} else {
// to still support old query syntax
let re = /([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))/i;
monthFrom = Number(from.split("-")[1])
monthTo = Number(to.split("-")[1])
dayFrom = Number(from.split("-")[2])
dayTo = Number(to.split("-")[2])
if (!from.match(re) || !to.match(re)) throw new Error("ERR: invalid parameter:",from,to)
if (moment(from, 'YYYY-MM-DD').add(23, 'hours').isAfter(moment(to, 'YYYY-MM-DD'))) throw new Error("ERR: 'to' must be at least one day after 'from'.")
}
// -- Prepare search --
// to calculate average if traveldates are in more than one month
let travelPeriods = []
if (monthFrom === monthTo) {
let element = {
month: monthFrom,
days: dayTo - dayFrom
}
travelPeriods.push(element)
} else {
for (let index = monthFrom; index <= monthTo; index++) {
let element = {}
if (index === monthFrom) {
element = {
month: index,
days: 32 - dayFrom
}
} else if (index === monthTo) {
element = {
month: index,
days: dayTo
}
} else {
element = {
month: index,
days: 30
}
}
travelPeriods.push(element)
}
}
// calculate detail scores
let detailScores = await Promise.all(travelPeriods.map(async period => {
period.climate = await getAllRegionsWithClimatePerMonth(period.month)
period.scores = {}
Object.entries(queries).forEach(([key, value]) => {
// console.log('key',key)
// console.log('val', value)
period.scores[key] = calculateScores(key, period.climate, value[0], value[1], minMax)
});
return period
}));
// calculate ratio and transform into target object
return {
results: transformer.transform(detailScores),
debug: {
detailScores: detailScores,
minMax: minMax
}
}
}
function calculateScores(type, regionDataRows, searchLowParam, searchMaxParam, minMax) {
console.log('calculateScores for', type)
let result = regionDataRows.map(x => {
const sc = Math.round(score.calculateScoreRange(minMax.min[type], minMax.max[type], multiplier[type], x[type], searchLowParam, searchMaxParam) * 100) / 100
return {
region_id: x.region_id,
type: type,
value: x[type],
score: x[type] === null ? null : sc
}
})
return result
}
async function getClimateMinMax() {
console.log('getClimateMinMax')
const sqlMin = `SELECT
MIN(temperature_mean) AS temperature_mean,
MIN(temperature_mean_min) AS temperature_mean_min,
MIN(temperature_mean_max) AS temperature_mean_max,
MIN(percipitation) AS percipitation,
MIN(raindays) AS raindays,
MIN(sunshine) AS sunhours
FROM region_climate`
const sqlMax = `SELECT
MAX(temperature_mean) AS temperature_mean,
MAX(temperature_mean_min) AS temperature_mean_min,
MAX(temperature_mean_max) AS temperature_mean_max,
MAX(percipitation) AS percipitation,
MAX(raindays) AS raindays,
MAX(sunshine) AS sunhours
FROM region_climate`
const [qResMin, qResMax] = await Promise.all([getQueryRows(sqlMin), getQueryRows(sqlMax)])
//console.log(qResMin)
return { min: qResMin[0], max: qResMax[0] }
}
async function getQueryRows(sql) {
//console.log('getQueryRows')
const [rows, fields] = await db.execute(sql)
return rows
}
function getAllRegions() {
const sql = `SELECT
regions.id AS region_id,
regions.region AS name,
regions.country_id AS country_id,
countries.country AS country,
regions.meteostat_id AS meteostat_id
FROM regions
JOIN countries
ON regions.country_id = countries.id`
return getQueryRows(sql)
}
function getClimatePerRegionAndMonth(regionId, month) {
console.log('getClimatePerRegionAndMonth')
const sql = `SELECT region_id, AVG(temperature_mean), AVG(temperature_mean_min), AVG(temperature_mean_max), AVG(percipitation), AVG(sunshine) FROM region_climate WHERE month = ${month} AND region_id = ${regionId}`
return getQueryRows(sql)
}
function getAllRegionsWithClimatePerMonth(month) {
console.log('getAllRegionsWithClimatePerMonth')
const sql = `SELECT
region_climate.region_id AS region_id,
regions.country_id AS country_id,
regions.region AS name,
ROUND(AVG(region_climate.temperature_mean), 1) AS temperature_mean,
ROUND(AVG(region_climate.temperature_mean_min), 1) AS temperature_mean_min,
ROUND(AVG(region_climate.temperature_mean_max), 1) AS temperature_mean_max,
ROUND(AVG(region_climate.percipitation), 1) AS percipitation,
ROUND(AVG(region_climate.raindays), 1) AS raindays,
ROUND(AVG(region_climate.sunshine), 1) AS sunhours
FROM region_climate JOIN regions ON region_climate.region_id = regions.id WHERE region_climate.month = ${month} GROUP BY region_id`
return getQueryRows(sql)
}
function oldToNewQuerySyntax(queries) {
let res = {}
try {
if (queries.temperature_mean_max) res.temperature_mean_max = [queries.temperature_mean_max.split(',')[0], queries.temperature_mean_max.split(',')[1]]
if (queries.percipitation) res.percipitation = [queries.percipitation.split(',')[0], queries.percipitation.split(',')[1]]
if (queries.raindays) res.raindays = [queries.raindays.split(',')[0], queries.raindays.split(',')[1]]
if (queries.sunhours) res.sunhours = [queries.sunhours.split(',')[0], queries.sunhours.split(',')[1]]
console.log('queries successfully transformed');
} catch (error) {
console.log('queries are ok');
return queries
}
return res
}