refactored search function befor implementing price search

This commit is contained in:
Timo Volkmann 2020-06-17 16:02:26 +02:00
parent 46a57891a0
commit 33249bf817
8 changed files with 152 additions and 188 deletions

View File

@ -68,16 +68,6 @@ module.exports = async (dbConn, id) => {
region[k].rain_days = rain_days.split(",");
for (i = 0; i < region[k].rain_days.length; i++) {
region[k].rain_days[i] = parseFloat(region[k].rain_days[i])
<<<<<<< HEAD
}
}
if (region[k].sun_hours !== null) {
const sun_hours = region[k].sun_hours
region[k].sun_hours = sun_hours.split(",");
for (i = 0; i < region[k].sun_hours.length; i++) {
region[k].sun_hours[i] = parseFloat(region[k].sun_hours[i])
=======
>>>>>>> satisfiy region interface
}
}
if (region[k].sun_hours !== null) {

View File

@ -39,8 +39,8 @@ function searchHandler(dbConn) {
let scoreQueryObj = {}
if (q.temperature) scoreQueryObj['temperature_mean_max'] = q.temperature
if (q.percipitation) scoreQueryObj['percipitation'] = q.percipitation
if (q.raindays) scoreQueryObj['raindays'] = q.raindays
if (q.sunhours) scoreQueryObj['sunhours'] = q.sunhours
if (q.rain_days) scoreQueryObj['rain_days'] = q.rain_days
if (q.sun_hours) scoreQueryObj['sun_hours'] = q.sun_hours
// @TimoJ 1. hier die Parameter die gescored werden sollen hinufügen
if (_.isEmpty(scoreQueryObj)) {

View File

@ -5,14 +5,15 @@ module.exports = function (dbConn) {
console.log('getAllRegionsWithClimatePerMonth')
const sql = `SELECT
region_climate.region_id AS region_id,
region_climate.month AS month,
countries.country AS country,
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
ROUND(AVG(region_climate.raindays), 1) AS rain_days,
ROUND(AVG(region_climate.sunshine), 1) AS sun_hours
FROM region_climate
JOIN regions ON region_climate.region_id = regions.id
JOIN countries ON regions.country_id = countries.id

View File

@ -5,16 +5,16 @@ exports.getClimateMinMax = async function (dbConn) {
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
MIN(raindays) AS rain_days,
MIN(sunshine) AS sun_hours
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
MAX(raindays) AS rain_days,
MAX(sunshine) AS sun_hours
FROM region_climate`
const [qResMin, qResMax] = await Promise.all([dbConn.query(sqlMin), dbConn.query(sqlMax)])
// console.log(qResMin)

View File

@ -3,8 +3,9 @@ exports.oldToNewQuerySyntax = function (queries) {
try {
if (queries.temperature_mean_max) res.temperature_mean_max = [Number(queries.temperature_mean_max.split(',')[0]), Number(queries.temperature_mean_max.split(',')[1])]
if (queries.percipitation) res.percipitation = [Number(queries.percipitation.split(',')[0]), Number(queries.percipitation.split(',')[1])]
if (queries.raindays) res.raindays = [Number(queries.raindays.split(',')[0]), Number(queries.raindays.split(',')[1])]
if (queries.sunhours) res.sunhours = [Number(queries.sunhours.split(',')[0]), Number(queries.sunhours.split(',')[1])]
if (queries.rain_days) res.rain_days = [Number(queries.rain_days.split(',')[0]), Number(queries.rain_days.split(',')[1])]
if (queries.sun_hours) res.sun_hours = [Number(queries.sun_hours.split(',')[0]), Number(queries.sun_hours.split(',')[1])]
if (queries.accomodation_costs) res.sun_hours = [Number(queries.accomodation_costs.split(',')[0]), Number(queries.accomodation_costs.split(',')[1])]
// @TimoJ hier noch Parameter hinzufügen wenn du die alte syntax nutzen willst, ansonsten egal
console.log('queries successfully transformed');
} catch (error) {

View File

@ -1,9 +1,11 @@
const _ = require('lodash')
exports.calculateAvgScore = (...scores) => {
return avgScore = scores.reduce((total, score) => total += score) / scores.length;
}
exports.calculateScoreRange = (min, max, multiplier, regionVal, sLowVal, sHighVal) => {
//console.log('scores.calculateScoreRange:', min, max, multiplier, regionVal, sLowVal, sHighVal)
console.log('scores.calculateScoreRange:', min, max, multiplier, regionVal, sLowVal, sHighVal)
// return full score when in range
if (regionVal >= sLowVal && regionVal <= sHighVal) return 10;
// choose value with smallest distance

View File

@ -4,124 +4,172 @@ const getClimateMinMax = require("./getClimateMinMax.js")
const oldToNewQuerySyntax = require("./oldToNewQuerySyntax.js")
const getAllRegionsWithClimatePerMonth = require('./getAllRegionsWithClimatePerMonth')
const score = require('./score')
const transformer = require('./transformer')
// const getRegions = require('../models/getRegions.js').getRegionsInternal
const getRegions = require('../models/getRegions2.js')
const SHOW_ALL_SCOREOBJECTS = false
const MULTIPLIER = {
temperature_mean_max: 5,
percipitation: 3.5,
raindays: 3,
sunhours: 2.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)
// get Min and Max values for each Parameter
const minMax = await getClimateMinMax.getClimateMinMax(dbConn)
// to still support old query syntax
// transform syntax and seperate climate queries from price queries
queries = oldToNewQuerySyntax.oldToNewQuerySyntax(queries)
console.log(queries)
const q = prepareQueries(queries)
// TODO simplify and remove support for old query syntaax
let monthFrom = 0
let monthTo = 0
let dayFrom = 0
let dayTo = 0
// for calculating average if traveldates are in more than one month
const travelPeriods = travelPeriodsFromDates(dates)
// FETCH DATA FROM DB
// fetch all data relevant before calculating
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.percipitation) q.climate.percipitation = [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]) => {
let finalScoreObj = getScoreAndAverage(key, travelPeriods, reg, value[0], value[1], minMax)
reg.scores.push(finalScoreObj)
});
})
return {
results: regions
}
}
function prepareQueries(queries) {
let q = {
climate: {},
costs: {}
}
// climate
if (queries.temperature_mean_max) q.climate.temperature_mean_max = queries.temperature_mean_max
if (queries.percipitation) q.climate.percipitation = queries.percipitation
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) {
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()
monthFrom = dateFrom.getMonth()
monthTo = dateTo.getMonth()
dayFrom = dateFrom.getDay()
dayTo = dateTo.getDay()
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
// 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;
monthFrom = Number(from.split("-")[1])
monthTo = Number(to.split("-")[1])
dayFrom = Number(from.split("-")[2])
dayTo = Number(to.split("-")[2])
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'.")
}
// -- 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(dbConn)(period.month)
// @TimoJ hier müssten die Preise mit rein, z.B.
// period.prices = await getAveragePricesPerMonth(dbConn)(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
}));
//console.log(allRegs)
// calculate ratio and transform into target object
let allRegs = await getRegions(dbConn)
return {
results: transformer.transform(detailScores, allRegs),
debug: {
detailScores: detailScores,
minMax: minMax
}
}
return fromAndTo
}
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
function getScoreAndAverage(type, travelPeriods, region, searchLowParam, searchMaxParam, minMax) {
console.log('calculateScores for', region.name, type)
//console.log(type, travelPeriods, region, searchLowParam, searchMaxParam, minMax)
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)
console.log(sc)
return {
region_id: x.region_id,
//region_id: x.region_id,
type: type,
value: x[type],
score: x[type] === null ? null : sc
value: region[type][period.month - 1],
score: (region[type][period.month - 1] === null || searchLowParam === null) ? null : sc,
days: period.days
}
})
return result
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
}
}

