|
|
|
const _ = require('lodash');
|
|
|
|
|
|
|
|
// This middleware component trims the results array by granularity when
|
|
|
|
// FallbackQuery was used. FallbackQuery is used for inputs like
|
|
|
|
// `1090 N Charlotte St, Lancaster, PA` where the address may not exist and
|
|
|
|
// we must fall back to trying `Lancaster, PA`. If the address does exist then
|
|
|
|
// FallbackQuery will return results for:
|
|
|
|
// - address+city+state
|
|
|
|
// - city+state
|
|
|
|
// - state
|
|
|
|
//
|
|
|
|
// Because the address matched, we're not interested in city+state or state, so
|
|
|
|
// this component removes results that aren't the most granular.
|
|
|
|
|
|
|
|
// layers in increasing order of granularity
|
|
|
|
// some places where this list differs in order from trimByGranularity:
|
|
|
|
// - because house number and street are in a single field, street hits must be considered
|
|
|
|
// more important than addresses due to how ES matches
|
|
|
|
// - country outranks dependency, this was done to ensure that "country=United States" doesn't
|
|
|
|
// bump up US dependencies containing "United States" above the country
|
|
|
|
// - retain both borough and locality results if both exist for when city=Manhattan is
|
|
|
|
// supplied we want to retain borough=Manhattan and city=Manhattan results
|
|
|
|
const layers = [
|
|
|
|
'venue',
|
|
|
|
'address',
|
|
|
|
'street',
|
|
|
|
'postalcode',
|
|
|
|
'neighbourhood',
|
|
|
|
['borough', 'locality'],
|
|
|
|
'localadmin',
|
|
|
|
'county',
|
|
|
|
'macrocounty',
|
|
|
|
'region',
|
|
|
|
'macroregion',
|
|
|
|
'country',
|
|
|
|
'dependency'
|
|
|
|
];
|
|
|
|
|
|
|
|
// these layers are strictly used to drive one special case:
|
|
|
|
// - when there was a borough explicitly supplied
|
|
|
|
// for example, if the user passed borough=manhattan and city=new york
|
|
|
|
// then we want to preserve just boroughs if they're most granular and throw away
|
|
|
|
// city results. In the usual case where no borough is passed, the city value
|
|
|
|
// is looked up as a borough in the off chance that the user passed
|
|
|
|
// city=Manhattan
|
|
|
|
const explicit_borough_layers = [
|
|
|
|
'venue',
|
|
|
|
'address',
|
|
|
|
'street',
|
|
|
|
'postalcode',
|
|
|
|
'neighbourhood',
|
|
|
|
'borough',
|
|
|
|
'locality',
|
|
|
|
'localadmin',
|
|
|
|
'county',
|
|
|
|
'macrocounty',
|
|
|
|
'region',
|
|
|
|
'macroregion',
|
|
|
|
'country',
|
|
|
|
'dependency'
|
|
|
|
];
|
|
|
|
|
|
|
|
// this helper method returns `true` if every result has a matched_query
|
|
|
|
// starting with `fallback.`
|
|
|
|
function isFallbackQuery(results) {
|
|
|
|
return results.every(function(result) {
|
|
|
|
return result.hasOwnProperty('_matched_queries') &&
|
|
|
|
!_.isEmpty(result._matched_queries) &&
|
|
|
|
_.startsWith(result._matched_queries[0], 'fallback.');
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function hasRecordsAtLayers(results, layer) {
|
|
|
|
return results.some( (result) => {
|
|
|
|
if (_.isArray(layer)) {
|
|
|
|
return layer.some( (sublayer) => {
|
|
|
|
return result._matched_queries[0] === 'fallback.' + sublayer;
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
return result._matched_queries[0] === 'fallback.' + layer;
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function retainRecordsAtLayers(results, layer) {
|
|
|
|
return results.filter( (result) => {
|
|
|
|
if (_.isArray(layer)) {
|
|
|
|
return layer.some( (sublayer) => {
|
|
|
|
return result._matched_queries[0] === 'fallback.' + sublayer;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return result._matched_queries[0] === 'fallback.' + layer;
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function getLayers(parsed_text) {
|
|
|
|
if (parsed_text && parsed_text.hasOwnProperty('borough')) {
|
|
|
|
return explicit_borough_layers;
|
|
|
|
}
|
|
|
|
return layers;
|
|
|
|
}
|
|
|
|
|
|
|
|
function setup() {
|
|
|
|
return function trim(req, res, next) {
|
|
|
|
// don't do anything if there are no results or there are non-fallback.* named queries
|
|
|
|
// there should never be a mixture of fallback.* and non-fallback.* named queries
|
|
|
|
if (_.isUndefined(res.data) || !isFallbackQuery(res.data)) {
|
|
|
|
return next();
|
|
|
|
}
|
|
|
|
|
|
|
|
const layers = getLayers(req.clean.parsed_text);
|
|
|
|
|
|
|
|
// start at the most granular possible layer. if there are results at a layer
|
|
|
|
// then remove everything not at that layer.
|
|
|
|
layers.forEach( (layer) => {
|
|
|
|
if (hasRecordsAtLayers(res.data, layer )) {
|
|
|
|
res.data = retainRecordsAtLayers(res.data, layer);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
next();
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = setup;
|