forked from pelias/api
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtrimByGranularityStructured.js
129 lines (115 loc) · 3.85 KB
/
trimByGranularityStructured.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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;