View File

@ -1,78 +0,0 @@
const _ = require('lodash')
const fs = require('fs')
exports.transform = (data, regions) => {
// get data
// let data = JSON.parse(fs.readFileSync('transformer-test.json'));
const types = Object.keys(data[0].scores)
// STEP 1 Create Response Array with region names from first climate object
let byRegion = regions
// let byRegion = regions.map(el => {
// // return el
// return {
// region_id: el.region_id,
// name: el.name,
// country: el.country,
// }
// })
// STEP 2 Prepare flat scoreobject array and set days property
scoreObjs = _.flatten(_.map(data, (period) => {
return _.reduce(period.scores, (arr, el) => {
return arr.concat(el)
}).map(element => {
element.days = period.days
return element
})
}))
// STEP 3 Collect scoreobjects for each region
let results = byRegion.map(region => {
let scores = []
types.forEach(typ => {
let tempScores = _.filter(scoreObjs, { 'region_id': region.region_id, 'type': typ })
if (_.some(tempScores, { 'score': null })) {
console.log("found 'null' scores! skipping...")
console.log(tempScores)
return
}
let averagedScore = {
region_id: region.region_id,
type: typ,
value: 0,
score: 0,
days: 0
}
tempScores.forEach(el => {
averagedScore.value += (el.value * el.days)
averagedScore.score += (el.score * el.days)
averagedScore.days += (el.days)
})
averagedScore.value = _.round(averagedScore.value / averagedScore.days, 1)
averagedScore.score = _.round(averagedScore.score / averagedScore.days, 1)
delete averagedScore.region_id
delete averagedScore.days
scores.push(averagedScore)
})
region.scores = scores
// STEP 4 Calculate Average Score
region.score = calculateAverage(region.scores)
//console.log(region)
return region
})
// console.log(results)
return _.orderBy(results, ({ score }) => score || 0, 'desc') //.filter(el => !_.isNaN(el.score))
//end
}
function calculateAverage(scores) {
let sum = 0
scores.forEach(el => sum += el.score)
//console.log(sum)
return _.round(sum / scores.length, 2)
}