refactored search function befor implementing price search
This commit is contained in:
parent
46a57891a0
commit
33249bf817
@ -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) {
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user