From 3894db0824bbb0023bce8402f1c4f578504fb8ce Mon Sep 17 00:00:00 2001 From: Geo Halkiadakis Date: Mon, 15 Apr 2024 18:41:27 +0300 Subject: refactoring routes; added weighted exact and partial match routines --- app.js | 17 +++--- benchmark/match-str.js | 48 +++++++++++----- paths.js | 147 ------------------------------------------------- routes/dev.js | 103 ++++++++++++++++++++++++++++++++++ routes/index.js | 26 +++++++++ routes/v1.js | 51 +++++++++++++++++ utils/match-util.js | 38 ++++++++++++- 7 files changed, 260 insertions(+), 170 deletions(-) delete mode 100644 paths.js create mode 100644 routes/dev.js create mode 100644 routes/index.js create mode 100644 routes/v1.js diff --git a/app.js b/app.js index aa79f62..c0392e6 100644 --- a/app.js +++ b/app.js @@ -1,19 +1,18 @@ // app.js -const Koa = require('koa'); -const { koaBody } = require('koa-body'); +const Koa = require('koa'); +// const { koaBody } = require('koa-body'); require('dotenv').config(); +// define app const app = new Koa(); // middleware -app.use(koaBody()); - -// Require the routers -let paths = require('./paths.js'); +// app.use(koaBody()); -// use the routes -app.use(paths.routes()); +// Require routes +let _r = require('./routes'); +app.use(_r.routes()).use(_r.allowedMethods()); -// app.listen(3000); +// start server listening on APP_PORT app.listen(process.env.APP_PORT); diff --git a/benchmark/match-str.js b/benchmark/match-str.js index fa802c1..2114f41 100644 --- a/benchmark/match-str.js +++ b/benchmark/match-str.js @@ -2,6 +2,7 @@ var microtime = require('microtime'); const match = require('../utils/match-util.js'); const memory_usage = require('../utils/mem-usage.js'); +const kb = require('../utils/kb-util.js'); const products = require('../data/products.json'); @@ -25,27 +26,48 @@ function run(query) { } } -// sumple search implementation -function matchQuery(q) { +// sample search implementation +function matchQuery(query) { var result = []; + let q = kb.keyboardize(query); + for(let i = 0; i < products.length; i++) { + + let rate = 0; let found = false; - let similarity = 0; - products[i].kb.split(' ').forEach( w => { - let sim = match.resemblance(q, w, 2); - if (sim > 0.5) { - found = true; - similarity = (sim > similarity) ? sim : similarity; - } - }); + let x; + let src = products[i].kb.split(' '); // (array) source of product (key-)words + if ((x = match.weighted_exact(q, src)) > 0) { + // console.log(products[i].kb, x); + rate = 5.0 + x; + found = true; + } + else if ((x = match.weighted_partial(q, src)) > 0) { + rate = 3.0 + x; + found = true; + } + else { + let similarity = 0; + src.forEach( w => { + let sim = match.resemblance(q, w, 2); + if (sim > 0.6) { + found = true; + similarity = (sim > similarity) ? sim : similarity; + } + }); + if (found) { + rate = 2 * similarity; + } + } + if (found) { - products[i].similarity = similarity; + products[i].rate = rate; result.push(products[i]); - } + } } - return result + return result.sort((a,b) => b.rate - a.rate).slice(0, 48);; } diff --git a/paths.js b/paths.js deleted file mode 100644 index 6e5fbef..0000000 --- a/paths.js +++ /dev/null @@ -1,147 +0,0 @@ -/** - * defines routes - * exports router - */ - -const Router = require('koa-router'); -const urler = require('./utils/url-util'); - -const data = require('./pieces/prepare-streams'); - -const bench = require('./benchmark/find.js'); -const matchStr = require('./benchmark/match-str.js'); - - -// Prefix all routes with: /items -const router = new Router({ - // prefix: '/items' -}); - - - -/* simple route example - - let items = [ - { id: 100, iname: 'Quartz Analog Wrist Watch', price: 'US $4.99'}, - { id: 101, iname: 'Leather Peep Pump Heels', price: 'US $33.56'}, - { id: 102, iname: 'Apple iPod', price: 'US $219.99'}, - { id: 103, iname: 'Prince Phantom 97P Tennnis Racket', price: 'US $50.00'}, - ]; - - // Routes - - router.get('/items', (ctx, next) => { - ctx.body = items; - next(); - }); - - router.get('/items/:id', (ctx, next) => { - let getCurrentItem = items.filter(function(item) { - if (item.id == ctx.params.id) { - return true; - } - }); - if (getCurrentItem.length) { - ctx.body = getCurrentItem[0]; - } else { - ctx.response.status = 404; - ctx.body = 'Item Not Found'; - } - next(); - }); -*/ - - -// NOTE: set timeout per route (how-to) -// https://stackoverflow.com/questions/66634123/how-to-add-individual-timeout-value-per-specific-route-using-node-js - - -// Routes - -router.get('/', (ctx, next) => { - ctx.body = { - success: true, - title: 'oseine', - description: 'oseine search engine is not elastic', - message: 'where are you now?' - } - next(); -}) - -router.get('/search', (ctx, next) => { - ctx.body = []; - next(); -}); - -router.get('/search/:title', (ctx, next) => { - // console.log(ctx); - const kb = require('./utils/kb-util.js'); - let words = kb.keyboardize(kb.clean(ctx.params.title)).split(' '); - ctx.body = { - params: ctx.params, - results: words, - nxt: next - }; - next(); -}); - - - -/** other routes - * ///////////////////////////////////////////////////////////////////////////// - * + /suggest/ - * + /prepare (products, keywords, linked-terms etc) - * + /stats - */ - - - -// TEST routes -//////////////////////////////////////////////////////////////////////////////// - -router.get('/test', (ctx, next) => { // easy test route - // test anything ... - ctx.body = { params: urler.struct(ctx.request, ctx.url), ctx: ctx } - next(); -}); - -router.get('/test/do-data', (ctx, next) => { // easy test route - // test anything ... - // data.create( - // 'products', - // 'https://storage.googleapis.com/pythia-files/uploads/json/emarket-products.json' - // ); - data.load_products( - 'https://storage.googleapis.com/pythia-files/uploads/json/emarket-products.json' - ); - ctx.body = { success: true, operation: 'create new data' }; - next(); -}); - -// benchmark routes; -// shall be removed from production -//////////////////////////////////////////////////////////////////////////////// - -/// router.get('/test/json', (ctx, next) => { // easy test route -/// // test anything ... -/// ctx.body = { t: + new Date(), p: products }; -/// next(); -/// }); - -router.get('/bench/find', (ctx, next) => { // easy test route - // test anything ... - ctx.body = bench.compare(); - next(); -}); - -router.get('/bench/match/:title', (ctx, next) => { // easy test route - // test anything ... [ query = 'solokata' ] - // ctx.body = bench.compare(); - ctx.body = matchStr.run(ctx.params.title); - next(); -}); - - -// export routes - -module.exports = router; diff --git a/routes/dev.js b/routes/dev.js new file mode 100644 index 0000000..3f142d0 --- /dev/null +++ b/routes/dev.js @@ -0,0 +1,103 @@ +/** + * defines routes + * exports router + */ + +const Router = require('koa-router'); +const urler = require('../utils/url-util.js'); + +const data = require('../pieces/prepare-streams.js'); + +const bench = require('../benchmark/find.js'); +const matchStr = require('../benchmark/match-str.js'); + + +// Prefix all routes with: /items +const router = new Router(); + + + +// Routes + +/* simple route example + + let items = [ + { id: 100, iname: 'Quartz Analog Wrist Watch', price: 'US $4.99'}, + { id: 101, iname: 'Leather Peep Pump Heels', price: 'US $33.56'}, + { id: 102, iname: 'Apple iPod', price: 'US $219.99'}, + { id: 103, iname: 'Prince Phantom 97P Tennnis Racket', price: 'US $50.00'}, + ]; + + router.get('/items', (ctx, next) => { + ctx.body = items; + next(); + }); + + router.get('/items/:id', (ctx, next) => { + let getCurrentItem = items.filter(function(item) { + if (item.id == ctx.params.id) { + return true; + } + }); + if (getCurrentItem.length) { + ctx.body = getCurrentItem[0]; + } else { + ctx.response.status = 404; + ctx.body = 'Item Not Found'; + } + next(); + }); +*/ + + +// NOTE: set timeout per route (how-to) +// https://stackoverflow.com/questions/66634123/how-to-add-individual-timeout-value-per-specific-route-using-node-js + + +// TEST routes +//////////////////////////////////////////////////////////////////////////////// + +router.get('/test', (ctx) => { // easy test route + // test anything ... + ctx.body = { params: urler.struct(ctx.request, ctx.url), ctx: ctx } +}); + +router.get('/test/do-data', (ctx) => { // easy test route + // test anything ... + // data.create( + // 'products', + // 'https://storage.googleapis.com/pythia-files/uploads/json/emarket-products.json' + // ); + data.load_products( + 'https://storage.googleapis.com/pythia-files/uploads/json/emarket-products.json' + ); + ctx.body = { success: true, operation: 'create new data' }; +}); + +// benchmark routes; +// shall be removed from production +//////////////////////////////////////////////////////////////////////////////// + +/// router.get('/test/json', (ctx, next) => { // easy test route +/// // test anything ... +/// ctx.body = { t: + new Date(), p: products }; +/// next(); +/// }); + +router.get('/bench/find', (ctx) => { // easy test route + // test anything ... + ctx.body = bench.compare(); + // next(); +}); + +router.get('/bench/match/:title', (ctx) => { // easy test route + // test anything ... [ query = 'solokata' ] + // ctx.body = bench.compare(); + ctx.body = matchStr.run(ctx.params.title); + // next(); +}); + + +// export routes + +module.exports = router; diff --git a/routes/index.js b/routes/index.js new file mode 100644 index 0000000..e3d6230 --- /dev/null +++ b/routes/index.js @@ -0,0 +1,26 @@ +/** + * routes (index) + */ +const Router = require('koa-router'); + +const router = new Router(); + +// Require grouped routes + +let v1 = require('./v1.js'); +router.use(v1.routes()); + +let dev_paths = require('./dev.js'); +router.use(dev_paths.routes()); + +// define default route +router.get('/', (ctx) => { + ctx.body = { + success: true, + title: 'oseine', + description: 'oseine search engine is not elastic', + message: 'where are you now?' + } +}) + +module.exports = router; diff --git a/routes/v1.js b/routes/v1.js new file mode 100644 index 0000000..859f7b0 --- /dev/null +++ b/routes/v1.js @@ -0,0 +1,51 @@ +/** + * api v1 routes + */ + +const Router = require('koa-router'); + + +// Prefix all routes with: /items +const router = new Router({ + prefix: '/v1' +}); + + + +// Routes + + + +router.get('/search', (ctx) => { + ctx.body = { success: true, results: [] }; +}); + +router.get('/search/:title', (ctx) => { + let result = [ + { id: 10, w: 'Ένα Προϊόν' }, + { id: 15, w: 'Άλλο Προϊόν' }, + { id: 20, w: 'Προϊόν 3' }, + { id: 25, w: 'Προϊόν 4' } + ] + // console.log(ctx); + /// const kb = require('./utils/kb-util.js'); + /// let words = kb.keyboardize(kb.clean(ctx.params.title)).split(' '); + /// ctx.body = { + /// params: ctx.params, + /// results: words, + /// nxt: next + /// }; + ctx.body = result; +}); + + +/** TODO: ?? other routes ?? + * ///////////////////////////////////////////////////////////////////////////// + * + /suggest/ + * + /prepare (products, keywords, linked-terms etc) + * + /stats + */ + + +// export routes +module.exports = router; diff --git a/utils/match-util.js b/utils/match-util.js index a13fddb..b6b9fc0 100644 --- a/utils/match-util.js +++ b/utils/match-util.js @@ -64,7 +64,6 @@ const resemblance = (a, b, n) => { /** is_exact_match - * * check if a searching string -> query (string/latin in kb-format) * matches exactly an item of the array of synonyms -> chkArr (array of utf-8/strings) * @@ -84,9 +83,46 @@ function partial( query, chkArr ) { return found; } +/** is exact match + weight rating + * @returns {float} weight rates depth of array when a match is found + */ +function weighted_exact( query, chkArr ) { + let weight = 0; // closer to left/begin rating + let len = chkArr.length; + for(let i = 0; i < len ; i++) { + if (chkArr[i] == query) { + // rating weights array depth + weight = (len - i + 1.0) / len; + // console.log(i, weight, query); + break; + } + } + return weight; +} + + +/** is partial match + weight rating + * @returns {float} weight rates both match position and depth of match + */ +function weighted_partial( query, chkArr ) { + let rate = 0; + let weight = 0; + let len = chkArr.length; + for(let i = 0; i < len ; i++) { + let chk = chkArr[i].indexOf(query) + if (chk != -1) { + rate = (len -i +1.0) / (len + 2.0 * chk); + weight = rate > weight ? rate : weight; + } + } + return weight; +} + module.exports = { exact, partial, + weighted_exact, + weighted_partial, similarity, resemblance } -- cgit v1.2.3