245 lines
7.6 KiB
JavaScript
245 lines
7.6 KiB
JavaScript
var express = require('express')
|
|
var _ = require('lodash')
|
|
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
|
|
//const multiplier_temp = 5
|
|
const multiplier = {
|
|
temperature_mean: 5,
|
|
percipitation: 3.5,
|
|
raindays: 3,
|
|
sunhours: 2.5,
|
|
}
|
|
|
|
const sampleRegions = [
|
|
{
|
|
id: 29837,
|
|
name: "Timbuktu",
|
|
country: "Mali"
|
|
}
|
|
]
|
|
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) => res.json(sampleRegions))
|
|
app.get('/v1/presets', (req, res) => res.json(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
|
|
}
|
|
|
|
console.log('log params')
|
|
console.log(req.query.from)
|
|
console.log(req.query.to)
|
|
console.log(req.query.temperature)
|
|
|
|
let validQueries = {}
|
|
if (req.query.temperature) validQueries['temperature_mean'] = req.query.temperature
|
|
if (req.query.percipitation) validQueries['percipitation'] = req.query.percipitation
|
|
if (req.query.raindays) validQueries['raindays'] = req.query.raindays
|
|
if (req.query.sunhours) validQueries['sunhours'] = req.query.sunhours
|
|
|
|
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)
|
|
})
|
|
}
|
|
|
|
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
|
|
if (_.isEmpty(queries)) {
|
|
let t = _.random(minMax.min.temperature_mean, minMax.max.temperature_mean-5)
|
|
let p = _.random(minMax.min.percipitation, minMax.max.percipitation-50)
|
|
let r = _.random(minMax.min.raindays, minMax.max.raindays-5)
|
|
let s = _.random(minMax.min.sunhours, minMax.max.sunhours-50)
|
|
queries.temperature_mean = `${t},${t + 5}`
|
|
queries.percipitation = `${p},${p + 50}`
|
|
queries.raindays = `${r},${r + 5}`
|
|
queries.sunhours = `${s},${s + 50}`
|
|
}
|
|
// 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("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 = []
|
|
if (monthFrom === monthTo) {
|
|
let element = {
|
|
month: monthFrom,
|
|
days: Number(to.split("-")[2]) - Number(from.split("-")[2])
|
|
}
|
|
travelPeriods.push(element)
|
|
} else {
|
|
for (let index = monthFrom; index <= monthTo; index++) {
|
|
let element = {}
|
|
if (index === monthFrom) {
|
|
element = {
|
|
month: index,
|
|
days: 32 - Number(from.split("-")[2])
|
|
}
|
|
} else if (index === monthTo) {
|
|
element = {
|
|
month: index,
|
|
days: Number(to.split("-")[2])
|
|
}
|
|
} else {
|
|
element = {
|
|
month: index,
|
|
days: 30
|
|
}
|
|
}
|
|
travelPeriods.push(element)
|
|
}
|
|
}
|
|
|
|
// calculate score
|
|
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.split(',')[0], value.split(',')[1], minMax)
|
|
});
|
|
return period
|
|
}));
|
|
|
|
|
|
// TODO: calculate ratio
|
|
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 getRegionIdsWithMeteostatData() {
|
|
console.log('getRegionIdsWithMeteostatData')
|
|
//return await getQueryRows(`SELECT * FROM regions WHERE meteostat_id IS NOT NULL`)
|
|
return getQueryRows(`SELECT region_id FROM region_climate GROUP BY region_id`)
|
|
}
|
|
|
|
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)
|
|
} |