diff --git a/middleware/confidenceScore.js b/middleware/confidenceScore.js index 04b87b7d..d04e0ab9 100644 --- a/middleware/confidenceScore.js +++ b/middleware/confidenceScore.js @@ -13,17 +13,21 @@ var stats = require('stats-lite'); var logger = require('pelias-logger').get('api'); +var check = require('check-types'); var RELATIVE_SCORES = true; function setup(peliasConfig) { - RELATIVE_SCORES = peliasConfig.hasOwnProperty('relativeScores') ? peliasConfig.relativeScores : true; + if (check.assigned(peliasConfig)) { + RELATIVE_SCORES = peliasConfig.hasOwnProperty('relativeScores') ? peliasConfig.relativeScores : true; + } return computeScores; } function computeScores(req, res, next) { // do nothing if no result data set - if (!res || !res.data || !res.meta) { + if (check.undefined(req.clean) || check.undefined(res) || + check.undefined(res.data) || check.undefined(res.meta)) { return next(); } @@ -71,6 +75,7 @@ function computeConfidenceScore(req, mean, stdev, hit) { // TODO: look at categories and location hit.confidence /= checkCount; + hit.confidence = Number((hit.confidence).toFixed(3)); logger.debug('[confidence]:', hit.confidence, hit.name.default); @@ -78,16 +83,16 @@ function computeConfidenceScore(req, mean, stdev, hit) { } function checkForDealBreakers(req, hit) { - if (!req.clean.parsed_text) { + if (check.undefined(req.clean.parsed_text)) { return false; } - if (req.clean.parsed_text.state && req.clean.parsed_text.state !== hit.admin1_abbr) { + if (check.assigned(req.clean.parsed_text.state) && req.clean.parsed_text.state !== hit.admin1_abbr) { logger.debug('[confidence][deal-breaker]: state !== admin1_abbr'); return true; } - if (req.clean.parsed_text.postalcode && req.clean.parsed_text.postalcode !== hit.zip) { + if (check.assigned(req.clean.parsed_text.postalcode) && req.clean.parsed_text.postalcode !== hit.zip) { logger.debug('[confidence][deal-breaker]: postalcode !== zip'); return true; } @@ -117,7 +122,8 @@ function checkDistanceFromMean(score, mean, stdev) { */ function checkName(text, parsed_text, hit) { // parsed_text name should take precedence if available since it's the cleaner name property - if (parsed_text && parsed_text.name && hit.name.default.toLowerCase() === parsed_text.name.toLowerCase()) { + if (check.assigned(parsed_text) && check.assigned(parsed_text.name) && + hit.name.default.toLowerCase() === parsed_text.name.toLowerCase()) { return 1; } @@ -139,7 +145,9 @@ function checkName(text, parsed_text, hit) { * @returns {number} */ function checkQueryType(text, hit) { - if (!!text.number && (!hit.address || (hit.address && !hit.address.number))) { + if (check.assigned(text) && check.assigned(text.number) && + (check.undefined(hit.address) || + (check.assigned(hit.address) && check.undefined(hit.address.number)))) { return 0; } return 1; @@ -156,22 +164,23 @@ function checkQueryType(text, hit) { function propMatch(textProp, hitProp, expectEnriched) { // both missing, but expect to have enriched value in result => BAD - if (!textProp && !hitProp && expectEnriched) { return 0; } + if (check.undefined(textProp) && check.undefined(hitProp) && check.assigned(expectEnriched)) { return 0; } // both missing, and no enrichment expected => GOOD - if (!textProp && !hitProp) { return 1; } + if (check.undefined(textProp) && check.undefined(hitProp)) { return 1; } // text has it, result doesn't => BAD - if (textProp && !hitProp) { return 0; } + if (check.assigned(textProp) && check.undefined(hitProp)) { return 0; } // text missing, result has it, and enrichment is expected => GOOD - if (!textProp && hitProp && expectEnriched) { return 1; } + if (check.undefined(textProp) && check.assigned(hitProp) && check.assigned(expectEnriched)) { return 1; } // text missing, result has it, enrichment not desired => 50/50 - if (!textProp && hitProp) { return 0.5; } + if (check.undefined(textProp) && check.assigned(hitProp)) { return 0.5; } // both present, values match => GREAT - if (textProp && hitProp && textProp.toString().toLowerCase() === hitProp.toString().toLowerCase()) { return 1; } + if (check.assigned(textProp) && check.assigned(hitProp) && + textProp.toString().toLowerCase() === hitProp.toString().toLowerCase()) { return 1; } // ¯\_(ツ)_/¯ return 0.7; @@ -200,7 +209,7 @@ function checkAddress(text, hit) { var checkCount = 5; var res = 0; - if (text && text.number && text.street) { + if (check.assigned(text) && check.assigned(text.number) && check.assigned(text.street)) { res += propMatch(text.number, (hit.address ? hit.address.number : null), false); res += propMatch(text.street, (hit.address ? hit.address.street : null), false); res += propMatch(text.postalcode, (hit.address ? hit.address.zip: null), true); diff --git a/test/unit/middleware/confidenceScore.js b/test/unit/middleware/confidenceScore.js new file mode 100644 index 00000000..c32a37d4 --- /dev/null +++ b/test/unit/middleware/confidenceScore.js @@ -0,0 +1,82 @@ +var confidenceScore = require('../../../middleware/confidenceScore')(); + +module.exports.tests = {}; + +module.exports.tests.confidenceScore = function(test, common) { + + test('empty res and req should not throw exception', function(t) { + try { + confidenceScore({}, {}, function() {}); + t.pass('no exception'); + } + catch (e) { + t.fail('an exception should not have been thrown'); + } + finally { + t.end(); + } + + }); + + test('res.results without parsed_text should not throw exception', function(t) { + var req = {}; + var res = { + data: [{ + name: 'foo' + }], + meta: [10] + }; + + try { + confidenceScore(req, res, function() {}); + t.pass('no exception'); + } + catch (e) { + t.fail('an exception should not have been thrown'); + console.log(e.stack); + } + finally { + t.end(); + } + + }); + + + test('res.results without parsed_text should not throw exception', function(t) { + var req = { + clean: { text: 'test name1' } + }; + var res = { + data: [{ + _score: 10, + found: true, + value: 1, + center_point: { lat: 100.1, lon: -50.5 }, + name: { default: 'test name1' }, + admin0: 'country1', admin1: 'state1', admin2: 'city1' + }, { + value: 2, + center_point: { lat: 100.2, lon: -51.5 }, + name: { default: 'test name2' }, + admin0: 'country2', admin1: 'state2', admin2: 'city2', + _score: 20 + }], + meta: {scores: [10]} + }; + + confidenceScore(req, res, function() {}); + t.equal(res.data[0].confidence, 0.6, 'score was set'); + t.end(); + }); + +}; + +module.exports.all = function (tape, common) { + function test(name, testFunction) { + return tape('[middleware] confidenceScore: ' + 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 977c1c58..74202992 100644 --- a/test/unit/run.js +++ b/test/unit/run.js @@ -29,6 +29,7 @@ var tests = [ require('./sanitiser/_geo_common'), require('./middleware/distance'), require('./middleware/confidenceScoreReverse'), + require('./middleware/confidenceScore'), require('./sanitiser/_size'), require('./sanitiser/_single_scalar_parameters'), require('./sanitiser/_geo_reverse'),