diff --git a/backend/models/getRegionById.js b/backend/models/getRegionById.js index e100571..88ef8f0 100644 --- a/backend/models/getRegionById.js +++ b/backend/models/getRegionById.js @@ -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) { diff --git a/backend/routes/search.js b/backend/routes/search.js index e861441..e8fabb6 100644 --- a/backend/routes/search.js +++ b/backend/routes/search.js @@ -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)) { diff --git a/backend/util/getAllRegionsWithClimatePerMonth.js b/backend/util/getAllRegionsWithClimatePerMonth.js index fc69f8d..57ec7a8 100644 --- a/backend/util/getAllRegionsWithClimatePerMonth.js +++ b/backend/util/getAllRegionsWithClimatePerMonth.js @@ -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 diff --git a/backend/util/getClimateMinMax.js b/backend/util/getClimateMinMax.js index e4acd71..cee3dad 100644 --- a/backend/util/getClimateMinMax.js +++ b/backend/util/getClimateMinMax.js @@ -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) diff --git a/backend/util/oldToNewQuerySyntax.js b/backend/util/oldToNewQuerySyntax.js index 1298f64..d46e5e6 100644 --- a/backend/util/oldToNewQuerySyntax.js +++ b/backend/util/oldToNewQuerySyntax.js @@ -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) { diff --git a/backend/util/score.js b/backend/util/score.js index 0a3a75a..016dd3d 100644 --- a/backend/util/score.js +++ b/backend/util/score.js @@ -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 diff --git a/backend/util/scoreAndSearch.js b/backend/util/scoreAndSearch.js index a93570f..dcc4158 100644 --- a/backend/util/scoreAndSearch.js +++ b/backend/util/scoreAndSearch.js @@ -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 } } \ No newline at end of file diff --git a/backend/util/transformer.js b/backend/util/transformer.js deleted file mode 100644 index 19383f6..0000000 --- a/backend/util/transformer.js +++ /dev/null @@ -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) -} \ No newline at end of file