From 9080feef05f579858597f7224ffe149685b27570 Mon Sep 17 00:00:00 2001 From: Julian Simioni Date: Mon, 16 Jul 2018 21:24:13 -0400 Subject: [PATCH] WIP: Configurable boosts for sources and layers This is a work in progress to enable customizing boosts for sources and layers. For now, the config must be hardcoded in query/autocomplete.js, but it will eventually be driven by `pelias.json` and take effect on all endpoints. --- query/autocomplete.js | 13 +++ query/view/boost_sources_and_layers.js | 48 +++++++++ .../query/view/boost_sources_and_layers.js | 98 +++++++++++++++++++ test/unit/run.js | 5 +- 4 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 query/view/boost_sources_and_layers.js create mode 100644 test/unit/query/view/boost_sources_and_layers.js diff --git a/query/autocomplete.js b/query/autocomplete.js index 0e6c4b11..de1288bc 100644 --- a/query/autocomplete.js +++ b/query/autocomplete.js @@ -44,6 +44,19 @@ query.score( peliasQuery.view.focus( views.ngrams_strict ) ); query.score( peliasQuery.view.popularity( views.pop_subquery ) ); query.score( peliasQuery.view.population( views.pop_subquery ) ); +const boostView = require( './view/boost_sources_and_layers' ); + +const boostConfig = {}; //TODO: empty config to make functional tests pass for now +// example useful config: +//const boostConfig = { + //layer: { + //stops: 5, + //fare: 5, + //station: 1 + //}, +//}; +query.score( boostView(boostConfig) ); + // non-scoring hard filters query.filter( peliasQuery.view.sources ); query.filter( peliasQuery.view.layers ); diff --git a/query/view/boost_sources_and_layers.js b/query/view/boost_sources_and_layers.js new file mode 100644 index 00000000..7d7626b0 --- /dev/null +++ b/query/view/boost_sources_and_layers.js @@ -0,0 +1,48 @@ +//example input +//{ +// "source": { +// "openstreetmap": 5 +// }, +// "layer": { +// "street": 3, +// "country": 5 +// } +//} + +function generateTermQuery(field, value, boost) { + return { + constant_score: { + boost: boost, + query: { + term: { + [field]: value, + } + } + } + }; +} + +module.exports = function( configuration ) { + return function( ) { + const filters = []; + ['source', 'layer'].forEach(function(target) { + if (configuration[target]) { + Object.keys(configuration[target]).forEach(function(item) { + filters.push(generateTermQuery(target, item, configuration[target][item])); + }); + } + }); + + if (filters.length === 0) { + return null; + } else if (filters.length === 1) { + return filters[0]; + } else { + return { + bool: { + should: filters + } + }; + } + }; +}; diff --git a/test/unit/query/view/boost_sources_and_layers.js b/test/unit/query/view/boost_sources_and_layers.js new file mode 100644 index 00000000..aff6a4d2 --- /dev/null +++ b/test/unit/query/view/boost_sources_and_layers.js @@ -0,0 +1,98 @@ +const boost_sources_and_layers = require('../../../../query/view/boost_sources_and_layers'); + +module.exports.tests = {}; + +module.exports.tests.empty_config = function(test, common) { + test('empty configuration returns empty query', function(t) { + const view_instance = boost_sources_and_layers({}); + const query = view_instance(); + t.equal(query, null, 'query is empty'); + t.end(); + }); +}; + +module.exports.tests.single_item_config = function(test, common) { + test('config with single layer entry returns single term query with boost', function(t) { + const config = { + layer: { + locality: 5 + } + }; + const expected_query = { + constant_score: { + boost: 5, + query: { + term: { + layer: 'locality' + } + } + } + }; + + const view_instance = boost_sources_and_layers(config); + + t.deepEquals(view_instance(), expected_query, 'query is a single term query'); + t.end(); + }); +}; + +module.exports.tests.mulitple_item_config = function(test, common) { + test('config with multiple items returns bool query with multiple should conditions', function(t) { + const config = { + source: { + whosonfirst: 6 + }, + layer: { + country: 2, + borough: 0.5 + }, + }; + const expected_query = { + bool: { + should: [{ + constant_score: { + boost: 6, + query: { + term: { + source: 'whosonfirst', + } + } + } + }, { + constant_score: { + boost: 2, + query: { + term: { + layer: 'country' + } + } + } + },{ + constant_score: { + boost: 0.5, + query: { + term: { + layer: 'borough' + } + } + } + }] + } + }; + const view_instance = boost_sources_and_layers(config); + + t.deepEquals(view_instance(), expected_query, 'query is a bool query with multiple term queres'); + t.end(); + + }); +}; + +module.exports.all = function (tape, common) { + function test(name, testFunction) { + return tape('boost sources and layers ' + name, testFunction); + } + + for( var testCase in module.exports.tests ){ + module.exports.tests[testCase](test, common); + } +}; diff --git a/test/unit/run.js b/test/unit/run.js index c8eee73a..7dfa79e2 100644 --- a/test/unit/run.js +++ b/test/unit/run.js @@ -64,13 +64,14 @@ var tests = [ require('./query/address_search_using_ids'), require('./query/autocomplete'), require('./query/autocomplete_defaults'), - require('./query/search_defaults'), - require('./query/reverse_defaults'), require('./query/reverse'), + require('./query/reverse_defaults'), require('./query/search'), + require('./query/search_defaults'), require('./query/search_original'), require('./query/structured_geocoding'), require('./query/text_parser'), + require('./query/view/boost_sources_and_layers'), require('./sanitizer/_boundary_country'), require('./sanitizer/_debug'), require('./sanitizer/_flag_bool'),