travopti/backend/util/scoreAndSearch.js
2020-06-17 21:50:31 +02:00

196 lines
6.4 KiB
JavaScript

const _ = require('lodash')
const moment = require("moment")
const getClimateMinMax = require("./getClimateMinMax.js")
const oldToNewQuerySyntax = require("./oldToNewQuerySyntax.js")
const getAllRegionsWithClimatePerMonth = require('./getAllRegionsWithClimatePerMonth')
const score = require('./score')
// const getRegions = require('../models/getRegions.js').getRegionsInternal
const getRegions = require('../models/getRegions2.js')
const SHOW_ALL_SCOREOBJECTS = false
const MULTIPLIER = {
temperature_mean_max: 5,
precipitation: 3.5,
rain_days: 3,
sun_hours: 2.5,
accomodation_costs: 1
}
module.exports = function (dbConn) {
return async function (from, to, queries) {
console.log('search')
// PREPARE SEARCH
// validate dates
const dates = validateDates(from, to)
// transform syntax and seperate climate queries from price queries
queries = oldToNewQuerySyntax.oldToNewQuerySyntax(queries)
// console.log(queries)
const q = prepareQueries(queries)
// console.log(q)
// for calculating average if traveldates are in more than one month
const travelPeriods = travelPeriodsFromDates(dates)
// FETCH DATA FROM DB
const minMax = await getClimateMinMax.getClimateMinMax(dbConn)
let regions = await getRegions(dbConn)
regions.forEach(reg => reg.scores = [])
// little tweak to show score object without request
if (SHOW_ALL_SCOREOBJECTS) {
if (!q.climate.temperature_mean_max) q.climate.temperature_mean_max = [null, null]
if (!q.climate.precipitation) q.climate.precipitation = [null, null]
if (!q.climate.rain_days) q.climate.rain_days = [null, null]
if (!q.climate.sun_hours) q.climate.sun_hours = [null, null]
}
// CALCULATE SCORES FOR CLIMATE PROPS
regions.forEach(reg => {
Object.entries(q.climate).forEach(([key, value]) => {
// console.log('key', key)
// console.log('val', value[0], value[1])
let finalScoreObj = getScoreAndAverage(key, travelPeriods, reg, value[0], value[1], minMax)
reg.scores.push(finalScoreObj)
});
// CALCULATE SCORES FOR PRICE PROPS
// CALCULATE AVERAGE SCORE
reg.score = calculateAverage(reg.scores)
})
return {
results: _.orderBy(regions, ({ score }) => score || 0, 'desc') //.filter(el => !_.isNaN(el.score))
}
}
function calculateAverage(scores) {
let sum = 0
scores.forEach(el => sum += el.score)
//console.log(sum)
return _.round(sum / scores.length, 2)
}
function prepareQueries(queries) {
let q = {
climate: {},
costs: {}
}
// climate
if (queries.temperature_mean_max) q.climate.temperature_mean_max = queries.temperature_mean_max
if (queries.precipitation) q.climate.precipitation = queries.precipitation
if (queries.rain_days) q.climate.rain_days = queries.rain_days
if (queries.sun_hours) q.climate.sun_hours = queries.sun_hours
// costs
if (queries.accomodation_costs) q.climate.accomodation_costs = queries.accomodation_costs
return q
}
function travelPeriodsFromDates(dates) {
//console.log(dates);
let travelPeriods = []
if (dates.from.month === dates.to.month) {
let period = {
month: dates.from.month,
days: dates.to.day - dates.from.day
}
travelPeriods.push(period)
} else {
for (let i = dates.from.month; i <= dates.to.month; i++) {
let period = {}
if (i === dates.from.month) {
period = {
month: i,
days: 32 - dates.from.day
}
} else if (i === dates.to.month) {
period = {
month: i,
days: dates.to.day
}
} else {
period = {
month: i,
days: 30
}
}
travelPeriods.push(period)
}
}
return travelPeriods
}
function validateDates(from, to) {
let fromAndTo = {
from: {},
to: {}
}
if (_.isNumber(from) && _.isNumber(to)) {
let dateFrom = moment(from).toDate()
let dateTo = moment(to).toDate()
fromAndTo.from.month = dateFrom.getMonth()
fromAndTo.to.month = dateTo.getMonth()
fromAndTo.from.day = dateFrom.getDay()
fromAndTo.to.day = 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 {
// this block to still support old query syntax, validating from and to parameter
let re = /([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))/i;
fromAndTo.from.month = Number(from.split("-")[1])
fromAndTo.to.month = Number(to.split("-")[1])
fromAndTo.from.day = Number(from.split("-")[2])
fromAndTo.to.day = 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'.")
}
console.log(fromAndTo)
return fromAndTo
}
function getScoreAndAverage(type, travelPeriods, region, searchLowParam, searchMaxParam, minMax) {
console.log('calculateScores for', region.name, type)
// console.log(type, travelPeriods, searchLowParam, searchMaxParam)
const singleScores = travelPeriods.map(period => {
const sc = _.round(score.calculateScoreRange(minMax.min[type], minMax.max[type], MULTIPLIER[type], region[type][period.month - 1], searchLowParam, searchMaxParam), 2)
let res = {
//region_id: x.region_id,
type: type,
value: region[type][period.month - 1],
score: (region[type][period.month - 1] === null || searchLowParam === null) ? null : sc,
days: period.days
}
console.log(res);
return res
})
let averagedScore = {
type: type,
value: 0,
score: 0,
days: 0
}
singleScores.forEach(el => {
if (el.value !== null) {
//console.log(el)
averagedScore.value += (el.value * el.days)
averagedScore.score += (el.score * el.days)
averagedScore.days += (el.days)
} else {
console.log('skip averaging')
console.log(el)
}
})
averagedScore.value = _.round(averagedScore.value / averagedScore.days, 1)
averagedScore.score = _.round(averagedScore.score / averagedScore.days, 1)
if (searchLowParam === null) averagedScore.score = null
delete averagedScore.days
return averagedScore
}
}