@@ -95,6 +95,240 @@ This guide shows how to implement an e-commerce search pipeline that:
9595]
9696```
9797
98+ Script: generateRequest.js
99+
100+ ``` js
101+ function handleRequest () {
102+ const queryTerm = context .envs .query ;
103+
104+ /* verifying that format of query is as expected before modifying */
105+ const requestBody = JSON .parse (context .request .body );
106+
107+ if (! requestBody .query || requestBody .query .length == 0 ) {
108+ /* Update the queries with the defaultQuery */
109+ requestBody .query .forEach ((queryEach , queryIndex ) => {
110+ console .log (" query id each: " , queryEach[" id" ]);
111+ if (
112+ queryEach .type == " suggestion" ||
113+ queryEach .type == " search" ||
114+ queryEach .type == undefined ||
115+ queryEach .type == " term"
116+ ) {
117+ requestBody .query [queryIndex] = injectDefaultQuery (queryEach);
118+ }
119+ });
120+
121+ return {
122+ request: { body: JSON .stringify (requestBody) }
123+ };
124+ }
125+
126+ /* If there is only one query and it is a suggestion type of
127+ * query, we need to inject a `result` id query where there will
128+ * be the defaultQuery and the current `search` query will react to
129+ * it.
130+ **/
131+ if (
132+ requestBody .query .length == 1 &&
133+ requestBody .query [0 ].type == " suggestion" &&
134+ requestBody .query [0 ].id == " search"
135+ ) {
136+ requestBody .query [0 ].id = " result" ;
137+ requestBody .query [0 ].execute = false ;
138+ let resultQuery = {
139+ ... requestBody .query [0 ],
140+ id: " search" ,
141+ react: { and: [" result" ] },
142+ execute: true
143+ };
144+ requestBody .query .push (resultQuery);
145+ }
146+
147+ /* Iterate through and if there is a suggestion or search query, update it
148+ * with the extra fields.
149+ **/
150+ console .log (" query length: " , requestBody .query .length );
151+ requestBody .query .forEach ((queryEach , queryIndex ) => {
152+ console .log (" query id: " , queryEach[" id" ]);
153+
154+ /* If there is a term query of id result, change it to `search` type */
155+ if (queryEach .id == " result" && queryEach .type == " term" ) {
156+ queryEach .type = " search" ;
157+ }
158+
159+ if (queryEach .type == " suggestion" ) {
160+ console .log (" query id: " , queryEach[" id" ]);
161+ requestBody .query [queryIndex] = updateSuggestionsQuery (queryTerm, queryEach);
162+ } else if (queryEach .type == " search" ) {
163+ requestBody .query [queryIndex] = updateSearchQuery (queryTerm, queryEach);
164+ console .log (JSON .stringify (requestBody .query [queryIndex]));
165+ }
166+
167+ /* Inject the defaultQuery
168+ *
169+ * If there is only one query, we don't want defaultQuery to
170+ * be injected since that will override the main query.
171+ */
172+ console .log (queryEach .id , " : " , queryEach .type );
173+ if (
174+ requestBody .query .length > 1 &&
175+ (queryEach .type == " suggestion" ||
176+ queryEach .type == " search" ||
177+ queryEach .type == undefined ||
178+ queryEach .type == " term" )
179+ ) {
180+ requestBody .query [queryIndex] = injectDefaultQuery (queryEach);
181+ }
182+ });
183+
184+ return {
185+ request: { body: JSON .stringify (requestBody) }
186+ };
187+ }
188+
189+ function updateSuggestionsQuery (queryTerm , suggestionQuery ) {
190+ if (! queryTerm) queryTerm = " " ;
191+ const queryLength = queryTerm .length ;
192+
193+ const dataFields = [
194+ { field: " longDescription" , weight: 0.5 },
195+ { field: " name" , weight: 3 },
196+ { field: " name.autosuggest" ,weight: 0.1 },
197+ { field: " name.search" , weight: 0.01 },
198+ { field: " class" , weight: 5 },
199+ { field: " class.search" , weight: 1 }
200+ ];
201+
202+ suggestionQuery[" dataField" ] = dataFields;
203+ suggestionQuery[" queryFormat" ] = " and" ;
204+ suggestionQuery[" includeFields" ] = [
205+ " class" , " globalRank" , " categoryRank" , " name" ,
206+ " shortDescription" , " longDescription" , " salePrice" , " image"
207+ ];
208+ suggestionQuery[" distinctField" ] = " class.keyword" ;
209+ suggestionQuery[" distinctFieldConfig" ] = {
210+ inner_hits: {
211+ name: " rel" ,
212+ size: 2 ,
213+ _source: [
214+ " class" , " globalRank" , " categoryRank" , " name" ,
215+ " shortDescription" , " longDescription" , " salePrice" , " image"
216+ ]
217+ }
218+ };
219+
220+ console .log (" query id: " , suggestionQuery[" id" ]);
221+
222+ /* Responding to the user intent based on the query length */
223+ if (queryLength <= 4 ) {
224+ // user is perhaps checking out the search
225+ suggestionQuery .enablePopularSuggestions = true ;
226+ suggestionQuery .enableFeaturedSuggestions = true ;
227+ suggestionQuery .enableRecentSuggestions = true ;
228+ } else if (queryLength >= 5 && queryLength <= 9 ) {
229+ // user is looking for something specific
230+ suggestionQuery .enablePopularSuggestions = false ;
231+ suggestionQuery .enableFeaturedSuggestions = false ;
232+ suggestionQuery .enableRecentSuggestions = true ;
233+ } else if (queryLength >= 10 ) {
234+ // user is definitely looking for something specific
235+ suggestionQuery .enableRecentSuggestions = false ;
236+ suggestionQuery .enablePopularSuggestions = false ;
237+ suggestionQuery .enableFeaturedSuggestions = false ;
238+ }
239+
240+ return suggestionQuery;
241+ }
242+
243+ function updateSearchQuery (queryTerm , searchQuery ) {
244+ const dataFields = [
245+ { field: " longDescription" , weight: 1 },
246+ { field: " name" , weight: 3 },
247+ { field: " name.autosuggest" ,weight: 0.2 },
248+ { field: " name.delimiter" , weight: 1 },
249+ { field: " name.search" , weight: 0.1 },
250+ { field: " class" , weight: 5 },
251+ { field: " class.search" , weight: 1 }
252+ ];
253+
254+ searchQuery[" dataField" ] = dataFields;
255+ searchQuery[" queryFormat" ] = " and" ;
256+ searchQuery[" includeFields" ] = [
257+ " class" , " globalRank" , " categoryRank" , " name" ,
258+ " shortDescription" , " longDescription" , " salePrice" , " image"
259+ ];
260+ searchQuery[" distinctField" ] = " class.keyword" ;
261+ searchQuery[" distinctFieldConfig" ] = {
262+ inner_hits: {
263+ name: " rel" ,
264+ size: 2 ,
265+ _source: [
266+ " class" , " globalRank" , " categoryRank" , " name" ,
267+ " shortDescription" , " longDescription" , " salePrice" , " image"
268+ ]
269+ }
270+ };
271+
272+ /* searchQuery['sortField'] = ['globalRank', 'categoryRank'];
273+ searchQuery['sortBy'] = 'asc'; */
274+
275+ console .log (" query is: " , searchQuery[" id" ]);
276+ console .log (" datafields are: " , JSON .stringify (searchQuery[" dataField" ]));
277+
278+ if (! queryTerm) return searchQuery;
279+
280+ const queryLength = queryTerm .length ;
281+ if (! queryTerm || queryLength === 0 ) return searchQuery;
282+
283+ if (
284+ queryLength >= 20 &&
285+ queryTerm .split (" " ).length >= 4 &&
286+ queryTerm[queryTerm .length - 1 ] == " ?"
287+ ) {
288+ searchQuery[" enableAI" ] = true ;
289+ searchQuery[" execute" ] = true ;
290+ }
291+
292+ return searchQuery;
293+ }
294+
295+ function injectDefaultQuery (query ) {
296+ /* Use exists query to boost score of results that contain
297+ either globalRank or categoryRank */
298+ console .log (" existing default query: " , JSON .stringify (query .defaultQuery ));
299+ if (
300+ (! query .defaultQuery || JSON .stringify (query .defaultQuery ) == " {}" ) &&
301+ query .execute
302+ ) {
303+ query .defaultQuery = {
304+ query: {
305+ bool: {
306+ must_not: [
307+ { match: { class: " ACCY" } },
308+ { match: { class: " APPLECARE" } },
309+ { match: { class: " ACCESSORIES" } },
310+ { match: { class: " ACCESS." } },
311+ { match: { class: " IPAD MONTHLY" } },
312+ { match: { class: " SVC FEE" } }
313+ ],
314+ should: [
315+ { range: { salePrice: { gte: 499 , boost: 5 } } },
316+ { range: { salePrice: { gte: 299 , boost: 5 } } },
317+ { range: { salePrice: { gte: 99 , boost: 4 } } },
318+ { range: { categoryRank: { lte: 10 , boost: 2 } } },
319+ { range: { categoryRank: { lte: 5 , boost: 2 } } },
320+ { range: { categoryRank: { lte: 1 , boost: 1 } } },
321+ { range: { globalRank: { lte: 10 , boost: 5 } } },
322+ { range: { salePrice: { gte: 50 , boost: 0.01 } } }
323+ ]
324+ }
325+ }
326+ };
327+ }
328+ return query;
329+ }
330+ ```
331+
98332### 3) First pass: RS → ES
99333
100334``` json
@@ -127,6 +361,81 @@ This guide shows how to implement an e-commerce search pipeline that:
127361]
128362```
129363
364+ Script: checkTypo.js
365+
366+ ``` js
367+ function handleRequest () {
368+ const queryTerm = context .envs .query ;
369+
370+ if (queryTerm && queryTerm .length < 5 ) {
371+ return {};
372+ }
373+
374+ const globalRankWeight = 0.2 ;
375+ const categoryRankWeight = 0.5 ;
376+
377+ const responseBody = JSON .parse (context .response .body );
378+ if (
379+ responseBody .search ? .hits ? .hits ? .length != 0 &&
380+ responseBody .result ? .hits ? .hits ? .length != 0
381+ ) {
382+ /* We have hits: re-score using rank signals */
383+ const results = responseBody .result ? .hits ? .hits ;
384+ if (results) {
385+ results .forEach ((hitEach , hitIndex ) => {
386+ if (! hitEach ._source .categoryRank && ! hitEach ._source .globalRank ) {
387+ hitEach ._source .categoryRank = 10 ;
388+ }
389+
390+ let isGlobalRank = false ;
391+
392+ if (hitEach ._source .globalRank ) {
393+ const ogScore = hitEach ._score ;
394+ results[hitIndex]._score =
395+ ogScore - globalRankWeight * hitEach ._source .globalRank ;
396+ isGlobalRank = true ;
397+ }
398+
399+ if (hitEach ._source .categoryRank && ! isGlobalRank) {
400+ const ogScore = hitEach ._score ;
401+ results[hitIndex]._score =
402+ ogScore - categoryRankWeight * hitEach ._source .categoryRank ;
403+ }
404+ });
405+
406+ /* Sort descending by updated score */
407+ results .sort ((a , b ) => b ._score - a ._score );
408+ responseBody .result .hits .hits = results;
409+
410+ return {
411+ response: { body: JSON .stringify (responseBody) }
412+ };
413+ }
414+ return {};
415+ }
416+
417+ /* No hits: enable fuzzy fallback and trigger research pass */
418+ const rsBody = JSON .parse (context .envs [" ORIGINAL_RS_BODY" ]);
419+
420+ let fuzziness = 1 ;
421+ if (queryTerm .length >= 5 && queryTerm .length <= 9 ) {
422+ fuzziness = 1 ;
423+ } else {
424+ fuzziness = 2 ;
425+ }
426+
427+ rsBody .query ? .forEach ((queryEach , queryIndex ) => {
428+ queryEach .fuzziness = fuzziness;
429+ rsBody .query [queryIndex] = queryEach;
430+ });
431+
432+ return {
433+ request: { body: JSON .stringify (rsBody) },
434+ envs: { research: true , ... context .envs }
435+ };
436+ }
437+ ` ` `
438+
130439### 5) Second pass (conditional)
131440
132441` ` ` json
0 commit comments