From 17f7b481b86916950b5daa99087da65542ff4fc6 Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Fri, 1 Mar 2024 16:08:13 -0800 Subject: [PATCH 01/27] sort itis response to place exact matches first --- api/package-lock.json | 1281 ++++++++++++++++++++++-------- api/src/services/itis-service.ts | 68 +- 2 files changed, 1007 insertions(+), 342 deletions(-) diff --git a/api/package-lock.json b/api/package-lock.json index 9fda741d..ff4bdc6b 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -36,9 +36,9 @@ "dev": true }, "@babel/core": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz", - "integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz", + "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==", "dev": true, "requires": { "@ampproject/remapping": "^2.2.0", @@ -46,11 +46,11 @@ "@babel/generator": "^7.23.6", "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.7", - "@babel/parser": "^7.23.6", - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.7", - "@babel/types": "^7.23.6", + "@babel/helpers": "^7.24.0", + "@babel/parser": "^7.24.0", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.0", + "@babel/types": "^7.24.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -255,14 +255,14 @@ "dev": true }, "@babel/helpers": { - "version": "7.23.8", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.8.tgz", - "integrity": "sha512-KDqYz4PiOWvDFrdHLPhKtCThtIcKVy6avWD2oG4GEvyQ+XDZwHD4YQd+H2vNMnq2rkdxsDkU82T+Vk8U/WXHRQ==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.0.tgz", + "integrity": "sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA==", "dev": true, "requires": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.7", - "@babel/types": "^7.23.6" + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.0", + "@babel/types": "^7.24.0" } }, "@babel/highlight": { @@ -320,20 +320,20 @@ } }, "@babel/parser": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", - "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz", + "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==", "dev": true }, "@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", "dev": true, "requires": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" }, "dependencies": { "@babel/code-frame": { @@ -390,9 +390,9 @@ } }, "@babel/traverse": { - "version": "7.23.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", - "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.0.tgz", + "integrity": "sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==", "dev": true, "requires": { "@babel/code-frame": "^7.23.5", @@ -401,8 +401,8 @@ "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.6", - "@babel/types": "^7.23.6", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -467,9 +467,9 @@ } }, "@babel/types": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", - "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", "dev": true, "requires": { "@babel/helper-string-parser": "^7.23.4", @@ -517,9 +517,9 @@ } }, "@elastic/transport": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@elastic/transport/-/transport-8.4.0.tgz", - "integrity": "sha512-Yb3fDa7yGD0ca3uMbL64M3vM1cE5h5uHmBcTjkdB4VpCasRNKSd09iDpwqX8zX1tbBtxcaKYLceKthWvPeIxTw==", + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@elastic/transport/-/transport-8.4.1.tgz", + "integrity": "sha512-/SXVuVnuU5b4dq8OFY4izG+dmGla185PcoqgK6+AJMpmOeY1QYVNbWtCwvSvoAANN5D/wV+EBU8+x7Vf9EphbA==", "requires": { "debug": "^4.3.4", "hpagent": "^1.0.0", @@ -598,9 +598,9 @@ } }, "@fastify/busboy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz", - "integrity": "sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==" }, "@humanwhocodes/config-array": { "version": "0.5.0", @@ -727,9 +727,9 @@ "dev": true }, "@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.4.tgz", + "integrity": "sha512-Oud2QPM5dHviZNn4y/WhhYKSXksv+1xLEIsNrAbGcFzUN3ubqWRFT5gwPchNc5NuzILOU4tPBDTZ4VwhL8Y7cw==", "dev": true, "requires": { "@jridgewell/set-array": "^1.0.1", @@ -738,15 +738,15 @@ } }, "@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true }, "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true }, "@jridgewell/sourcemap-codec": { @@ -756,9 +756,9 @@ "dev": true }, "@jridgewell/trace-mapping": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", - "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.23.tgz", + "integrity": "sha512-9/4foRoUKp8s96tSkh8DlAAc5A0Ty8vLXld+l9gjKKY6ckwI8G15f0hskGmuLZu78ZlGa1vtsfOa+lnB4vG6Jg==", "dev": true, "requires": { "@jridgewell/resolve-uri": "^3.1.0", @@ -920,9 +920,9 @@ } }, "@types/express-serve-static-core": { - "version": "4.17.41", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", - "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", + "version": "4.17.43", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz", + "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==", "requires": { "@types/node": "*", "@types/qs": "*", @@ -1043,9 +1043,9 @@ "dev": true }, "@types/qs": { - "version": "6.9.11", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", - "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==" + "version": "6.9.12", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.12.tgz", + "integrity": "sha512-bZcOkJ6uWrL0Qb2NAWKa7TBU+mJHPzhx9jjLL1KHF+XpzEcR7EXHvjbHlGtR/IsP1vyPrehuS6XqkmaePy//mg==" }, "@types/range-parser": { "version": "1.2.7", @@ -1202,9 +1202,9 @@ } }, "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -1285,9 +1285,9 @@ } }, "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -1517,13 +1517,47 @@ "dev": true }, "array-buffer-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", - "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "is-array-buffer": "^3.0.1" + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "dependencies": { + "call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + } + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + } } }, "array-each": { @@ -1610,18 +1644,53 @@ "dev": true }, "arraybuffer.prototype.slice": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", - "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", "dev": true, "requires": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "get-intrinsic": "^1.2.1", - "is-array-buffer": "^3.0.2", + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", "is-shared-array-buffer": "^1.0.2" + }, + "dependencies": { + "call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + } + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + } } }, "arrify": { @@ -1879,13 +1948,13 @@ "dev": true }, "browserslist": { - "version": "4.22.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", - "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001565", - "electron-to-chromium": "^1.4.601", + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" } @@ -1985,9 +2054,9 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, "caniuse-lite": { - "version": "1.0.30001576", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001576.tgz", - "integrity": "sha512-ff5BdakGe2P3SQsMsiqmt1Lc8221NR1VzHj5jXN5vBny9A6fpze94HiVV/n7XRosOlsShJcvMv5mdnpjOGCEgg==", + "version": "1.0.30001591", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001591.tgz", + "integrity": "sha512-PCzRMei/vXjJyL5mJtzNiUCKP59dm8Apqc3PH8gJkMnMXZGox93RbE76jHsmLwmIo6/3nsYIpJtx0O7u5PqFuQ==", "dev": true }, "cfb": { @@ -2058,9 +2127,9 @@ } }, "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, "requires": { "anymatch": "~3.1.2", @@ -2585,14 +2654,14 @@ "dev": true }, "define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, "requires": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" } }, "define-properties": { @@ -2779,9 +2848,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "electron-to-chromium": { - "version": "1.4.628", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.628.tgz", - "integrity": "sha512-2k7t5PHvLsufpP6Zwk0nof62yLOsCf032wZx7/q0mv8gwlXjhcxI3lz6f0jBr0GrnWKcm3burXzI3t5IrcdUxw==", + "version": "1.4.688", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.688.tgz", + "integrity": "sha512-3/tHg2ChPF00eukURIB8cSVt3/9oeS1oTUIEt3ivngBInUaEcBhG2VdyEDejhwQdR6SLqaiEAEc0dHS0V52pOw==", "dev": true }, "emoji-regex": { @@ -2836,61 +2905,74 @@ } }, "es-abstract": { - "version": "1.22.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", - "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", - "dev": true, - "requires": { - "array-buffer-byte-length": "^1.0.0", - "arraybuffer.prototype.slice": "^1.0.2", - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.5", - "es-set-tostringtag": "^2.0.1", + "version": "1.22.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.5.tgz", + "integrity": "sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", "es-to-primitive": "^1.2.1", "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.2", - "get-symbol-description": "^1.0.0", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", "globalthis": "^1.0.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "has-proto": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", "has-symbols": "^1.0.3", - "hasown": "^2.0.0", - "internal-slot": "^1.0.5", - "is-array-buffer": "^3.0.2", + "hasown": "^2.0.1", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", + "is-negative-zero": "^2.0.3", "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", + "is-shared-array-buffer": "^1.0.3", "is-string": "^1.0.7", - "is-typed-array": "^1.1.12", + "is-typed-array": "^1.1.13", "is-weakref": "^1.0.2", "object-inspect": "^1.13.1", "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "safe-array-concat": "^1.0.1", - "safe-regex-test": "^1.0.0", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.0", + "safe-regex-test": "^1.0.3", "string.prototype.trim": "^1.2.8", "string.prototype.trimend": "^1.0.7", "string.prototype.trimstart": "^1.0.7", - "typed-array-buffer": "^1.0.0", - "typed-array-byte-length": "^1.0.0", - "typed-array-byte-offset": "^1.0.0", - "typed-array-length": "^1.0.4", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.5", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.13" + "which-typed-array": "^1.1.14" }, "dependencies": { + "available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "requires": { + "possible-typed-array-names": "^1.0.0" + } + }, "call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dev": true, "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" } }, "function-bind": { @@ -2900,24 +2982,49 @@ "dev": true }, "get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, "requires": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" } }, + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0" + } + }, + "has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true + }, + "hasown": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "dev": true, + "requires": { + "function-bind": "^1.1.2" + } + }, "is-typed-array": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", - "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", "dev": true, "requires": { - "which-typed-array": "^1.1.11" + "which-typed-array": "^1.1.14" } }, "object-inspect": { @@ -2925,31 +3032,50 @@ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", "dev": true + } + } + }, + "es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "requires": { + "get-intrinsic": "^1.2.4" + }, + "dependencies": { + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, - "which-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", - "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", - "dev": true, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.4", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" } } } }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, "es-set-tostringtag": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", - "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", "dev": true, "requires": { - "get-intrinsic": "^1.2.2", - "has-tostringtag": "^1.0.0", - "hasown": "^2.0.0" + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" }, "dependencies": { "function-bind": { @@ -2959,16 +3085,35 @@ "dev": true }, "get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, "requires": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" } + }, + "has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.3" + } + }, + "hasown": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz", + "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==", + "dev": true, + "requires": { + "function-bind": "^1.1.2" + } } } }, @@ -2984,13 +3129,14 @@ } }, "es5-ext": { - "version": "0.10.62", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz", - "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==", + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", "dev": true, "requires": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", "next-tick": "^1.1.0" } }, @@ -3167,9 +3313,9 @@ } }, "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -3237,6 +3383,26 @@ "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==" }, + "esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "dev": true, + "requires": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "dependencies": { + "type": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", + "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", + "dev": true + } + } + }, "espree": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", @@ -3312,6 +3478,16 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -3862,9 +4038,9 @@ } }, "flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, "flush-write-stream": { @@ -3915,9 +4091,9 @@ "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, "follow-redirects": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", - "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==" + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==" }, "for-each": { "version": "0.3.3", @@ -4078,13 +4254,48 @@ "dev": true }, "get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "dependencies": { + "call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + } + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + } } }, "get-value": { @@ -4805,12 +5016,9 @@ } }, "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", + "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==" }, "has-bigints": { "version": "1.0.2", @@ -4828,7 +5036,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", - "dev": true, "requires": { "get-intrinsic": "^1.2.2" }, @@ -4836,15 +5043,14 @@ "function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", - "dev": true, + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "requires": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", @@ -5014,9 +5220,9 @@ "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" }, "ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true }, "ignore-by-default": { @@ -5081,34 +5287,14 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "internal-slot": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", - "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", "dev": true, "requires": { - "get-intrinsic": "^1.2.2", + "es-errors": "^1.3.0", "hasown": "^2.0.0", "side-channel": "^1.0.4" - }, - "dependencies": { - "function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true - }, - "get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", - "dev": true, - "requires": { - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - } - } } }, "interpret": { @@ -5156,14 +5342,13 @@ } }, "is-array-buffer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", - "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", "dev": true, "requires": { "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.0", - "is-typed-array": "^1.1.10" + "get-intrinsic": "^1.2.1" } }, "is-arrayish": { @@ -5292,9 +5477,9 @@ "dev": true }, "is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true }, "is-number": { @@ -5359,12 +5544,46 @@ } }, "is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", "dev": true, "requires": { - "call-bind": "^1.0.2" + "call-bind": "^1.0.7" + }, + "dependencies": { + "call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + } + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + } } }, "is-stream": { @@ -5561,9 +5780,9 @@ } }, "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -5606,9 +5825,9 @@ } }, "istanbul-reports": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", - "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, "requires": { "html-escaper": "^2.0.0", @@ -6044,9 +6263,9 @@ } }, "lru-cache": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", - "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==" + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==" }, "lru-memoizer": { "version": "2.2.0", @@ -6684,9 +6903,9 @@ "dev": true }, "nise": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.7.tgz", - "integrity": "sha512-wWtNUhkT7k58uvWTB/Gy26eA/EJKtPZFVAhEilN5UYVmmGRYOURbejRUyKm0Uu9XVEW7K5nBOZfR8VMB4QR2RQ==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", "dev": true, "requires": { "@sinonjs/commons": "^3.0.0", @@ -6697,9 +6916,9 @@ }, "dependencies": { "@sinonjs/commons": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", - "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, "requires": { "type-detect": "4.0.8" @@ -7172,14 +7391,16 @@ }, "dependencies": { "call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dev": true, "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" } }, "function-bind": { @@ -7187,10 +7408,23 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true - } - } - }, - "object.defaults": { + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + } + } + }, + "object.defaults": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", @@ -7777,6 +8011,11 @@ "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", "dev": true }, + "possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==" + }, "postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -8105,14 +8344,49 @@ } }, "regexp.prototype.flags": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", - "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "set-function-name": "^2.0.0" + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "dependencies": { + "call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + } + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + } } }, "regexpp": { @@ -8311,17 +8585,49 @@ } }, "safe-array-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", - "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.0.tgz", + "integrity": "sha512-ZdQ0Jeb9Ofti4hbt5lX3T2JcAamT9hfzYU1MNB+z/jaEbB6wfFfPIR/zEORmZqobkCCJhSjodobH6WHNmJ97dg==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", + "call-bind": "^1.0.5", + "get-intrinsic": "^1.2.2", "has-symbols": "^1.0.3", "isarray": "^2.0.5" }, "dependencies": { + "call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + } + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + }, "isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -8345,25 +8651,27 @@ } }, "safe-regex-test": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.2.tgz", - "integrity": "sha512-83S9w6eFq12BBIJYvjMux6/dkirb8+4zJRA9cxNBVb7Wq5fJBW+Xze48WqR8pxua7bDuAaaAxtVVd4Idjp1dBQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", "dev": true, "requires": { - "call-bind": "^1.0.5", - "get-intrinsic": "^1.2.2", + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", "is-regex": "^1.1.4" }, "dependencies": { "call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dev": true, "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" } }, "function-bind": { @@ -8373,11 +8681,12 @@ "dev": true }, "get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dev": true, "requires": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", @@ -8488,26 +8797,61 @@ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, "set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", + "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", "dev": true, "requires": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.1" + }, + "dependencies": { + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + } } }, "set-function-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", - "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dev": true, "requires": { - "define-data-property": "^1.0.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.0" + "has-property-descriptors": "^1.0.2" + }, + "dependencies": { + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0" + } + } } }, "set-value": { @@ -8846,9 +9190,9 @@ } }, "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", "dev": true }, "spdx-expression-parse": { @@ -8862,9 +9206,9 @@ } }, "spdx-license-ids": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", - "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==", + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", + "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", "dev": true }, "split-string": { @@ -8948,9 +9292,9 @@ "dev": true }, "stream-shift": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.2.tgz", - "integrity": "sha512-rV4Bovi9xx0BFzOb/X0B2GqoIjvqPCttZdu0Wgtx2Dxkj7ETyWl9gmqJ4EutWRLvtZWm8dxE+InQZX1IryZn/w==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", "dev": true }, "streamsearch": { @@ -9087,9 +9431,9 @@ } }, "swagger-ui-dist": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.11.0.tgz", - "integrity": "sha512-j0PIATqQSEFGOLmiJOJZj1X1Jt6bFIur3JpY7+ghliUnfZs0fpWDdHEkn9q7QUlBtKbkn6TepvSxTqnE8l3s0A==" + "version": "5.11.8", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.11.8.tgz", + "integrity": "sha512-IfPtCPdf6opT5HXrzHO4kjL1eco0/8xJCtcs7ilhKuzatrpF2j9s+3QbOag6G3mVFKf+g+Ca5UG9DquVUs2obA==" }, "swagger-ui-express": { "version": "4.3.0", @@ -9522,50 +9866,254 @@ } }, "typed-array-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", - "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", "dev": true, "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1", - "is-typed-array": "^1.1.10" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "dependencies": { + "call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + } + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + }, + "is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "requires": { + "which-typed-array": "^1.1.14" + } + } } }, "typed-array-byte-length": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", - "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", "dev": true, "requires": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "dependencies": { + "call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + } + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + }, + "has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true + }, + "is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "requires": { + "which-typed-array": "^1.1.14" + } + } } }, "typed-array-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", - "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", "dev": true, "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "has-proto": "^1.0.1", - "is-typed-array": "^1.1.10" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "dependencies": { + "available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "requires": { + "possible-typed-array-names": "^1.0.0" + } + }, + "call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + } + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + }, + "has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true + }, + "is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "requires": { + "which-typed-array": "^1.1.14" + } + } } }, "typed-array-length": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", - "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.5.tgz", + "integrity": "sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA==", "dev": true, "requires": { - "call-bind": "^1.0.2", + "call-bind": "^1.0.7", "for-each": "^0.3.3", - "is-typed-array": "^1.1.9" + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "dependencies": { + "call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + } + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + }, + "has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true + }, + "is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "requires": { + "which-typed-array": "^1.1.14" + } + } } }, "typedarray": { @@ -9644,9 +10192,9 @@ "dev": true }, "undici": { - "version": "5.28.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.2.tgz", - "integrity": "sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==", + "version": "5.28.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.3.tgz", + "integrity": "sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==", "requires": { "@fastify/busboy": "^2.0.0" } @@ -9962,16 +10510,85 @@ "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" }, "which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.14.tgz", + "integrity": "sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==", "requires": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.5", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" + "has-tostringtag": "^1.0.1" + }, + "dependencies": { + "available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "requires": { + "possible-typed-array-names": "^1.0.0" + } + }, + "call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + } + }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + }, + "has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "requires": { + "has-symbols": "^1.0.3" + } + }, + "set-function-length": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz", + "integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==", + "requires": { + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.1" + } + } } }, "wide-align": { @@ -10055,9 +10672,9 @@ } }, "winston-transport": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.6.0.tgz", - "integrity": "sha512-wbBA9PbPAHxKiygo7ub7BYRiKxms0tpfU2ljtWzb3SjRjv5yl6Ozuy/TkXf00HTAt+Uylo3gSkNwzc4ME0wiIg==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.7.0.tgz", + "integrity": "sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==", "requires": { "logform": "^2.3.2", "readable-stream": "^3.6.0", diff --git a/api/src/services/itis-service.ts b/api/src/services/itis-service.ts index 306c0801..09f14b0c 100644 --- a/api/src/services/itis-service.ts +++ b/api/src/services/itis-service.ts @@ -42,7 +42,12 @@ export class ItisService { return []; } - return this._sanitizeItisData(response.data.response.docs); + const sanitizedResponse = this._sanitizeItisData(response.data.response.docs); + + const sortedResponse = this._sortExactMatches(sanitizedResponse, searchTerms); + + // Return only 25 records + return sortedResponse.slice(0, 25); } /** @@ -66,6 +71,49 @@ export class ItisService { return response.data.response.docs; } + /** + * Sorts the ITIS response such that exact matches with search terms are first + * + * @param {ItisSolrSearchResponse[]} data + * @memberof ItisService + */ + _sortExactMatches = (data: TaxonSearchResult[], searchTerms: string[]): TaxonSearchResult[] => { + // Lowercase for exact matches and join + const searchTermsLower = searchTerms.map((item) => item.toLowerCase()); + + // Custom sorting function + const customSort = (a: TaxonSearchResult, b: TaxonSearchResult) => { + const aInReference = checkForMatch(a, searchTermsLower); + const bInReference = checkForMatch(b, searchTermsLower); + + if (aInReference && !bInReference) { + return -1; // Place items from searchTerms before other items + } else if (!aInReference && bInReference) { + return 1; // Place other items after items from searchTerms + } else { + return 0; // Maintain the original order if both are from searchTerms or both are not + } + }; + + // Function to check if an item is a match with search terms + const checkForMatch = (item: TaxonSearchResult, searchTermsLower: string[]) => { + // Lowercase commonName and split into individual words + const commonNameWords = item.commonName ? item.commonName.toLowerCase().split(/\s+/) : []; + + // Lowercase scientificName and split into individual words + const scientificNameWords = item.scientificName.toLowerCase().split(/\s+/); + + // Check if any word in commonName or scientificName matches any word in searchTerms + return searchTermsLower.some( + (searchTerm) => + commonNameWords.some((word) => word === searchTerm) || scientificNameWords.some((word) => word === searchTerm) + ); + }; + + // Sort the data array using the custom sorting function + return data.sort(customSort); + }; + /** * Cleans up the ITIS search response data. * @@ -100,10 +148,10 @@ export class ItisService { } return `${itisUrl}?${this._getItisSolrTypeParam()}&${this._getItisSolrSortParam( - 'nameWOInd', - 'asc', - 25 - )}&${this._getItisSolrFilterParam()}&${this._getItisSolrQueryParam(searchTerms)}`; + 'credibilityRating', + 'desc', + 150 + )}&${this._getItisSolrFilterParam()}&${this._getItisSolrQueryParam(searchTerms)}&qf=nameWOInd^2`; } /** @@ -122,9 +170,9 @@ export class ItisService { } return `${itisUrl}??${this._getItisSolrTypeParam()}&${this._getItisSolrSortParam( - 'nameWOInd', - 'asc', - 25 + 'credibilityRating', + 'desc', + 150 )}&${this._getItisSolrFilterParam()}&&q=${this._getItisSolrTsnSearch(searchTsnIds)}`; } @@ -153,11 +201,11 @@ export class ItisService { * * @param {string} sortBy * @param {('asc' | 'desc')} sortDir - * @param {number} [limit=25] + * @param {number} limit * @return {*} {string} * @memberof ItisService */ - _getItisSolrSortParam(sortBy: string, sortDir: 'asc' | 'desc', limit = 25): string { + _getItisSolrSortParam(sortBy: string, sortDir: 'asc' | 'desc', limit: number): string { return `sort=${sortBy}+${sortDir}&rows=${limit}`; } From cb79c4e722ccc655284c4a653e28eecb2451c629 Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Fri, 1 Mar 2024 17:12:15 -0800 Subject: [PATCH 02/27] helper functions for sorting itis response by exact and partial match --- api/src/services/itis-service.ts | 48 ++++---------------------------- api/src/utils/itis.ts | 44 +++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 42 deletions(-) create mode 100644 api/src/utils/itis.ts diff --git a/api/src/services/itis-service.ts b/api/src/services/itis-service.ts index 09f14b0c..57d4b3a4 100644 --- a/api/src/services/itis-service.ts +++ b/api/src/services/itis-service.ts @@ -1,4 +1,5 @@ import axios from 'axios'; +import { sortExactMatches } from '../utils/itis'; import { getLogger } from '../utils/logger'; import { TaxonSearchResult } from './taxonomy-service'; @@ -44,7 +45,8 @@ export class ItisService { const sanitizedResponse = this._sanitizeItisData(response.data.response.docs); - const sortedResponse = this._sortExactMatches(sanitizedResponse, searchTerms); + // Sort the results to place exact matches at the top + const sortedResponse = sortExactMatches(sanitizedResponse, searchTerms); // Return only 25 records return sortedResponse.slice(0, 25); @@ -72,47 +74,9 @@ export class ItisService { } /** - * Sorts the ITIS response such that exact matches with search terms are first + * Sorts by exact matches within, ie. Keywords of "Black" and "Bear" would match on "Black Willow" * - * @param {ItisSolrSearchResponse[]} data - * @memberof ItisService */ - _sortExactMatches = (data: TaxonSearchResult[], searchTerms: string[]): TaxonSearchResult[] => { - // Lowercase for exact matches and join - const searchTermsLower = searchTerms.map((item) => item.toLowerCase()); - - // Custom sorting function - const customSort = (a: TaxonSearchResult, b: TaxonSearchResult) => { - const aInReference = checkForMatch(a, searchTermsLower); - const bInReference = checkForMatch(b, searchTermsLower); - - if (aInReference && !bInReference) { - return -1; // Place items from searchTerms before other items - } else if (!aInReference && bInReference) { - return 1; // Place other items after items from searchTerms - } else { - return 0; // Maintain the original order if both are from searchTerms or both are not - } - }; - - // Function to check if an item is a match with search terms - const checkForMatch = (item: TaxonSearchResult, searchTermsLower: string[]) => { - // Lowercase commonName and split into individual words - const commonNameWords = item.commonName ? item.commonName.toLowerCase().split(/\s+/) : []; - - // Lowercase scientificName and split into individual words - const scientificNameWords = item.scientificName.toLowerCase().split(/\s+/); - - // Check if any word in commonName or scientificName matches any word in searchTerms - return searchTermsLower.some( - (searchTerm) => - commonNameWords.some((word) => word === searchTerm) || scientificNameWords.some((word) => word === searchTerm) - ); - }; - - // Sort the data array using the custom sorting function - return data.sort(customSort); - }; /** * Cleans up the ITIS search response data. @@ -150,7 +114,7 @@ export class ItisService { return `${itisUrl}?${this._getItisSolrTypeParam()}&${this._getItisSolrSortParam( 'credibilityRating', 'desc', - 150 + 100 )}&${this._getItisSolrFilterParam()}&${this._getItisSolrQueryParam(searchTerms)}&qf=nameWOInd^2`; } @@ -172,7 +136,7 @@ export class ItisService { return `${itisUrl}??${this._getItisSolrTypeParam()}&${this._getItisSolrSortParam( 'credibilityRating', 'desc', - 150 + 100 )}&${this._getItisSolrFilterParam()}&&q=${this._getItisSolrTsnSearch(searchTsnIds)}`; } diff --git a/api/src/utils/itis.ts b/api/src/utils/itis.ts new file mode 100644 index 00000000..588b938a --- /dev/null +++ b/api/src/utils/itis.ts @@ -0,0 +1,44 @@ + +import { TaxonSearchResult } from '../services/taxonomy-service'; + +/** + * Sorts the ITIS response such that exact matches with search terms are first + * + * @param {TaxonSearchResult[]} data + * @param {string[]} searchTerms + * @returns {TaxonSearchResult[]} + */ +export const sortExactMatches = (data: TaxonSearchResult[], searchTerms: string[]): TaxonSearchResult[] => { + const searchTermLower = searchTerms.join(' ').toLowerCase(); + + return data.sort((a, b) => { + const aMatch = checkForMatch(a); + const bMatch = checkForMatch(b); + + // Prioritize exact matches over partial matches + if (aMatch.exact && !bMatch.exact) { + return -1; + } else if (!aMatch.exact && bMatch.exact) { + return 1; + } else { + // If both are exact matches or both are not, sort based on partial matches + if (aMatch.partial && !bMatch.partial) { + return -1; + } else if (!aMatch.partial && bMatch.partial) { + return 1; + } else { + return 0; + } + } + }); + + function checkForMatch(item: TaxonSearchResult) { + const commonNameWord = item.commonName && item.commonName.toLowerCase(); + const scientificNameWord = item.scientificName.toLowerCase(); + + return { + exact: commonNameWord === searchTermLower || scientificNameWord === searchTermLower, + partial: commonNameWord?.includes(searchTermLower) || scientificNameWord.includes(searchTermLower) + }; + } +}; From b09cb31b2cd26f1087880926c9aff6a3bdf13795 Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Fri, 1 Mar 2024 17:48:23 -0800 Subject: [PATCH 03/27] cleanup sorting functions --- api/src/services/itis-service.ts | 6 +- api/src/utils/itis.ts | 148 +++++++++++++++++++++++++------ 2 files changed, 123 insertions(+), 31 deletions(-) diff --git a/api/src/services/itis-service.ts b/api/src/services/itis-service.ts index 57d4b3a4..8d4d7832 100644 --- a/api/src/services/itis-service.ts +++ b/api/src/services/itis-service.ts @@ -114,8 +114,8 @@ export class ItisService { return `${itisUrl}?${this._getItisSolrTypeParam()}&${this._getItisSolrSortParam( 'credibilityRating', 'desc', - 100 - )}&${this._getItisSolrFilterParam()}&${this._getItisSolrQueryParam(searchTerms)}&qf=nameWOInd^2`; + 75 + )}&${this._getItisSolrFilterParam()}&${this._getItisSolrQueryParam(searchTerms)}`; } /** @@ -136,7 +136,7 @@ export class ItisService { return `${itisUrl}??${this._getItisSolrTypeParam()}&${this._getItisSolrSortParam( 'credibilityRating', 'desc', - 100 + 75 )}&${this._getItisSolrFilterParam()}&&q=${this._getItisSolrTsnSearch(searchTsnIds)}`; } diff --git a/api/src/utils/itis.ts b/api/src/utils/itis.ts index 588b938a..91473c78 100644 --- a/api/src/utils/itis.ts +++ b/api/src/utils/itis.ts @@ -1,44 +1,136 @@ - import { TaxonSearchResult } from '../services/taxonomy-service'; /** * Sorts the ITIS response such that exact matches with search terms are first * - * @param {TaxonSearchResult[]} data - * @param {string[]} searchTerms - * @returns {TaxonSearchResult[]} + * @param {ItisSolrSearchResponse[]} data + * @memberof ItisService */ export const sortExactMatches = (data: TaxonSearchResult[], searchTerms: string[]): TaxonSearchResult[] => { - const searchTermLower = searchTerms.join(' ').toLowerCase(); + const searchTermsLower = searchTerms.map((item) => item.toLowerCase()); + + const contains = customSortContainsAnyMatchingSearchTerm(data, searchTermsLower); + const someEquals = customSortContainsSearchTermsJoined(contains, searchTermsLower); + const exactEquals = customSortEqualsSearchTermsJoined(someEquals, searchTermsLower); - return data.sort((a, b) => { - const aMatch = checkForMatch(a); - const bMatch = checkForMatch(b); + return exactEquals; +}; + +/** + * Sorts the ITIS response such that exact matches with search terms are first + * + * @param {ItisSolrSearchResponse[]} data + * @memberof ItisService + */ +export const customSortContainsAnyMatchingSearchTerm = ( + data: TaxonSearchResult[], + searchTerms: string[] +): TaxonSearchResult[] => { + // Custom sorting function + const customSort = (a: TaxonSearchResult, b: TaxonSearchResult) => { + const aInReference = checkForMatch(a, searchTerms); + const bInReference = checkForMatch(b, searchTerms); - // Prioritize exact matches over partial matches - if (aMatch.exact && !bMatch.exact) { - return -1; - } else if (!aMatch.exact && bMatch.exact) { - return 1; + if (aInReference && !bInReference) { + return -1; // Place items from searchTerms before other items + } else if (!aInReference && bInReference) { + return 1; // Place other items after items from searchTerms } else { - // If both are exact matches or both are not, sort based on partial matches - if (aMatch.partial && !bMatch.partial) { - return -1; - } else if (!aMatch.partial && bMatch.partial) { - return 1; - } else { - return 0; - } + return 0; // Maintain the original order if both are from searchTerms or both are not } - }); + }; + + // Function to check if an item is a match with search terms + const checkForMatch = (item: TaxonSearchResult, searchTerms: string[]) => { + // Lowercase commonName and split into individual words + const commonNameWords = item.commonName && item.commonName.toLowerCase().split(/\s+/); + + // Lowercase scientificName and split into individual words + const scientificNameWords = item.scientificName.toLowerCase().split(/\s+/); - function checkForMatch(item: TaxonSearchResult) { + // Check if any word in commonName or scientificName matches any word in searchTerms + return searchTerms.some( + (searchTerm) => + scientificNameWords.some((word) => word === searchTerm) || + (commonNameWords && commonNameWords.some((word) => word === searchTerm)) + ); + }; + + return data.sort(customSort); +}; + +/** + * Sorts the ITIS response such that exact matches with search terms are first + * + * @param {ItisSolrSearchResponse[]} data + * @memberof ItisService + */ +export const customSortContainsSearchTermsJoined = ( + data: TaxonSearchResult[], + searchTerms: string[] +): TaxonSearchResult[] => { + // Custom sorting function + const customSort = (a: TaxonSearchResult, b: TaxonSearchResult) => { + const aInReference = checkForMatch(a, searchTerms); + const bInReference = checkForMatch(b, searchTerms); + + if (aInReference && !bInReference) { + return -1; // Place items from searchTerms before other items + } else if (!aInReference && bInReference) { + return 0; // Place other items after items from searchTerms + } else { + return 0; // Maintain the original order if both are from searchTerms or both are not + } + }; + + // Function to check if an item is a match with search terms + const checkForMatch = (item: TaxonSearchResult, searchTerms: string[]) => { + // Lowercase commonName and split into individual words + const commonNameWords = item.commonName && item.commonName.toLowerCase(); + + // Lowercase scientificName and split into individual words + const scientificNameWords = item.scientificName.toLowerCase(); + + const joinedSearchTerms = searchTerms.join(' '); + + return scientificNameWords.includes(joinedSearchTerms) || commonNameWords?.includes(joinedSearchTerms); + }; + + return data.sort(customSort); +}; + +/** + * Sorts the ITIS response such that exact matches with search terms are first + * + * @param {ItisSolrSearchResponse[]} data + * @memberof ItisService + */ +export const customSortEqualsSearchTermsJoined = ( + data: TaxonSearchResult[], + searchTerms: string[] +): TaxonSearchResult[] => { + // Custom sorting function + const customSort = (a: TaxonSearchResult, b: TaxonSearchResult) => { + const aInReference = checkForMatch(a, searchTerms); + const bInReference = checkForMatch(b, searchTerms); + + if (aInReference && !bInReference) { + return -1; // Place items from searchTerms before other items + } else if (!aInReference && bInReference) { + return 0; // Place other items after items from searchTerms + } else { + return 0; // Maintain the original order if both are from searchTerms or both are not + } + }; + + // Function to check if an item is a match with search terms + const checkForMatch = (item: TaxonSearchResult, searchTerms: string[]) => { const commonNameWord = item.commonName && item.commonName.toLowerCase(); + const scientificNameWord = item.scientificName.toLowerCase(); - return { - exact: commonNameWord === searchTermLower || scientificNameWord === searchTermLower, - partial: commonNameWord?.includes(searchTermLower) || scientificNameWord.includes(searchTermLower) - }; - } + return scientificNameWord === searchTerms.join(' ') || commonNameWord === searchTerms.join(' '); + }; + + return data.sort(customSort); }; From 820854ef6bbb127eea9898242cabd26d79971243 Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Fri, 1 Mar 2024 18:43:21 -0800 Subject: [PATCH 04/27] return first english common name --- api/src/services/itis-service.ts | 3 ++- api/src/services/taxonomy-service.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/api/src/services/itis-service.ts b/api/src/services/itis-service.ts index 8d4d7832..20687759 100644 --- a/api/src/services/itis-service.ts +++ b/api/src/services/itis-service.ts @@ -86,7 +86,8 @@ export class ItisService { */ _sanitizeItisData = (data: ItisSolrSearchResponse[]): TaxonSearchResult[] => { return data.map((item: ItisSolrSearchResponse) => { - const commonName = item.commonNames ? item.commonNames[0].split('$')[1] : null; + const firstEnglishName = item.commonNames?.find((name) => name.split('$')[2] === 'English'); + const commonName = firstEnglishName ? firstEnglishName.split('$')[1] : null; return { tsn: Number(item.tsn), diff --git a/api/src/services/taxonomy-service.ts b/api/src/services/taxonomy-service.ts index 5b24e759..f0bd7c0d 100644 --- a/api/src/services/taxonomy-service.ts +++ b/api/src/services/taxonomy-service.ts @@ -75,7 +75,8 @@ export class TaxonomyService { async addItisTaxonRecord(itisSolrResponse: ItisSolrSearchResponse): Promise { let commonName = null; if (itisSolrResponse.commonNames) { - commonName = itisSolrResponse.commonNames[0].split('$')[1]; + const firstEnglishName = itisSolrResponse.commonNames.find((name) => name.split('$')[2] === 'English'); + commonName = firstEnglishName ? firstEnglishName.split('$')[1] : null; /* Sample itisResponse: * commonNames: [ * '$withered wooly milk-vetch$English$N$152846$2012-12-21 00:00:00$', From 6e575e391c616a36e4a6e81536de71dd075593cd Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Fri, 1 Mar 2024 23:26:47 -0800 Subject: [PATCH 05/27] simplify sorting --- api/src/paths/taxonomy/taxon/index.test.ts | 4 +- api/src/paths/taxonomy/taxon/index.ts | 13 +++-- .../paths/taxonomy/taxon/tsn/index.test.ts | 4 +- api/src/paths/taxonomy/taxon/tsn/index.ts | 4 +- api/src/repositories/taxonomy-repository.ts | 8 +-- api/src/services/itis-service.test.ts | 8 +-- api/src/services/itis-service.ts | 24 ++++---- api/src/services/taxonomy-service.test.ts | 12 ++-- api/src/services/taxonomy-service.ts | 20 +++---- api/src/utils/itis.ts | 58 ++----------------- .../release.0.8.0/smoketest_release.sql | 2 +- 11 files changed, 58 insertions(+), 99 deletions(-) diff --git a/api/src/paths/taxonomy/taxon/index.test.ts b/api/src/paths/taxonomy/taxon/index.test.ts index a1e0d7ae..f0d0382b 100644 --- a/api/src/paths/taxonomy/taxon/index.test.ts +++ b/api/src/paths/taxonomy/taxon/index.test.ts @@ -52,8 +52,8 @@ describe('taxon', () => { sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); - const mock1 = { id: '1', commonName: 'something', scientificName: 'string' } as unknown as any; - const mock2 = { id: '2', commonName: null, scientificName: 'string' } as unknown as any; + const mock1 = { id: '1', commonNames: 'something', scientificName: 'string' } as unknown as any; + const mock2 = { id: '2', commonNames: null, scientificName: 'string' } as unknown as any; const getSpeciesFromIdsStub = sinon.stub(ItisService.prototype, 'searchItisByTerm').resolves([mock1, mock2]); diff --git a/api/src/paths/taxonomy/taxon/index.ts b/api/src/paths/taxonomy/taxon/index.ts index aaec0324..901c9218 100644 --- a/api/src/paths/taxonomy/taxon/index.ts +++ b/api/src/paths/taxonomy/taxon/index.ts @@ -45,17 +45,22 @@ GET.apiDoc = { items: { title: 'Taxon', type: 'object', - required: ['tsn', 'commonName', 'scientificName'], + required: ['tsn', 'commonNames', 'scientificName'], properties: { tsn: { type: 'integer' }, - commonName: { - type: 'string', - nullable: true + commonNames: { + type: 'array', + items: { + type: 'string' + } }, scientificName: { type: 'string' + }, + rank: { + type: 'string' } }, additionalProperties: false diff --git a/api/src/paths/taxonomy/taxon/tsn/index.test.ts b/api/src/paths/taxonomy/taxon/tsn/index.test.ts index 7102e926..38bb0f1b 100644 --- a/api/src/paths/taxonomy/taxon/tsn/index.test.ts +++ b/api/src/paths/taxonomy/taxon/tsn/index.test.ts @@ -52,8 +52,8 @@ describe('tsn', () => { sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); - const mock1 = { tsn: '1', commonName: 'something', scientificName: 'string' } as unknown as any; - const mock2 = { tsn: '2', commonName: null, scientificName: 'string' } as unknown as any; + const mock1 = { tsn: '1', commonNames: ['something'], scientificName: 'string' } as unknown as any; + const mock2 = { tsn: '2', commonNames: null, scientificName: 'string' } as unknown as any; const getTaxonByTsnIdsStub = sinon.stub(TaxonomyService.prototype, 'getTaxonByTsnIds').resolves([mock1, mock2]); diff --git a/api/src/paths/taxonomy/taxon/tsn/index.ts b/api/src/paths/taxonomy/taxon/tsn/index.ts index 7ac992da..94e61936 100644 --- a/api/src/paths/taxonomy/taxon/tsn/index.ts +++ b/api/src/paths/taxonomy/taxon/tsn/index.ts @@ -47,12 +47,12 @@ GET.apiDoc = { items: { title: 'Species', type: 'object', - required: ['tsn', 'commonName', 'scientificName'], + required: ['tsn', 'commonNames', 'scientificName'], properties: { tsn: { type: 'integer' }, - commonName: { + commonNames: { type: 'string', nullable: true }, diff --git a/api/src/repositories/taxonomy-repository.ts b/api/src/repositories/taxonomy-repository.ts index 262b4e61..ce5416e5 100644 --- a/api/src/repositories/taxonomy-repository.ts +++ b/api/src/repositories/taxonomy-repository.ts @@ -12,7 +12,7 @@ export const TaxonRecord = z.object({ itis_tsn: z.number(), bc_taxon_code: z.string().nullable(), itis_scientific_name: z.string(), - common_name: z.string().nullable(), + common_name: z.string(), itis_data: z.record(z.any()), record_effective_date: z.string(), record_end_date: z.string().nullable(), @@ -53,7 +53,7 @@ export class TaxonomyRepository extends BaseRepository { * * @param {number} itisTsn * @param {string} itisScientificName - * @param {(string | null)} commonName + * @param {(string | null)} commonNames * @param {Record} itisData * @param {string} itisUpdateDate * @return {*} {Promise} @@ -62,7 +62,7 @@ export class TaxonomyRepository extends BaseRepository { async addItisTaxonRecord( itisTsn: number, itisScientificName: string, - commonName: string | null, + commonNames: string | null, itisData: Record, itisUpdateDate: string ): Promise { @@ -82,7 +82,7 @@ export class TaxonomyRepository extends BaseRepository { VALUES ( ${itisTsn}, ${itisScientificName}, - ${commonName}, + ${commonNames}, ${itisData}, ${itisUpdateDate} ) diff --git a/api/src/services/itis-service.test.ts b/api/src/services/itis-service.test.ts index f08ec48c..67b16a8f 100644 --- a/api/src/services/itis-service.test.ts +++ b/api/src/services/itis-service.test.ts @@ -40,7 +40,7 @@ describe('ItisService', () => { response: { docs: [ { - commonNames: ['$commonName'], + commonNames: ['$commonNames'], kingdom: 'kingdom', name: 'name', parentTSN: 'parentTSN', @@ -67,7 +67,7 @@ describe('ItisService', () => { expect(response).to.eql([ { tsn: 123, - commonName: 'commonName', + commonNames: 'commonNames', scientificName: 'scientificName' } ]); @@ -121,7 +121,7 @@ describe('ItisService', () => { response: { docs: [ { - commonNames: ['$commonName'], + commonNames: ['$commonNames'], kingdom: 'kingdom', name: 'name', parentTSN: 'parentTSN', @@ -145,7 +145,7 @@ describe('ItisService', () => { expect(response).to.eql([ { - commonNames: ['$commonName'], + commonNames: ['$commonNames'], kingdom: 'kingdom', name: 'name', parentTSN: 'parentTSN', diff --git a/api/src/services/itis-service.ts b/api/src/services/itis-service.ts index 20687759..3fedcd9e 100644 --- a/api/src/services/itis-service.ts +++ b/api/src/services/itis-service.ts @@ -1,7 +1,7 @@ import axios from 'axios'; -import { sortExactMatches } from '../utils/itis'; import { getLogger } from '../utils/logger'; import { TaxonSearchResult } from './taxonomy-service'; +import { sortExactMatches } from '../utils/itis'; const defaultLog = getLogger('services/itis-service'); @@ -14,6 +14,7 @@ export type ItisSolrSearchResponse = { tsn: string; updateDate: string; usage: string; + rank: string; }; /** @@ -86,13 +87,14 @@ export class ItisService { */ _sanitizeItisData = (data: ItisSolrSearchResponse[]): TaxonSearchResult[] => { return data.map((item: ItisSolrSearchResponse) => { - const firstEnglishName = item.commonNames?.find((name) => name.split('$')[2] === 'English'); - const commonName = firstEnglishName ? firstEnglishName.split('$')[1] : null; + const englishNames = item.commonNames?.filter((name) => name.split('$')[2] === 'English'); + const commonNames = englishNames ? englishNames.map((name) => name.split('$')[1]) : null; return { tsn: Number(item.tsn), - commonName: commonName, - scientificName: item.scientificName + commonNames: commonNames || [], + scientificName: item.scientificName, + rank: item.rank }; }); }; @@ -113,9 +115,9 @@ export class ItisService { } return `${itisUrl}?${this._getItisSolrTypeParam()}&${this._getItisSolrSortParam( - 'credibilityRating', - 'desc', - 75 + 'kingdom', + 'asc', + 100 )}&${this._getItisSolrFilterParam()}&${this._getItisSolrQueryParam(searchTerms)}`; } @@ -135,9 +137,9 @@ export class ItisService { } return `${itisUrl}??${this._getItisSolrTypeParam()}&${this._getItisSolrSortParam( - 'credibilityRating', + 'kingdom', 'desc', - 75 + 100 )}&${this._getItisSolrFilterParam()}&&q=${this._getItisSolrTsnSearch(searchTsnIds)}`; } @@ -181,7 +183,7 @@ export class ItisService { * @memberof ItisService */ _getItisSolrFilterParam(): string { - return 'omitHeader=true&fl=tsn+scientificName:nameWOInd+kingdom+parentTSN+commonNames:vernacular+updateDate+usage'; + return 'omitHeader=true&fl=tsn+scientificName:nameWOInd+kingdom+parentTSN+commonNames:vernacular+updateDate+usage+rank:rankId'; } /** diff --git a/api/src/services/taxonomy-service.test.ts b/api/src/services/taxonomy-service.test.ts index bb6af9df..5940c038 100644 --- a/api/src/services/taxonomy-service.test.ts +++ b/api/src/services/taxonomy-service.test.ts @@ -16,7 +16,7 @@ describe('TaxonomyService', () => { const getItisSolrSearchResponse: ItisSolrSearchResponse[] = [ { - commonNames: ['$commonName'], + commonNames: ['$commonNames'], kingdom: 'kingdom', name: 'name', parentTSN: 'parentTSN', @@ -63,7 +63,7 @@ describe('TaxonomyService', () => { const response = await taxonomyService.getTaxonByTsnIds([1]); expect(repo).to.be.calledOnce; - expect(response).to.be.eql([{ tsn: 1, commonName: 'common_name', scientificName: 'itis_scientific_name' }]); + expect(response).to.be.eql([{ tsn: 1, commonNames: 'common_name', scientificName: 'itis_scientific_name' }]); }); it('if some records do not exist in db should return array of taxon records', async () => { @@ -118,8 +118,8 @@ describe('TaxonomyService', () => { expect(searchItisByTSNStub).to.be.calledOnce; expect(itisService).to.be.calledOnce; expect(response).to.be.eql([ - { tsn: 1, commonName: 'common_name', scientificName: 'itis_scientific_name' }, - { tsn: 2, commonName: 'common_name', scientificName: 'itis_scientific_name' } + { tsn: 1, commonNames: 'common_name', scientificName: 'itis_scientific_name' }, + { tsn: 2, commonNames: 'common_name', scientificName: 'itis_scientific_name' } ]); }); }); @@ -135,7 +135,7 @@ describe('TaxonomyService', () => { itis_tsn: 1, bc_taxon_code: null, itis_scientific_name: 'scientificName', - common_name: 'commonName', + common_name: 'commonNames', itis_data: {}, record_effective_date: 'updateDate', record_end_date: null, @@ -154,7 +154,7 @@ describe('TaxonomyService', () => { itis_tsn: 1, bc_taxon_code: null, itis_scientific_name: 'scientificName', - common_name: 'commonName', + common_name: 'commonNames', itis_data: {}, record_effective_date: 'updateDate', record_end_date: null, diff --git a/api/src/services/taxonomy-service.ts b/api/src/services/taxonomy-service.ts index f0bd7c0d..8988465a 100644 --- a/api/src/services/taxonomy-service.ts +++ b/api/src/services/taxonomy-service.ts @@ -7,7 +7,7 @@ const defaultLog = getLogger('services/taxonomy-service'); export type TaxonSearchResult = { tsn: number; - commonName: string | null; + commonNames: string[]; scientificName: string; }; @@ -56,13 +56,11 @@ export class TaxonomyService { } _sanitizeTaxonRecordsData(taxonRecords: TaxonRecord[]): TaxonSearchResult[] { - return taxonRecords.map((item: TaxonRecord) => { - return { - tsn: item.itis_tsn, - commonName: item.common_name, - scientificName: item.itis_scientific_name - }; - }); + return taxonRecords.map((item: TaxonRecord) => ({ + tsn: item.itis_tsn, + commonNames: [item.common_name], + scientificName: item.itis_scientific_name + })); } /** @@ -73,10 +71,10 @@ export class TaxonomyService { * @memberof TaxonomyService */ async addItisTaxonRecord(itisSolrResponse: ItisSolrSearchResponse): Promise { - let commonName = null; + let commonNames = null; if (itisSolrResponse.commonNames) { const firstEnglishName = itisSolrResponse.commonNames.find((name) => name.split('$')[2] === 'English'); - commonName = firstEnglishName ? firstEnglishName.split('$')[1] : null; + commonNames = firstEnglishName ? firstEnglishName.split('$')[1] : null; /* Sample itisResponse: * commonNames: [ * '$withered wooly milk-vetch$English$N$152846$2012-12-21 00:00:00$', @@ -90,7 +88,7 @@ export class TaxonomyService { return this.taxonRepository.addItisTaxonRecord( Number(itisSolrResponse.tsn), itisSolrResponse.scientificName, - commonName, + commonNames, itisSolrResponse, itisSolrResponse.updateDate ); diff --git a/api/src/utils/itis.ts b/api/src/utils/itis.ts index 91473c78..11637b6b 100644 --- a/api/src/utils/itis.ts +++ b/api/src/utils/itis.ts @@ -9,56 +9,12 @@ import { TaxonSearchResult } from '../services/taxonomy-service'; export const sortExactMatches = (data: TaxonSearchResult[], searchTerms: string[]): TaxonSearchResult[] => { const searchTermsLower = searchTerms.map((item) => item.toLowerCase()); - const contains = customSortContainsAnyMatchingSearchTerm(data, searchTermsLower); - const someEquals = customSortContainsSearchTermsJoined(contains, searchTermsLower); + const someEquals = customSortContainsSearchTermsJoined(data, searchTermsLower); const exactEquals = customSortEqualsSearchTermsJoined(someEquals, searchTermsLower); return exactEquals; }; -/** - * Sorts the ITIS response such that exact matches with search terms are first - * - * @param {ItisSolrSearchResponse[]} data - * @memberof ItisService - */ -export const customSortContainsAnyMatchingSearchTerm = ( - data: TaxonSearchResult[], - searchTerms: string[] -): TaxonSearchResult[] => { - // Custom sorting function - const customSort = (a: TaxonSearchResult, b: TaxonSearchResult) => { - const aInReference = checkForMatch(a, searchTerms); - const bInReference = checkForMatch(b, searchTerms); - - if (aInReference && !bInReference) { - return -1; // Place items from searchTerms before other items - } else if (!aInReference && bInReference) { - return 1; // Place other items after items from searchTerms - } else { - return 0; // Maintain the original order if both are from searchTerms or both are not - } - }; - - // Function to check if an item is a match with search terms - const checkForMatch = (item: TaxonSearchResult, searchTerms: string[]) => { - // Lowercase commonName and split into individual words - const commonNameWords = item.commonName && item.commonName.toLowerCase().split(/\s+/); - - // Lowercase scientificName and split into individual words - const scientificNameWords = item.scientificName.toLowerCase().split(/\s+/); - - // Check if any word in commonName or scientificName matches any word in searchTerms - return searchTerms.some( - (searchTerm) => - scientificNameWords.some((word) => word === searchTerm) || - (commonNameWords && commonNameWords.some((word) => word === searchTerm)) - ); - }; - - return data.sort(customSort); -}; - /** * Sorts the ITIS response such that exact matches with search terms are first * @@ -85,15 +41,13 @@ export const customSortContainsSearchTermsJoined = ( // Function to check if an item is a match with search terms const checkForMatch = (item: TaxonSearchResult, searchTerms: string[]) => { - // Lowercase commonName and split into individual words - const commonNameWords = item.commonName && item.commonName.toLowerCase(); + // Lowercase commonNames and split into individual words + const commonNameWords = item.commonNames && item.commonNames.map((name) => name.toLowerCase()); // Lowercase scientificName and split into individual words const scientificNameWords = item.scientificName.toLowerCase(); - const joinedSearchTerms = searchTerms.join(' '); - - return scientificNameWords.includes(joinedSearchTerms) || commonNameWords?.includes(joinedSearchTerms); + return scientificNameWords.includes(searchTerms.join(' ')) || commonNameWords?.includes(searchTerms.join(' ')); }; return data.sort(customSort); @@ -125,11 +79,11 @@ export const customSortEqualsSearchTermsJoined = ( // Function to check if an item is a match with search terms const checkForMatch = (item: TaxonSearchResult, searchTerms: string[]) => { - const commonNameWord = item.commonName && item.commonName.toLowerCase(); + const commonNameWords = item.commonNames && item.commonNames.map((name) => name.toLowerCase()); const scientificNameWord = item.scientificName.toLowerCase(); - return scientificNameWord === searchTerms.join(' ') || commonNameWord === searchTerms.join(' '); + return scientificNameWord === searchTerms.join(' ') || commonNameWords.includes(searchTerms.join(' ')); }; return data.sort(customSort); diff --git a/database/src/migrations/release.0.8.0/smoketest_release.sql b/database/src/migrations/release.0.8.0/smoketest_release.sql index df58b3cb..864c9866 100644 --- a/database/src/migrations/release.0.8.0/smoketest_release.sql +++ b/database/src/migrations/release.0.8.0/smoketest_release.sql @@ -215,7 +215,7 @@ declare SPECIES Amaranthus albus - Tumbleweed + Tumbleweed AMARALB From 352c17a55c288ccb83922aeb91a888819fdf7a02 Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Fri, 1 Mar 2024 23:57:05 -0800 Subject: [PATCH 06/27] fix word matching --- api/src/paths/taxonomy/taxon/index.ts | 3 ++ api/src/services/itis-service.ts | 21 +++++------ api/src/services/taxonomy-service.ts | 2 +- api/src/utils/itis.ts | 51 ++++++++++++++++++++++++--- 4 files changed, 62 insertions(+), 15 deletions(-) diff --git a/api/src/paths/taxonomy/taxon/index.ts b/api/src/paths/taxonomy/taxon/index.ts index 901c9218..590927f9 100644 --- a/api/src/paths/taxonomy/taxon/index.ts +++ b/api/src/paths/taxonomy/taxon/index.ts @@ -61,6 +61,9 @@ GET.apiDoc = { }, rank: { type: 'string' + }, + kingdom: { + type: 'string' } }, additionalProperties: false diff --git a/api/src/services/itis-service.ts b/api/src/services/itis-service.ts index 3fedcd9e..c7048ba3 100644 --- a/api/src/services/itis-service.ts +++ b/api/src/services/itis-service.ts @@ -1,7 +1,7 @@ import axios from 'axios'; +import { sortExactMatches } from '../utils/itis'; import { getLogger } from '../utils/logger'; import { TaxonSearchResult } from './taxonomy-service'; -import { sortExactMatches } from '../utils/itis'; const defaultLog = getLogger('services/itis-service'); @@ -50,7 +50,7 @@ export class ItisService { const sortedResponse = sortExactMatches(sanitizedResponse, searchTerms); // Return only 25 records - return sortedResponse.slice(0, 25); + return sortedResponse.slice(0, 15); } /** @@ -94,7 +94,8 @@ export class ItisService { tsn: Number(item.tsn), commonNames: commonNames || [], scientificName: item.scientificName, - rank: item.rank + rank: item.rank, + kingdom: item.kingdom }; }); }; @@ -115,9 +116,9 @@ export class ItisService { } return `${itisUrl}?${this._getItisSolrTypeParam()}&${this._getItisSolrSortParam( - 'kingdom', - 'asc', - 100 + ['kingdom'], + ['asc'], + 50 )}&${this._getItisSolrFilterParam()}&${this._getItisSolrQueryParam(searchTerms)}`; } @@ -137,8 +138,8 @@ export class ItisService { } return `${itisUrl}??${this._getItisSolrTypeParam()}&${this._getItisSolrSortParam( - 'kingdom', - 'desc', + ['kingdom'], + ['asc'], 100 )}&${this._getItisSolrFilterParam()}&&q=${this._getItisSolrTsnSearch(searchTsnIds)}`; } @@ -172,8 +173,8 @@ export class ItisService { * @return {*} {string} * @memberof ItisService */ - _getItisSolrSortParam(sortBy: string, sortDir: 'asc' | 'desc', limit: number): string { - return `sort=${sortBy}+${sortDir}&rows=${limit}`; + _getItisSolrSortParam(sortBy: string[], sortDir: ('asc' | 'desc')[], limit: number): string { + return `sort=${sortBy.map((f, index) => `${f}+${sortDir[index]}`).join(',')}&rows=${limit}`; } /** diff --git a/api/src/services/taxonomy-service.ts b/api/src/services/taxonomy-service.ts index 8988465a..3b420251 100644 --- a/api/src/services/taxonomy-service.ts +++ b/api/src/services/taxonomy-service.ts @@ -59,7 +59,7 @@ export class TaxonomyService { return taxonRecords.map((item: TaxonRecord) => ({ tsn: item.itis_tsn, commonNames: [item.common_name], - scientificName: item.itis_scientific_name + scientificName: item.itis_scientific_name, })); } diff --git a/api/src/utils/itis.ts b/api/src/utils/itis.ts index 11637b6b..0d56f61e 100644 --- a/api/src/utils/itis.ts +++ b/api/src/utils/itis.ts @@ -9,12 +9,55 @@ import { TaxonSearchResult } from '../services/taxonomy-service'; export const sortExactMatches = (data: TaxonSearchResult[], searchTerms: string[]): TaxonSearchResult[] => { const searchTermsLower = searchTerms.map((item) => item.toLowerCase()); - const someEquals = customSortContainsSearchTermsJoined(data, searchTermsLower); + const contains = customSortContainsAnyMatchingSearchTerm(data, searchTermsLower); + const someEquals = customSortContainsSearchTermsJoined(contains, searchTermsLower); const exactEquals = customSortEqualsSearchTermsJoined(someEquals, searchTermsLower); return exactEquals; }; +/** + * Sorts the ITIS response such that exact matches with search terms are first + * + * @param {ItisSolrSearchResponse[]} data + * @memberof ItisService + */ +export const customSortContainsAnyMatchingSearchTerm = ( + data: TaxonSearchResult[], + searchTerms: string[] +): TaxonSearchResult[] => { + // Custom sorting function + const customSort = (a: TaxonSearchResult, b: TaxonSearchResult) => { + const aInReference = checkForMatch(a, searchTerms); + const bInReference = checkForMatch(b, searchTerms); + + if (aInReference && !bInReference) { + return -1; // Place items from searchTerms before other items + } else if (!aInReference && bInReference) { + return 1; // Place other items after items from searchTerms + } else { + return 0; // Maintain the original order if both are from searchTerms or both are not + } + }; + + const checkForMatch = (item: TaxonSearchResult, searchTerms: string[]) => { + // Lowercase commonNames and split into individual words + const commonNameWords = item.commonNames && item.commonNames.flatMap((name) => name.toLowerCase().split(/\s+/)); + + // Lowercase scientificName and split into individual words + const scientificNameWords = item.scientificName.toLowerCase().split(/\s+/); + + // Check if any word in commonNames or scientificName matches any word in searchTerms + return searchTerms.some( + (searchTerm) => + scientificNameWords.some((word) => word === searchTerm) || // Check if any word in scientific name matches any search term + (commonNameWords && commonNameWords.some((word) => word === searchTerm)) // Check if any word in common names matches any search term + ); + }; + + return data.sort(customSort); +}; + /** * Sorts the ITIS response such that exact matches with search terms are first * @@ -45,9 +88,9 @@ export const customSortContainsSearchTermsJoined = ( const commonNameWords = item.commonNames && item.commonNames.map((name) => name.toLowerCase()); // Lowercase scientificName and split into individual words - const scientificNameWords = item.scientificName.toLowerCase(); + const scientificNameWords = item.scientificName.toLowerCase().split(/\s+/); - return scientificNameWords.includes(searchTerms.join(' ')) || commonNameWords?.includes(searchTerms.join(' ')); + return commonNameWords?.includes(searchTerms.join(' ')) || scientificNameWords.includes(searchTerms.join(' ')); }; return data.sort(customSort); @@ -81,7 +124,7 @@ export const customSortEqualsSearchTermsJoined = ( const checkForMatch = (item: TaxonSearchResult, searchTerms: string[]) => { const commonNameWords = item.commonNames && item.commonNames.map((name) => name.toLowerCase()); - const scientificNameWord = item.scientificName.toLowerCase(); + const scientificNameWord = item.scientificName.toLowerCase() return scientificNameWord === searchTerms.join(' ') || commonNameWords.includes(searchTerms.join(' ')); }; From 85434b66069c0c5fd60883cd3972d7ddb785f489 Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Mon, 4 Mar 2024 16:18:37 -0800 Subject: [PATCH 07/27] fix exact match sorting --- api/src/paths/taxonomy/taxon/index.ts | 2 +- api/src/paths/taxonomy/taxon/tsn/index.ts | 6 ++++++ api/src/services/itis-service.ts | 2 +- api/src/services/taxonomy-service.ts | 2 +- api/src/utils/itis.ts | 16 ++++++++++++---- 5 files changed, 21 insertions(+), 7 deletions(-) diff --git a/api/src/paths/taxonomy/taxon/index.ts b/api/src/paths/taxonomy/taxon/index.ts index 590927f9..cec7ed75 100644 --- a/api/src/paths/taxonomy/taxon/index.ts +++ b/api/src/paths/taxonomy/taxon/index.ts @@ -107,7 +107,7 @@ export function findTaxonBySearchTerms(): RequestHandler { const response = await itisService.searchItisByTerm(searchTerms); // Overwrite default cache-control header, allow caching up to 7 days - res.setHeader('Cache-Control', 'max-age=604800'); + res.setHeader('Cache-Control', 'no-cache'); res.status(200).json({ searchResponse: response }); } catch (error) { diff --git a/api/src/paths/taxonomy/taxon/tsn/index.ts b/api/src/paths/taxonomy/taxon/tsn/index.ts index 94e61936..e80b03c0 100644 --- a/api/src/paths/taxonomy/taxon/tsn/index.ts +++ b/api/src/paths/taxonomy/taxon/tsn/index.ts @@ -58,6 +58,12 @@ GET.apiDoc = { }, scientificName: { type: 'string' + }, + rank: { + type: 'string' + }, + kingdom: { + type: 'string' } }, additionalProperties: false diff --git a/api/src/services/itis-service.ts b/api/src/services/itis-service.ts index c7048ba3..ed1aa7b2 100644 --- a/api/src/services/itis-service.ts +++ b/api/src/services/itis-service.ts @@ -118,7 +118,7 @@ export class ItisService { return `${itisUrl}?${this._getItisSolrTypeParam()}&${this._getItisSolrSortParam( ['kingdom'], ['asc'], - 50 + 150 )}&${this._getItisSolrFilterParam()}&${this._getItisSolrQueryParam(searchTerms)}`; } diff --git a/api/src/services/taxonomy-service.ts b/api/src/services/taxonomy-service.ts index 3b420251..8988465a 100644 --- a/api/src/services/taxonomy-service.ts +++ b/api/src/services/taxonomy-service.ts @@ -59,7 +59,7 @@ export class TaxonomyService { return taxonRecords.map((item: TaxonRecord) => ({ tsn: item.itis_tsn, commonNames: [item.common_name], - scientificName: item.itis_scientific_name, + scientificName: item.itis_scientific_name })); } diff --git a/api/src/utils/itis.ts b/api/src/utils/itis.ts index 0d56f61e..7d0ac2f0 100644 --- a/api/src/utils/itis.ts +++ b/api/src/utils/itis.ts @@ -13,6 +13,10 @@ export const sortExactMatches = (data: TaxonSearchResult[], searchTerms: string[ const someEquals = customSortContainsSearchTermsJoined(contains, searchTermsLower); const exactEquals = customSortEqualsSearchTermsJoined(someEquals, searchTermsLower); + console.log(someEquals); + console.log(exactEquals); + console.log(exactEquals); + return exactEquals; }; @@ -51,7 +55,7 @@ export const customSortContainsAnyMatchingSearchTerm = ( return searchTerms.some( (searchTerm) => scientificNameWords.some((word) => word === searchTerm) || // Check if any word in scientific name matches any search term - (commonNameWords && commonNameWords.some((word) => word === searchTerm)) // Check if any word in common names matches any search term + commonNameWords?.includes(searchTerm) // Check if any word in common names matches any search term ); }; @@ -76,7 +80,7 @@ export const customSortContainsSearchTermsJoined = ( if (aInReference && !bInReference) { return -1; // Place items from searchTerms before other items } else if (!aInReference && bInReference) { - return 0; // Place other items after items from searchTerms + return 1; // Place other items after items from searchTerms } else { return 0; // Maintain the original order if both are from searchTerms or both are not } @@ -124,9 +128,13 @@ export const customSortEqualsSearchTermsJoined = ( const checkForMatch = (item: TaxonSearchResult, searchTerms: string[]) => { const commonNameWords = item.commonNames && item.commonNames.map((name) => name.toLowerCase()); - const scientificNameWord = item.scientificName.toLowerCase() + const scientificNameWord = item.scientificName.toLowerCase(); - return scientificNameWord === searchTerms.join(' ') || commonNameWords.includes(searchTerms.join(' ')); + // Add a space such that "Black bear" matches "American black bear" and not "Black Bearded" + return ( + scientificNameWord === searchTerms.join(' ') || + commonNameWords?.some((name) => `${name}${' '}`.includes(`${searchTerms.join(' ')}${' '}`)) + ); }; return data.sort(customSort); From 410dcd8ad5ef4bd1701def1d8eda427cb4a42f26 Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Mon, 4 Mar 2024 16:56:46 -0800 Subject: [PATCH 08/27] restore cache to 7 days --- api/src/paths/taxonomy/taxon/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/paths/taxonomy/taxon/index.ts b/api/src/paths/taxonomy/taxon/index.ts index cec7ed75..590927f9 100644 --- a/api/src/paths/taxonomy/taxon/index.ts +++ b/api/src/paths/taxonomy/taxon/index.ts @@ -107,7 +107,7 @@ export function findTaxonBySearchTerms(): RequestHandler { const response = await itisService.searchItisByTerm(searchTerms); // Overwrite default cache-control header, allow caching up to 7 days - res.setHeader('Cache-Control', 'no-cache'); + res.setHeader('Cache-Control', 'max-age=604800'); res.status(200).json({ searchResponse: response }); } catch (error) { From f25fd6e1d82fb21c3ba932da97f4d37f0ed6d785 Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Mon, 4 Mar 2024 16:57:29 -0800 Subject: [PATCH 09/27] console logs --- api/src/utils/itis.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/api/src/utils/itis.ts b/api/src/utils/itis.ts index 7d0ac2f0..4f17fffa 100644 --- a/api/src/utils/itis.ts +++ b/api/src/utils/itis.ts @@ -12,11 +12,7 @@ export const sortExactMatches = (data: TaxonSearchResult[], searchTerms: string[ const contains = customSortContainsAnyMatchingSearchTerm(data, searchTermsLower); const someEquals = customSortContainsSearchTermsJoined(contains, searchTermsLower); const exactEquals = customSortEqualsSearchTermsJoined(someEquals, searchTermsLower); - - console.log(someEquals); - console.log(exactEquals); - console.log(exactEquals); - + return exactEquals; }; From 978933bfc439ced417e61b460cb6107542478a40 Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Mon, 4 Mar 2024 17:35:19 -0800 Subject: [PATCH 10/27] fix tests --- api/src/services/itis-service.test.ts | 25 ++++++++++++++--------- api/src/services/itis-service.ts | 4 ++-- api/src/services/taxonomy-service.test.ts | 9 ++++---- api/src/services/taxonomy-service.ts | 1 + api/src/utils/itis.ts | 2 +- 5 files changed, 24 insertions(+), 17 deletions(-) diff --git a/api/src/services/itis-service.test.ts b/api/src/services/itis-service.test.ts index 67b16a8f..be8b8dd7 100644 --- a/api/src/services/itis-service.test.ts +++ b/api/src/services/itis-service.test.ts @@ -40,14 +40,15 @@ describe('ItisService', () => { response: { docs: [ { - commonNames: ['$commonNames'], + commonNames: ['$commonNames$English'], kingdom: 'kingdom', name: 'name', parentTSN: 'parentTSN', scientificName: 'scientificName', tsn: '123', updateDate: 'updateDate', - usage: 'usage' + usage: '', + rank: 'kingdom' } ] } @@ -67,8 +68,10 @@ describe('ItisService', () => { expect(response).to.eql([ { tsn: 123, - commonNames: 'commonNames', - scientificName: 'scientificName' + commonNames: ['commonNames'], + scientificName: 'scientificName', + rank: 'kingdom', + kingdom: 'kingdom' } ]); @@ -121,14 +124,15 @@ describe('ItisService', () => { response: { docs: [ { - commonNames: ['$commonNames'], + commonNames: ['$commonNames$English'], kingdom: 'kingdom', name: 'name', parentTSN: 'parentTSN', scientificName: 'scientificName', tsn: '123', updateDate: 'updateDate', - usage: 'usage' + usage: '', + rank: 'kingdom' } ] } @@ -145,14 +149,15 @@ describe('ItisService', () => { expect(response).to.eql([ { - commonNames: ['$commonNames'], + commonNames: ['$commonNames$English'], kingdom: 'kingdom', name: 'name', parentTSN: 'parentTSN', scientificName: 'scientificName', tsn: '123', updateDate: 'updateDate', - usage: 'usage' + usage: '', + rank: 'kingdom' } ]); @@ -200,7 +205,7 @@ describe('ItisService', () => { const response = await itisService.getItisSolrTermSearchUrl(['term']); expect(response).to.equal( - 'https://services.itis.gov/?wt=json&sort=nameWOInd+asc&rows=25&omitHeader=true&fl=tsn+scientificName:nameWOInd+kingdom+parentTSN+commonNames:vernacular+updateDate+usage&q=((nameWOInd:*term*+AND+usage:/(valid|accepted)/)+OR+(vernacular:*term*+AND+usage:/(valid|accepted)/))' + 'https://services.itis.gov/?wt=json&sort=kingdom+asc&rows=150&omitHeader=true&fl=tsn+scientificName:nameWOInd+kingdom+parentTSN+commonNames:vernacular+updateDate+usage+rank&q=((nameWOInd:*term*+AND+usage:/(valid|accepted)/)+OR+(vernacular:*term*+AND+usage:/(valid|accepted)/))' ); }); }); @@ -228,7 +233,7 @@ describe('ItisService', () => { const response = await itisService.getItisSolrTsnSearchUrl([123]); expect(response).to.equal( - 'https://services.itis.gov/??wt=json&sort=nameWOInd+asc&rows=25&omitHeader=true&fl=tsn+scientificName:nameWOInd+kingdom+parentTSN+commonNames:vernacular+updateDate+usage&&q=tsn:123' + 'https://services.itis.gov/??wt=json&sort=kingdom+asc&rows=150&omitHeader=true&fl=tsn+scientificName:nameWOInd+kingdom+parentTSN+commonNames:vernacular+updateDate+usage+rank&&q=tsn:123' ); }); }); diff --git a/api/src/services/itis-service.ts b/api/src/services/itis-service.ts index ed1aa7b2..93f12388 100644 --- a/api/src/services/itis-service.ts +++ b/api/src/services/itis-service.ts @@ -140,7 +140,7 @@ export class ItisService { return `${itisUrl}??${this._getItisSolrTypeParam()}&${this._getItisSolrSortParam( ['kingdom'], ['asc'], - 100 + 150 )}&${this._getItisSolrFilterParam()}&&q=${this._getItisSolrTsnSearch(searchTsnIds)}`; } @@ -184,7 +184,7 @@ export class ItisService { * @memberof ItisService */ _getItisSolrFilterParam(): string { - return 'omitHeader=true&fl=tsn+scientificName:nameWOInd+kingdom+parentTSN+commonNames:vernacular+updateDate+usage+rank:rankId'; + return 'omitHeader=true&fl=tsn+scientificName:nameWOInd+kingdom+parentTSN+commonNames:vernacular+updateDate+usage+rank'; } /** diff --git a/api/src/services/taxonomy-service.test.ts b/api/src/services/taxonomy-service.test.ts index 5940c038..1ddc58b9 100644 --- a/api/src/services/taxonomy-service.test.ts +++ b/api/src/services/taxonomy-service.test.ts @@ -23,7 +23,8 @@ describe('TaxonomyService', () => { scientificName: 'scientificName', tsn: 'tsn', updateDate: 'updateDate', - usage: 'usage' + usage: 'usage', + rank: 'rank' } ]; @@ -63,7 +64,7 @@ describe('TaxonomyService', () => { const response = await taxonomyService.getTaxonByTsnIds([1]); expect(repo).to.be.calledOnce; - expect(response).to.be.eql([{ tsn: 1, commonNames: 'common_name', scientificName: 'itis_scientific_name' }]); + expect(response).to.be.eql([{ tsn: 1, commonNames: ['common_name'], scientificName: 'itis_scientific_name' }]); }); it('if some records do not exist in db should return array of taxon records', async () => { @@ -118,8 +119,8 @@ describe('TaxonomyService', () => { expect(searchItisByTSNStub).to.be.calledOnce; expect(itisService).to.be.calledOnce; expect(response).to.be.eql([ - { tsn: 1, commonNames: 'common_name', scientificName: 'itis_scientific_name' }, - { tsn: 2, commonNames: 'common_name', scientificName: 'itis_scientific_name' } + { tsn: 1, commonNames: ['common_name'], scientificName: 'itis_scientific_name' }, + { tsn: 2, commonNames: ['common_name'], scientificName: 'itis_scientific_name' } ]); }); }); diff --git a/api/src/services/taxonomy-service.ts b/api/src/services/taxonomy-service.ts index 8988465a..4da753ab 100644 --- a/api/src/services/taxonomy-service.ts +++ b/api/src/services/taxonomy-service.ts @@ -58,6 +58,7 @@ export class TaxonomyService { _sanitizeTaxonRecordsData(taxonRecords: TaxonRecord[]): TaxonSearchResult[] { return taxonRecords.map((item: TaxonRecord) => ({ tsn: item.itis_tsn, + // placeholder: wrap commonNames in array until the database supports multiple common names commonNames: [item.common_name], scientificName: item.itis_scientific_name })); diff --git a/api/src/utils/itis.ts b/api/src/utils/itis.ts index 4f17fffa..3b6a03a5 100644 --- a/api/src/utils/itis.ts +++ b/api/src/utils/itis.ts @@ -12,7 +12,7 @@ export const sortExactMatches = (data: TaxonSearchResult[], searchTerms: string[ const contains = customSortContainsAnyMatchingSearchTerm(data, searchTermsLower); const someEquals = customSortContainsSearchTermsJoined(contains, searchTermsLower); const exactEquals = customSortEqualsSearchTermsJoined(someEquals, searchTermsLower); - + return exactEquals; }; From 2571f2d2c685aa0c5d5775961193803e25eaa5ce Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Mon, 4 Mar 2024 21:52:01 -0800 Subject: [PATCH 11/27] common name from string to array --- api/src/paths/taxonomy/taxon/index.test.ts | 2 +- api/src/paths/taxonomy/taxon/tsn/index.ts | 6 +- api/src/repositories/taxonomy-repository.ts | 2 +- api/src/services/itis-service.ts | 3 +- api/src/services/taxonomy-service.ts | 4 +- api/src/utils/itis.ts | 104 ++++++++++---------- 6 files changed, 63 insertions(+), 58 deletions(-) diff --git a/api/src/paths/taxonomy/taxon/index.test.ts b/api/src/paths/taxonomy/taxon/index.test.ts index f0d0382b..bd4662e1 100644 --- a/api/src/paths/taxonomy/taxon/index.test.ts +++ b/api/src/paths/taxonomy/taxon/index.test.ts @@ -52,7 +52,7 @@ describe('taxon', () => { sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); - const mock1 = { id: '1', commonNames: 'something', scientificName: 'string' } as unknown as any; + const mock1 = { id: '1', commonNames: ['something'], scientificName: 'string' } as unknown as any; const mock2 = { id: '2', commonNames: null, scientificName: 'string' } as unknown as any; const getSpeciesFromIdsStub = sinon.stub(ItisService.prototype, 'searchItisByTerm').resolves([mock1, mock2]); diff --git a/api/src/paths/taxonomy/taxon/tsn/index.ts b/api/src/paths/taxonomy/taxon/tsn/index.ts index e80b03c0..ca366067 100644 --- a/api/src/paths/taxonomy/taxon/tsn/index.ts +++ b/api/src/paths/taxonomy/taxon/tsn/index.ts @@ -53,8 +53,10 @@ GET.apiDoc = { type: 'integer' }, commonNames: { - type: 'string', - nullable: true + type: 'array', + items: { + type: 'string' + } }, scientificName: { type: 'string' diff --git a/api/src/repositories/taxonomy-repository.ts b/api/src/repositories/taxonomy-repository.ts index ce5416e5..11ec214f 100644 --- a/api/src/repositories/taxonomy-repository.ts +++ b/api/src/repositories/taxonomy-repository.ts @@ -12,7 +12,7 @@ export const TaxonRecord = z.object({ itis_tsn: z.number(), bc_taxon_code: z.string().nullable(), itis_scientific_name: z.string(), - common_name: z.string(), + common_name: z.string().nullable(), itis_data: z.record(z.any()), record_effective_date: z.string(), record_end_date: z.string().nullable(), diff --git a/api/src/services/itis-service.ts b/api/src/services/itis-service.ts index 93f12388..b1340052 100644 --- a/api/src/services/itis-service.ts +++ b/api/src/services/itis-service.ts @@ -49,7 +49,8 @@ export class ItisService { // Sort the results to place exact matches at the top const sortedResponse = sortExactMatches(sanitizedResponse, searchTerms); - // Return only 25 records + // Return only a subset of the records + // More records than are returned here are requested from ITIS to help find and prioritize exact matches return sortedResponse.slice(0, 15); } diff --git a/api/src/services/taxonomy-service.ts b/api/src/services/taxonomy-service.ts index 4da753ab..0e44727d 100644 --- a/api/src/services/taxonomy-service.ts +++ b/api/src/services/taxonomy-service.ts @@ -7,7 +7,7 @@ const defaultLog = getLogger('services/taxonomy-service'); export type TaxonSearchResult = { tsn: number; - commonNames: string[]; + commonNames: string[] | []; scientificName: string; }; @@ -59,7 +59,7 @@ export class TaxonomyService { return taxonRecords.map((item: TaxonRecord) => ({ tsn: item.itis_tsn, // placeholder: wrap commonNames in array until the database supports multiple common names - commonNames: [item.common_name], + commonNames: item?.common_name ? [item.common_name] : [], scientificName: item.itis_scientific_name })); } diff --git a/api/src/utils/itis.ts b/api/src/utils/itis.ts index 3b6a03a5..96e6fe64 100644 --- a/api/src/utils/itis.ts +++ b/api/src/utils/itis.ts @@ -1,7 +1,7 @@ import { TaxonSearchResult } from '../services/taxonomy-service'; /** - * Sorts the ITIS response such that exact matches with search terms are first + * Sorts the ITIS response such that exact matches are at the start of the array of matching taxa * * @param {ItisSolrSearchResponse[]} data * @memberof ItisService @@ -9,9 +9,16 @@ import { TaxonSearchResult } from '../services/taxonomy-service'; export const sortExactMatches = (data: TaxonSearchResult[], searchTerms: string[]): TaxonSearchResult[] => { const searchTermsLower = searchTerms.map((item) => item.toLowerCase()); + // Prioritize taxa where any word in the scientific or common name matches any of the search terms + // eg. ['Black', 'bear'] -> "Black" matches on "Black widow" const contains = customSortContainsAnyMatchingSearchTerm(data, searchTermsLower); - const someEquals = customSortContainsSearchTermsJoined(contains, searchTermsLower); - const exactEquals = customSortEqualsSearchTermsJoined(someEquals, searchTermsLower); + + // Prioritize records where any word in the scientific or common name matches the JOINED search terms + // const someEquals = customSortContainsSearchTermsJoined(contains, searchTermsLower); + + // Prioritize taxa where either the scientific name or any common name CONTAINS the search terms joined + // eg. ['Black', 'bear'] -> "Black bear" matches on "American black bear" + const exactEquals = customSortContainsSearchTermsJoined(contains, searchTermsLower); return exactEquals; }; @@ -41,60 +48,56 @@ export const customSortContainsAnyMatchingSearchTerm = ( }; const checkForMatch = (item: TaxonSearchResult, searchTerms: string[]) => { - // Lowercase commonNames and split into individual words - const commonNameWords = item.commonNames && item.commonNames.flatMap((name) => name.toLowerCase().split(/\s+/)); + const commonNameWords = item.commonNames.flatMap((name: string) => name.toLowerCase().split(/\s+/)) ?? []; - // Lowercase scientificName and split into individual words const scientificNameWords = item.scientificName.toLowerCase().split(/\s+/); // Check if any word in commonNames or scientificName matches any word in searchTerms return searchTerms.some( - (searchTerm) => - scientificNameWords.some((word) => word === searchTerm) || // Check if any word in scientific name matches any search term - commonNameWords?.includes(searchTerm) // Check if any word in common names matches any search term + (searchTerm) => scientificNameWords?.includes(searchTerm) || commonNameWords?.includes(searchTerm) ); }; return data.sort(customSort); }; -/** - * Sorts the ITIS response such that exact matches with search terms are first - * - * @param {ItisSolrSearchResponse[]} data - * @memberof ItisService - */ -export const customSortContainsSearchTermsJoined = ( - data: TaxonSearchResult[], - searchTerms: string[] -): TaxonSearchResult[] => { - // Custom sorting function - const customSort = (a: TaxonSearchResult, b: TaxonSearchResult) => { - const aInReference = checkForMatch(a, searchTerms); - const bInReference = checkForMatch(b, searchTerms); - - if (aInReference && !bInReference) { - return -1; // Place items from searchTerms before other items - } else if (!aInReference && bInReference) { - return 1; // Place other items after items from searchTerms - } else { - return 0; // Maintain the original order if both are from searchTerms or both are not - } - }; - - // Function to check if an item is a match with search terms - const checkForMatch = (item: TaxonSearchResult, searchTerms: string[]) => { - // Lowercase commonNames and split into individual words - const commonNameWords = item.commonNames && item.commonNames.map((name) => name.toLowerCase()); - - // Lowercase scientificName and split into individual words - const scientificNameWords = item.scientificName.toLowerCase().split(/\s+/); - - return commonNameWords?.includes(searchTerms.join(' ')) || scientificNameWords.includes(searchTerms.join(' ')); - }; - - return data.sort(customSort); -}; +// /** +// * Sorts the ITIS response such that exact matches with search terms are first +// * +// * @param {ItisSolrSearchResponse[]} data +// * @memberof ItisService +// */ +// export const customSortContainsSearchTermsJoined = ( +// data: TaxonSearchResult[], +// searchTerms: string[] +// ): TaxonSearchResult[] => { +// // Custom sorting function +// const customSort = (a: TaxonSearchResult, b: TaxonSearchResult) => { +// const aInReference = checkForMatch(a, searchTerms); +// const bInReference = checkForMatch(b, searchTerms); + +// if (aInReference && !bInReference) { +// return -1; // Place items from searchTerms before other items +// } else if (!aInReference && bInReference) { +// return 1; // Place other items after items from searchTerms +// } else { +// return 0; // Maintain the original order if both are from searchTerms or both are not +// } +// }; + +// // Function to check if an item is a match with search terms +// const checkForMatch = (item: TaxonSearchResult, searchTerms: string[]) => { +// // Lowercase commonNames and split into individual words +// const commonNameWords = item.commonNames && item.commonNames.map((name) => name.toLowerCase()); + +// // Lowercase scientificName and split into individual words +// const scientificNameWords = item.scientificName.toLowerCase().split(/\s+/); + +// return commonNameWords?.includes(searchTerms.join(' ')) || scientificNameWords.includes(searchTerms.join(' ')); +// }; + +// return data.sort(customSort); +// }; /** * Sorts the ITIS response such that exact matches with search terms are first @@ -102,11 +105,10 @@ export const customSortContainsSearchTermsJoined = ( * @param {ItisSolrSearchResponse[]} data * @memberof ItisService */ -export const customSortEqualsSearchTermsJoined = ( +export const customSortContainsSearchTermsJoined = ( data: TaxonSearchResult[], searchTerms: string[] ): TaxonSearchResult[] => { - // Custom sorting function const customSort = (a: TaxonSearchResult, b: TaxonSearchResult) => { const aInReference = checkForMatch(a, searchTerms); const bInReference = checkForMatch(b, searchTerms); @@ -114,15 +116,15 @@ export const customSortEqualsSearchTermsJoined = ( if (aInReference && !bInReference) { return -1; // Place items from searchTerms before other items } else if (!aInReference && bInReference) { - return 0; // Place other items after items from searchTerms + return 0; // Maintain the original order } else { - return 0; // Maintain the original order if both are from searchTerms or both are not + return 0; // Maintain the original order } }; // Function to check if an item is a match with search terms const checkForMatch = (item: TaxonSearchResult, searchTerms: string[]) => { - const commonNameWords = item.commonNames && item.commonNames.map((name) => name.toLowerCase()); + const commonNameWords = item.commonNames.map((name: string) => name.toLowerCase()); const scientificNameWord = item.scientificName.toLowerCase(); From 7410677945b56fa202c9d4837c59cbac4f3960e4 Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Tue, 5 Mar 2024 07:43:33 -0800 Subject: [PATCH 12/27] cleanup --- api/src/repositories/taxonomy-repository.ts | 4 +- api/src/services/itis-service.ts | 11 +-- api/src/utils/itis.ts | 77 ++++++++++----------- 3 files changed, 43 insertions(+), 49 deletions(-) diff --git a/api/src/repositories/taxonomy-repository.ts b/api/src/repositories/taxonomy-repository.ts index 11ec214f..10de882e 100644 --- a/api/src/repositories/taxonomy-repository.ts +++ b/api/src/repositories/taxonomy-repository.ts @@ -62,7 +62,7 @@ export class TaxonomyRepository extends BaseRepository { async addItisTaxonRecord( itisTsn: number, itisScientificName: string, - commonNames: string | null, + commonName: string | null, itisData: Record, itisUpdateDate: string ): Promise { @@ -82,7 +82,7 @@ export class TaxonomyRepository extends BaseRepository { VALUES ( ${itisTsn}, ${itisScientificName}, - ${commonNames}, + ${commonName}, ${itisData}, ${itisUpdateDate} ) diff --git a/api/src/services/itis-service.ts b/api/src/services/itis-service.ts index b1340052..dbd00c53 100644 --- a/api/src/services/itis-service.ts +++ b/api/src/services/itis-service.ts @@ -76,12 +76,7 @@ export class ItisService { } /** - * Sorts by exact matches within, ie. Keywords of "Black" and "Bear" would match on "Black Willow" - * - */ - - /** - * Cleans up the ITIS search response data. + * Cleans up the ITIS search response data * * @param {ItisSolrSearchResponse[]} data * @memberof ItisService @@ -89,11 +84,11 @@ export class ItisService { _sanitizeItisData = (data: ItisSolrSearchResponse[]): TaxonSearchResult[] => { return data.map((item: ItisSolrSearchResponse) => { const englishNames = item.commonNames?.filter((name) => name.split('$')[2] === 'English'); - const commonNames = englishNames ? englishNames.map((name) => name.split('$')[1]) : null; + const commonNames = englishNames && englishNames.map((name) => name.split('$')[1]) return { tsn: Number(item.tsn), - commonNames: commonNames || [], + commonNames: commonNames, scientificName: item.scientificName, rank: item.rank, kingdom: item.kingdom diff --git a/api/src/utils/itis.ts b/api/src/utils/itis.ts index 96e6fe64..c67d9ebd 100644 --- a/api/src/utils/itis.ts +++ b/api/src/utils/itis.ts @@ -14,11 +14,11 @@ export const sortExactMatches = (data: TaxonSearchResult[], searchTerms: string[ const contains = customSortContainsAnyMatchingSearchTerm(data, searchTermsLower); // Prioritize records where any word in the scientific or common name matches the JOINED search terms - // const someEquals = customSortContainsSearchTermsJoined(contains, searchTermsLower); + const someEquals = customSortContainsSearchTermsJoined(contains, searchTermsLower); // Prioritize taxa where either the scientific name or any common name CONTAINS the search terms joined // eg. ['Black', 'bear'] -> "Black bear" matches on "American black bear" - const exactEquals = customSortContainsSearchTermsJoined(contains, searchTermsLower); + const exactEquals = customSortContainsSearchTermsJoined(someEquals, searchTermsLower); return exactEquals; }; @@ -61,43 +61,42 @@ export const customSortContainsAnyMatchingSearchTerm = ( return data.sort(customSort); }; -// /** -// * Sorts the ITIS response such that exact matches with search terms are first -// * -// * @param {ItisSolrSearchResponse[]} data -// * @memberof ItisService -// */ -// export const customSortContainsSearchTermsJoined = ( -// data: TaxonSearchResult[], -// searchTerms: string[] -// ): TaxonSearchResult[] => { -// // Custom sorting function -// const customSort = (a: TaxonSearchResult, b: TaxonSearchResult) => { -// const aInReference = checkForMatch(a, searchTerms); -// const bInReference = checkForMatch(b, searchTerms); - -// if (aInReference && !bInReference) { -// return -1; // Place items from searchTerms before other items -// } else if (!aInReference && bInReference) { -// return 1; // Place other items after items from searchTerms -// } else { -// return 0; // Maintain the original order if both are from searchTerms or both are not -// } -// }; - -// // Function to check if an item is a match with search terms -// const checkForMatch = (item: TaxonSearchResult, searchTerms: string[]) => { -// // Lowercase commonNames and split into individual words -// const commonNameWords = item.commonNames && item.commonNames.map((name) => name.toLowerCase()); - -// // Lowercase scientificName and split into individual words -// const scientificNameWords = item.scientificName.toLowerCase().split(/\s+/); - -// return commonNameWords?.includes(searchTerms.join(' ')) || scientificNameWords.includes(searchTerms.join(' ')); -// }; - -// return data.sort(customSort); -// }; +/** + * Sorts the ITIS response such that exact matches with search terms are first + * + * @param {ItisSolrSearchResponse[]} data + * @memberof ItisService + */ +export const customSortContainsSearchTermsJoined = ( + data: TaxonSearchResult[], + searchTerms: string[] +): TaxonSearchResult[] => { + // Custom sorting function + const customSort = (a: TaxonSearchResult, b: TaxonSearchResult) => { + const aInReference = checkForMatch(a, searchTerms); + const bInReference = checkForMatch(b, searchTerms); + + if (aInReference && !bInReference) { + return -1; // Place items from searchTerms before other items + } else if (!aInReference && bInReference) { + return 1; // Place other items after items from searchTerms + } else { + return 0; // Maintain the original order if both are from searchTerms or both are not + } + }; + + // Function to check if an item is a match with search terms + const checkForMatch = (item: TaxonSearchResult, searchTerms: string[]) => { + const commonNameWords = item.commonNames?.map((name) => name.toLowerCase()); + + // Lowercase scientificName and split into individual words + const scientificNameWords = item.scientificName.toLowerCase().split(/\s+/); + + return commonNameWords?.includes(searchTerms.join(' ')) || scientificNameWords.includes(searchTerms.join(' ')); + }; + + return data.sort(customSort); +}; /** * Sorts the ITIS response such that exact matches with search terms are first From b5fd23ed73df4b5e3bd64c3f2a34c41887f57cad Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Tue, 5 Mar 2024 08:14:00 -0800 Subject: [PATCH 13/27] comments on itis sorting functions --- api/src/services/itis-service.ts | 4 +-- api/src/utils/itis.test.ts | 1 + api/src/utils/itis.ts | 62 ++++++++++++++++---------------- 3 files changed, 35 insertions(+), 32 deletions(-) create mode 100644 api/src/utils/itis.test.ts diff --git a/api/src/services/itis-service.ts b/api/src/services/itis-service.ts index dbd00c53..6511d32f 100644 --- a/api/src/services/itis-service.ts +++ b/api/src/services/itis-service.ts @@ -84,11 +84,11 @@ export class ItisService { _sanitizeItisData = (data: ItisSolrSearchResponse[]): TaxonSearchResult[] => { return data.map((item: ItisSolrSearchResponse) => { const englishNames = item.commonNames?.filter((name) => name.split('$')[2] === 'English'); - const commonNames = englishNames && englishNames.map((name) => name.split('$')[1]) + const commonNames = englishNames?.map((name) => name.split('$')[1]); return { tsn: Number(item.tsn), - commonNames: commonNames, + commonNames: commonNames ?? [], scientificName: item.scientificName, rank: item.rank, kingdom: item.kingdom diff --git a/api/src/utils/itis.test.ts b/api/src/utils/itis.test.ts new file mode 100644 index 00000000..0ffdd02f --- /dev/null +++ b/api/src/utils/itis.test.ts @@ -0,0 +1 @@ +// TODO \ No newline at end of file diff --git a/api/src/utils/itis.ts b/api/src/utils/itis.ts index c67d9ebd..bb65dbd9 100644 --- a/api/src/utils/itis.ts +++ b/api/src/utils/itis.ts @@ -1,32 +1,36 @@ import { TaxonSearchResult } from '../services/taxonomy-service'; /** - * Sorts the ITIS response such that exact matches are at the start of the array of matching taxa + * Sorts the ITIS response by how strongly records match the search terms * * @param {ItisSolrSearchResponse[]} data + * @param {string[]} searchTerms * @memberof ItisService */ export const sortExactMatches = (data: TaxonSearchResult[], searchTerms: string[]): TaxonSearchResult[] => { const searchTermsLower = searchTerms.map((item) => item.toLowerCase()); - // Prioritize taxa where any word in the scientific or common name matches any of the search terms + // Prioritize taxa where any word in the scientific or common name matches ANY of the search terms // eg. ['Black', 'bear'] -> "Black" matches on "Black widow" - const contains = customSortContainsAnyMatchingSearchTerm(data, searchTermsLower); - - // Prioritize records where any word in the scientific or common name matches the JOINED search terms - const someEquals = customSortContainsSearchTermsJoined(contains, searchTermsLower); + const containsAnyMatch = customSortContainsAnyMatchingSearchTerm(data, searchTermsLower); // Prioritize taxa where either the scientific name or any common name CONTAINS the search terms joined // eg. ['Black', 'bear'] -> "Black bear" matches on "American black bear" - const exactEquals = customSortContainsSearchTermsJoined(someEquals, searchTermsLower); + const containsAnyMatchJoined = customSortContainsSearchTermsJoinedExact(containsAnyMatch, searchTermsLower); + + // Prioritize taxa where either the scientific name or any common name is EXACTLY EQUAL to the search terms joined + // eg. ['Wolf'] -> "Wolf" is prioritized over "Forest Wolf" + const exactlyEquals = customSortEqualsSearchTermsExact(containsAnyMatchJoined, searchTermsLower); - return exactEquals; + return exactlyEquals; }; /** - * Sorts the ITIS response such that exact matches with search terms are first + * Sorts the ITIS response to prioritize records where any word in the scientific or + * common name matches ANY of the search terms * * @param {ItisSolrSearchResponse[]} data + * @param {string[]} searchTerms * @memberof ItisService */ export const customSortContainsAnyMatchingSearchTerm = ( @@ -48,8 +52,7 @@ export const customSortContainsAnyMatchingSearchTerm = ( }; const checkForMatch = (item: TaxonSearchResult, searchTerms: string[]) => { - const commonNameWords = item.commonNames.flatMap((name: string) => name.toLowerCase().split(/\s+/)) ?? []; - + const commonNameWords = item.commonNames?.flatMap((name: string) => name.toLowerCase().split(/\s+/)); const scientificNameWords = item.scientificName.toLowerCase().split(/\s+/); // Check if any word in commonNames or scientificName matches any word in searchTerms @@ -62,16 +65,17 @@ export const customSortContainsAnyMatchingSearchTerm = ( }; /** - * Sorts the ITIS response such that exact matches with search terms are first + * Sorts the ITIS response to prioritize records where either the scientific name or + * any common name CONTAINS the search terms joined * * @param {ItisSolrSearchResponse[]} data + * @param {string[]} searchTerms * @memberof ItisService */ -export const customSortContainsSearchTermsJoined = ( +export const customSortContainsSearchTermsJoinedExact = ( data: TaxonSearchResult[], searchTerms: string[] ): TaxonSearchResult[] => { - // Custom sorting function const customSort = (a: TaxonSearchResult, b: TaxonSearchResult) => { const aInReference = checkForMatch(a, searchTerms); const bInReference = checkForMatch(b, searchTerms); @@ -79,32 +83,35 @@ export const customSortContainsSearchTermsJoined = ( if (aInReference && !bInReference) { return -1; // Place items from searchTerms before other items } else if (!aInReference && bInReference) { - return 1; // Place other items after items from searchTerms + return 0; // Maintain the original order } else { - return 0; // Maintain the original order if both are from searchTerms or both are not + return 0; // Maintain the original order } }; // Function to check if an item is a match with search terms const checkForMatch = (item: TaxonSearchResult, searchTerms: string[]) => { - const commonNameWords = item.commonNames?.map((name) => name.toLowerCase()); - - // Lowercase scientificName and split into individual words - const scientificNameWords = item.scientificName.toLowerCase().split(/\s+/); + const commonNameWords = item.commonNames?.map((name: string) => name.toLowerCase()); + const scientificNameWord = item.scientificName.toLowerCase(); - return commonNameWords?.includes(searchTerms.join(' ')) || scientificNameWords.includes(searchTerms.join(' ')); + // Add a space such that "Black bear" matches "American black bear" and not "Black Bearded" + return ( + scientificNameWord === searchTerms.join(' ') || + commonNameWords?.some((name) => `${name}${' '}`.includes(`${searchTerms.join(' ')}${' '}`)) + ); }; return data.sort(customSort); }; /** - * Sorts the ITIS response such that exact matches with search terms are first + * Sorts the ITIS response to prioritize taxa where either the scientific name or + * any common name is EXACTLY EQUAL to the search terms joined * * @param {ItisSolrSearchResponse[]} data * @memberof ItisService */ -export const customSortContainsSearchTermsJoined = ( +export const customSortEqualsSearchTermsExact = ( data: TaxonSearchResult[], searchTerms: string[] ): TaxonSearchResult[] => { @@ -123,15 +130,10 @@ export const customSortContainsSearchTermsJoined = ( // Function to check if an item is a match with search terms const checkForMatch = (item: TaxonSearchResult, searchTerms: string[]) => { - const commonNameWords = item.commonNames.map((name: string) => name.toLowerCase()); - + const commonNameWords = item.commonNames?.map((name: string) => name.toLowerCase()); const scientificNameWord = item.scientificName.toLowerCase(); - // Add a space such that "Black bear" matches "American black bear" and not "Black Bearded" - return ( - scientificNameWord === searchTerms.join(' ') || - commonNameWords?.some((name) => `${name}${' '}`.includes(`${searchTerms.join(' ')}${' '}`)) - ); + return scientificNameWord === searchTerms.join(' ') || commonNameWords?.includes(searchTerms.join(' ')); }; return data.sort(customSort); From 88e4b39827aa2488bb4ab4830323183dfd0c7a7f Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Tue, 5 Mar 2024 08:15:34 -0800 Subject: [PATCH 14/27] cleanup --- api/src/utils/itis.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/api/src/utils/itis.ts b/api/src/utils/itis.ts index bb65dbd9..81711750 100644 --- a/api/src/utils/itis.ts +++ b/api/src/utils/itis.ts @@ -109,6 +109,7 @@ export const customSortContainsSearchTermsJoinedExact = ( * any common name is EXACTLY EQUAL to the search terms joined * * @param {ItisSolrSearchResponse[]} data + * @param {string[]} searchTerms * @memberof ItisService */ export const customSortEqualsSearchTermsExact = ( From bdcc25a98e804badd317c0a2c08ce19fa2861035 Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Tue, 5 Mar 2024 14:37:29 -0800 Subject: [PATCH 15/27] undo commonName -> commonNames change in past migrations --- database/src/migrations/release.0.8.0/smoketest_release.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/src/migrations/release.0.8.0/smoketest_release.sql b/database/src/migrations/release.0.8.0/smoketest_release.sql index 864c9866..df58b3cb 100644 --- a/database/src/migrations/release.0.8.0/smoketest_release.sql +++ b/database/src/migrations/release.0.8.0/smoketest_release.sql @@ -215,7 +215,7 @@ declare SPECIES Amaranthus albus - Tumbleweed + Tumbleweed AMARALB From d772e39127abce9f32562e1b69d50c5558457232 Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Tue, 5 Mar 2024 14:52:53 -0800 Subject: [PATCH 16/27] fix placeholder tests --- api/src/paths/taxonomy/taxon/tsn/index.test.ts | 2 +- api/src/services/itis-service.ts | 2 +- api/src/utils/itis-sort.test.ts | 3 +++ api/src/utils/{itis.ts => itis-sort.ts} | 0 api/src/utils/itis.test.ts | 1 - 5 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 api/src/utils/itis-sort.test.ts rename api/src/utils/{itis.ts => itis-sort.ts} (100%) delete mode 100644 api/src/utils/itis.test.ts diff --git a/api/src/paths/taxonomy/taxon/tsn/index.test.ts b/api/src/paths/taxonomy/taxon/tsn/index.test.ts index 38bb0f1b..cddbcab3 100644 --- a/api/src/paths/taxonomy/taxon/tsn/index.test.ts +++ b/api/src/paths/taxonomy/taxon/tsn/index.test.ts @@ -53,7 +53,7 @@ describe('tsn', () => { sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); const mock1 = { tsn: '1', commonNames: ['something'], scientificName: 'string' } as unknown as any; - const mock2 = { tsn: '2', commonNames: null, scientificName: 'string' } as unknown as any; + const mock2 = { tsn: '2', commonNames: [], scientificName: 'string' } as unknown as any; const getTaxonByTsnIdsStub = sinon.stub(TaxonomyService.prototype, 'getTaxonByTsnIds').resolves([mock1, mock2]); diff --git a/api/src/services/itis-service.ts b/api/src/services/itis-service.ts index 6511d32f..0cf4dff8 100644 --- a/api/src/services/itis-service.ts +++ b/api/src/services/itis-service.ts @@ -1,5 +1,5 @@ import axios from 'axios'; -import { sortExactMatches } from '../utils/itis'; +import { sortExactMatches } from '../utils/itis-sort'; import { getLogger } from '../utils/logger'; import { TaxonSearchResult } from './taxonomy-service'; diff --git a/api/src/utils/itis-sort.test.ts b/api/src/utils/itis-sort.test.ts new file mode 100644 index 00000000..da26fb34 --- /dev/null +++ b/api/src/utils/itis-sort.test.ts @@ -0,0 +1,3 @@ +// TODO +export const placeholder = () => {} +// describe('itis-sort', () => {}); diff --git a/api/src/utils/itis.ts b/api/src/utils/itis-sort.ts similarity index 100% rename from api/src/utils/itis.ts rename to api/src/utils/itis-sort.ts diff --git a/api/src/utils/itis.test.ts b/api/src/utils/itis.test.ts deleted file mode 100644 index 0ffdd02f..00000000 --- a/api/src/utils/itis.test.ts +++ /dev/null @@ -1 +0,0 @@ -// TODO \ No newline at end of file From 70af144c66738304ef66b822a030a8751efb3c13 Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Thu, 7 Mar 2024 10:47:11 -0800 Subject: [PATCH 17/27] addressing PR comments --- api/package-lock.json | 121 +++++++++++++++----- api/src/repositories/taxonomy-repository.ts | 5 +- api/src/services/itis-service.ts | 4 +- api/src/services/taxonomy-service.ts | 26 ++--- 4 files changed, 109 insertions(+), 47 deletions(-) diff --git a/api/package-lock.json b/api/package-lock.json index ff4bdc6b..5642d2ba 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -678,6 +678,36 @@ "strip-ansi": "^7.0.1" } }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, "strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", @@ -686,6 +716,21 @@ "ansi-regex": "^6.0.1" } }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + } + } + }, "wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -695,6 +740,54 @@ "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } + }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + } + } } } }, @@ -9312,16 +9405,6 @@ "strip-ansi": "^6.0.1" } }, - "string-width-cjs": { - "version": "npm:string-width@4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, "string.prototype.padend": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.5.tgz", @@ -9379,14 +9462,6 @@ "ansi-regex": "^5.0.1" } }, - "strip-ansi-cjs": { - "version": "npm:strip-ansi@6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - }, "strip-bom": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", @@ -10727,16 +10802,6 @@ "strip-ansi": "^6.0.0" } }, - "wrap-ansi-cjs": { - "version": "npm:wrap-ansi@7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/api/src/repositories/taxonomy-repository.ts b/api/src/repositories/taxonomy-repository.ts index 10de882e..42acc1fb 100644 --- a/api/src/repositories/taxonomy-repository.ts +++ b/api/src/repositories/taxonomy-repository.ts @@ -62,12 +62,13 @@ export class TaxonomyRepository extends BaseRepository { async addItisTaxonRecord( itisTsn: number, itisScientificName: string, - commonName: string | null, + commonNames: string[], itisData: Record, itisUpdateDate: string ): Promise { defaultLog.debug({ label: 'addItisTaxonRecord', itisTsn }); + // TODO: Store multiple common names rather than just the first item of the commonNames array const sqlStatement = SQL` WITH inserted_row AS ( INSERT INTO @@ -82,7 +83,7 @@ export class TaxonomyRepository extends BaseRepository { VALUES ( ${itisTsn}, ${itisScientificName}, - ${commonName}, + ${commonNames[0]}, ${itisData}, ${itisUpdateDate} ) diff --git a/api/src/services/itis-service.ts b/api/src/services/itis-service.ts index 0cf4dff8..11614ce3 100644 --- a/api/src/services/itis-service.ts +++ b/api/src/services/itis-service.ts @@ -84,11 +84,11 @@ export class ItisService { _sanitizeItisData = (data: ItisSolrSearchResponse[]): TaxonSearchResult[] => { return data.map((item: ItisSolrSearchResponse) => { const englishNames = item.commonNames?.filter((name) => name.split('$')[2] === 'English'); - const commonNames = englishNames?.map((name) => name.split('$')[1]); + const commonNames = englishNames?.map((name) => name.split('$')[1]) ?? []; return { tsn: Number(item.tsn), - commonNames: commonNames ?? [], + commonNames: commonNames, scientificName: item.scientificName, rank: item.rank, kingdom: item.kingdom diff --git a/api/src/services/taxonomy-service.ts b/api/src/services/taxonomy-service.ts index 0e44727d..84b64fe2 100644 --- a/api/src/services/taxonomy-service.ts +++ b/api/src/services/taxonomy-service.ts @@ -7,7 +7,7 @@ const defaultLog = getLogger('services/taxonomy-service'); export type TaxonSearchResult = { tsn: number; - commonNames: string[] | []; + commonNames: string[]; scientificName: string; }; @@ -58,7 +58,7 @@ export class TaxonomyService { _sanitizeTaxonRecordsData(taxonRecords: TaxonRecord[]): TaxonSearchResult[] { return taxonRecords.map((item: TaxonRecord) => ({ tsn: item.itis_tsn, - // placeholder: wrap commonNames in array until the database supports multiple common names + // TODO: wrap commonNames in array until the database supports multiple common names commonNames: item?.common_name ? [item.common_name] : [], scientificName: item.itis_scientific_name })); @@ -72,19 +72,15 @@ export class TaxonomyService { * @memberof TaxonomyService */ async addItisTaxonRecord(itisSolrResponse: ItisSolrSearchResponse): Promise { - let commonNames = null; - if (itisSolrResponse.commonNames) { - const firstEnglishName = itisSolrResponse.commonNames.find((name) => name.split('$')[2] === 'English'); - commonNames = firstEnglishName ? firstEnglishName.split('$')[1] : null; - /* Sample itisResponse: - * commonNames: [ - * '$withered wooly milk-vetch$English$N$152846$2012-12-21 00:00:00$', - * '$woolly locoweed$English$N$124501$2011-06-29 00:00:00$', - * '$Davis Mountains locoweed$English$N$124502$2011-06-29 00:00:00$', - * '$woolly milkvetch$English$N$72035$2012-12-21 00:00:00$' - * ] - */ - } + const commonNames = itisSolrResponse.commonNames.filter((name) => name.split('$')[2] === 'English') ?? []; + /* Sample itisResponse: + * commonNames: [ + * '$withered wooly milk-vetch$English$N$152846$2012-12-21 00:00:00$', + * '$woolly locoweed$English$N$124501$2011-06-29 00:00:00$', + * '$Davis Mountains locoweed$English$N$124502$2011-06-29 00:00:00$', + * '$woolly milkvetch$English$N$72035$2012-12-21 00:00:00$' + * ] + */ return this.taxonRepository.addItisTaxonRecord( Number(itisSolrResponse.tsn), From 49b3f36b3cfd6fa845c016bc8bc785d440777779 Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Thu, 7 Mar 2024 10:55:50 -0800 Subject: [PATCH 18/27] continue addressing PR comments --- api/src/services/taxonomy-service.ts | 5 ++++- api/src/utils/itis-sort.ts | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/api/src/services/taxonomy-service.ts b/api/src/services/taxonomy-service.ts index 84b64fe2..b9686cd8 100644 --- a/api/src/services/taxonomy-service.ts +++ b/api/src/services/taxonomy-service.ts @@ -72,7 +72,10 @@ export class TaxonomyService { * @memberof TaxonomyService */ async addItisTaxonRecord(itisSolrResponse: ItisSolrSearchResponse): Promise { - const commonNames = itisSolrResponse.commonNames.filter((name) => name.split('$')[2] === 'English') ?? []; + const commonNames = + itisSolrResponse.commonNames + .filter((name) => name.split('$')[2] === 'English') + .map((name) => name.split('$')[1]) ?? []; /* Sample itisResponse: * commonNames: [ * '$withered wooly milk-vetch$English$N$152846$2012-12-21 00:00:00$', diff --git a/api/src/utils/itis-sort.ts b/api/src/utils/itis-sort.ts index 81711750..e4e00ee8 100644 --- a/api/src/utils/itis-sort.ts +++ b/api/src/utils/itis-sort.ts @@ -52,8 +52,8 @@ export const customSortContainsAnyMatchingSearchTerm = ( }; const checkForMatch = (item: TaxonSearchResult, searchTerms: string[]) => { - const commonNameWords = item.commonNames?.flatMap((name: string) => name.toLowerCase().split(/\s+/)); - const scientificNameWords = item.scientificName.toLowerCase().split(/\s+/); + const commonNameWords = item.commonNames?.flatMap((name: string) => name.toLowerCase().split(' ')) + const scientificNameWords = item.scientificName.toLowerCase().split(' '); // Check if any word in commonNames or scientificName matches any word in searchTerms return searchTerms.some( From d68c956b7050a2d60ae0ec0a0840194a733c4a36 Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Thu, 7 Mar 2024 11:14:56 -0800 Subject: [PATCH 19/27] simplify itis sorting funcitons --- api/src/utils/itis-sort.ts | 107 +++++++++++++------------------------ 1 file changed, 38 insertions(+), 69 deletions(-) diff --git a/api/src/utils/itis-sort.ts b/api/src/utils/itis-sort.ts index e4e00ee8..0a561f85 100644 --- a/api/src/utils/itis-sort.ts +++ b/api/src/utils/itis-sort.ts @@ -9,10 +9,15 @@ import { TaxonSearchResult } from '../services/taxonomy-service'; */ export const sortExactMatches = (data: TaxonSearchResult[], searchTerms: string[]): TaxonSearchResult[] => { const searchTermsLower = searchTerms.map((item) => item.toLowerCase()); + const taxonNames = data.map((item) => { + item.scientificName = item.scientificName.toLowerCase(); + item.commonNames = item.commonNames.map((name) => name.toLowerCase()); + return item; + }); // Prioritize taxa where any word in the scientific or common name matches ANY of the search terms // eg. ['Black', 'bear'] -> "Black" matches on "Black widow" - const containsAnyMatch = customSortContainsAnyMatchingSearchTerm(data, searchTermsLower); + const containsAnyMatch = customSortContainsAnyMatchingSearchTerm(taxonNames, searchTermsLower); // Prioritize taxa where either the scientific name or any common name CONTAINS the search terms joined // eg. ['Black', 'bear'] -> "Black bear" matches on "American black bear" @@ -36,33 +41,23 @@ export const sortExactMatches = (data: TaxonSearchResult[], searchTerms: string[ export const customSortContainsAnyMatchingSearchTerm = ( data: TaxonSearchResult[], searchTerms: string[] -): TaxonSearchResult[] => { - // Custom sorting function - const customSort = (a: TaxonSearchResult, b: TaxonSearchResult) => { - const aInReference = checkForMatch(a, searchTerms); - const bInReference = checkForMatch(b, searchTerms); +): TaxonSearchResult[] => + data.sort((a, b) => { + const checkForMatch = (item: TaxonSearchResult) => { + const commonNameWords = item.commonNames?.flatMap((name) => name.toLowerCase().split(' ')); + const scientificNameWords = item.scientificName.toLowerCase().split(' '); - if (aInReference && !bInReference) { - return -1; // Place items from searchTerms before other items - } else if (!aInReference && bInReference) { - return 1; // Place other items after items from searchTerms - } else { - return 0; // Maintain the original order if both are from searchTerms or both are not - } - }; - - const checkForMatch = (item: TaxonSearchResult, searchTerms: string[]) => { - const commonNameWords = item.commonNames?.flatMap((name: string) => name.toLowerCase().split(' ')) - const scientificNameWords = item.scientificName.toLowerCase().split(' '); + // Check if any word in commonNames or scientificName matches any word in searchTerms + return searchTerms.some( + (searchTerm) => scientificNameWords.includes(searchTerm) || commonNameWords?.includes(searchTerm) + ); + }; - // Check if any word in commonNames or scientificName matches any word in searchTerms - return searchTerms.some( - (searchTerm) => scientificNameWords?.includes(searchTerm) || commonNameWords?.includes(searchTerm) - ); - }; + const aInReference = checkForMatch(a); + const bInReference = checkForMatch(b); - return data.sort(customSort); -}; + return aInReference && !bInReference ? -1 : !aInReference && bInReference ? 1 : 0; + }); /** * Sorts the ITIS response to prioritize records where either the scientific name or @@ -75,34 +70,18 @@ export const customSortContainsAnyMatchingSearchTerm = ( export const customSortContainsSearchTermsJoinedExact = ( data: TaxonSearchResult[], searchTerms: string[] -): TaxonSearchResult[] => { - const customSort = (a: TaxonSearchResult, b: TaxonSearchResult) => { - const aInReference = checkForMatch(a, searchTerms); - const bInReference = checkForMatch(b, searchTerms); +): TaxonSearchResult[] => + data.sort((a, b) => { + const checkForMatch = (item: TaxonSearchResult) => { + const searchTermString = searchTerms.join(' '); + return item.commonNames.some((name) => name.includes(searchTermString)) || item.scientificName === searchTermString; + }; - if (aInReference && !bInReference) { - return -1; // Place items from searchTerms before other items - } else if (!aInReference && bInReference) { - return 0; // Maintain the original order - } else { - return 0; // Maintain the original order - } - }; - - // Function to check if an item is a match with search terms - const checkForMatch = (item: TaxonSearchResult, searchTerms: string[]) => { - const commonNameWords = item.commonNames?.map((name: string) => name.toLowerCase()); - const scientificNameWord = item.scientificName.toLowerCase(); + const aInReference = checkForMatch(a); + const bInReference = checkForMatch(b); - // Add a space such that "Black bear" matches "American black bear" and not "Black Bearded" - return ( - scientificNameWord === searchTerms.join(' ') || - commonNameWords?.some((name) => `${name}${' '}`.includes(`${searchTerms.join(' ')}${' '}`)) - ); - }; - - return data.sort(customSort); -}; + return aInReference && !bInReference ? -1 : 0; + }); /** * Sorts the ITIS response to prioritize taxa where either the scientific name or @@ -115,27 +94,17 @@ export const customSortContainsSearchTermsJoinedExact = ( export const customSortEqualsSearchTermsExact = ( data: TaxonSearchResult[], searchTerms: string[] -): TaxonSearchResult[] => { - const customSort = (a: TaxonSearchResult, b: TaxonSearchResult) => { - const aInReference = checkForMatch(a, searchTerms); - const bInReference = checkForMatch(b, searchTerms); +): TaxonSearchResult[] => + data.sort((a, b) => { + const checkForMatch = (item: TaxonSearchResult) => + item.scientificName === searchTerms.join(' ') || item.commonNames.includes(searchTerms.join(' ')); + + const aInReference = checkForMatch(a); + const bInReference = checkForMatch(b); if (aInReference && !bInReference) { return -1; // Place items from searchTerms before other items - } else if (!aInReference && bInReference) { - return 0; // Maintain the original order } else { return 0; // Maintain the original order } - }; - - // Function to check if an item is a match with search terms - const checkForMatch = (item: TaxonSearchResult, searchTerms: string[]) => { - const commonNameWords = item.commonNames?.map((name: string) => name.toLowerCase()); - const scientificNameWord = item.scientificName.toLowerCase(); - - return scientificNameWord === searchTerms.join(' ') || commonNameWords?.includes(searchTerms.join(' ')); - }; - - return data.sort(customSort); -}; + }); From da9fa992454107a799b4be1a2faaee5b05d70438 Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Thu, 7 Mar 2024 12:21:45 -0800 Subject: [PATCH 20/27] fix tests by changing scientific and common names to lowercase --- .../repositories/taxonomy-repository.test.ts | 2 +- api/src/services/itis-service.test.ts | 8 ++--- api/src/utils/itis-sort.test.ts | 3 -- api/src/utils/itis-sort.ts | 29 ++++++++----------- 4 files changed, 17 insertions(+), 25 deletions(-) delete mode 100644 api/src/utils/itis-sort.test.ts diff --git a/api/src/repositories/taxonomy-repository.test.ts b/api/src/repositories/taxonomy-repository.test.ts index 348654b9..ad601af9 100644 --- a/api/src/repositories/taxonomy-repository.test.ts +++ b/api/src/repositories/taxonomy-repository.test.ts @@ -73,7 +73,7 @@ describe('TaxonomyRepository', () => { const taxonomyRepository = new TaxonomyRepository(mockDBConnection); - const response = await taxonomyRepository.addItisTaxonRecord(1, 'string', 'string', {}, 'string'); + const response = await taxonomyRepository.addItisTaxonRecord(1, 'string', ['string'], {}, 'string'); expect(response).to.be.eql({ taxon_id: 1, diff --git a/api/src/services/itis-service.test.ts b/api/src/services/itis-service.test.ts index be8b8dd7..a82cfc14 100644 --- a/api/src/services/itis-service.test.ts +++ b/api/src/services/itis-service.test.ts @@ -68,8 +68,8 @@ describe('ItisService', () => { expect(response).to.eql([ { tsn: 123, - commonNames: ['commonNames'], - scientificName: 'scientificName', + commonNames: ['commonnames'], + scientificName: 'scientificname', rank: 'kingdom', kingdom: 'kingdom' } @@ -128,7 +128,7 @@ describe('ItisService', () => { kingdom: 'kingdom', name: 'name', parentTSN: 'parentTSN', - scientificName: 'scientificName', + scientificName: 'scientificname', tsn: '123', updateDate: 'updateDate', usage: '', @@ -153,7 +153,7 @@ describe('ItisService', () => { kingdom: 'kingdom', name: 'name', parentTSN: 'parentTSN', - scientificName: 'scientificName', + scientificName: 'scientificname', tsn: '123', updateDate: 'updateDate', usage: '', diff --git a/api/src/utils/itis-sort.test.ts b/api/src/utils/itis-sort.test.ts deleted file mode 100644 index da26fb34..00000000 --- a/api/src/utils/itis-sort.test.ts +++ /dev/null @@ -1,3 +0,0 @@ -// TODO -export const placeholder = () => {} -// describe('itis-sort', () => {}); diff --git a/api/src/utils/itis-sort.ts b/api/src/utils/itis-sort.ts index 0a561f85..16669126 100644 --- a/api/src/utils/itis-sort.ts +++ b/api/src/utils/itis-sort.ts @@ -10,8 +10,8 @@ import { TaxonSearchResult } from '../services/taxonomy-service'; export const sortExactMatches = (data: TaxonSearchResult[], searchTerms: string[]): TaxonSearchResult[] => { const searchTermsLower = searchTerms.map((item) => item.toLowerCase()); const taxonNames = data.map((item) => { - item.scientificName = item.scientificName.toLowerCase(); - item.commonNames = item.commonNames.map((name) => name.toLowerCase()); + item.scientificName = item.scientificName.toLowerCase().trim(); + item.commonNames = item.commonNames.map((name) => name.toLowerCase().trim()); return item; }); @@ -43,15 +43,12 @@ export const customSortContainsAnyMatchingSearchTerm = ( searchTerms: string[] ): TaxonSearchResult[] => data.sort((a, b) => { - const checkForMatch = (item: TaxonSearchResult) => { - const commonNameWords = item.commonNames?.flatMap((name) => name.toLowerCase().split(' ')); - const scientificNameWords = item.scientificName.toLowerCase().split(' '); - - // Check if any word in commonNames or scientificName matches any word in searchTerms - return searchTerms.some( - (searchTerm) => scientificNameWords.includes(searchTerm) || commonNameWords?.includes(searchTerm) + const checkForMatch = (item: TaxonSearchResult) => + searchTerms.some( + (searchTerm) => + item.scientificName.split(' ').includes(searchTerm) || + item.commonNames?.flatMap((name) => name.split(' ')).includes(searchTerm) ); - }; const aInReference = checkForMatch(a); const bInReference = checkForMatch(b); @@ -73,8 +70,10 @@ export const customSortContainsSearchTermsJoinedExact = ( ): TaxonSearchResult[] => data.sort((a, b) => { const checkForMatch = (item: TaxonSearchResult) => { - const searchTermString = searchTerms.join(' '); - return item.commonNames.some((name) => name.includes(searchTermString)) || item.scientificName === searchTermString; + return ( + item.commonNames.some((name) => name.includes(searchTerms.join(' '))) || + item.scientificName === searchTerms.join(' ') + ); }; const aInReference = checkForMatch(a); @@ -102,9 +101,5 @@ export const customSortEqualsSearchTermsExact = ( const aInReference = checkForMatch(a); const bInReference = checkForMatch(b); - if (aInReference && !bInReference) { - return -1; // Place items from searchTerms before other items - } else { - return 0; // Maintain the original order - } + return aInReference && !bInReference ? -1 : 0 }); From 6090068dca769c1835dc8fbe9e6bb1f374be231d Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Thu, 7 Mar 2024 12:38:43 -0800 Subject: [PATCH 21/27] linter --- api/src/utils/itis-sort.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/utils/itis-sort.ts b/api/src/utils/itis-sort.ts index 16669126..bd3c59ed 100644 --- a/api/src/utils/itis-sort.ts +++ b/api/src/utils/itis-sort.ts @@ -101,5 +101,5 @@ export const customSortEqualsSearchTermsExact = ( const aInReference = checkForMatch(a); const bInReference = checkForMatch(b); - return aInReference && !bInReference ? -1 : 0 + return aInReference && !bInReference ? -1 : 0; }); From 8fc01b0cf1e8f937a175ae0610d9ddc3754372f6 Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Thu, 7 Mar 2024 12:52:02 -0800 Subject: [PATCH 22/27] JS Doc fix --- api/src/repositories/taxonomy-repository.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/repositories/taxonomy-repository.ts b/api/src/repositories/taxonomy-repository.ts index 42acc1fb..10773a43 100644 --- a/api/src/repositories/taxonomy-repository.ts +++ b/api/src/repositories/taxonomy-repository.ts @@ -53,7 +53,7 @@ export class TaxonomyRepository extends BaseRepository { * * @param {number} itisTsn * @param {string} itisScientificName - * @param {(string | null)} commonNames + * @param {string[]} commonNames * @param {Record} itisData * @param {string} itisUpdateDate * @return {*} {Promise} From 1d6972c4413773551612ee1c9fd2ebf862ca8f31 Mon Sep 17 00:00:00 2001 From: Nick Phura Date: Wed, 1 May 2024 17:46:39 -0700 Subject: [PATCH 23/27] Merge dev --- app/package-lock.json | 218 ++++++++++++++++++------------------- database/package-lock.json | 60 +++++----- 2 files changed, 136 insertions(+), 142 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index ca5135e9..a4fa1974 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -1838,12 +1838,33 @@ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, "string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -1855,6 +1876,43 @@ "strip-ansi": "^7.0.1" } }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, "wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -1865,6 +1923,54 @@ "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } + }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } } } }, @@ -18107,40 +18213,6 @@ } } }, - "string-width-cjs": { - "version": "npm:string-width@4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, "string.prototype.matchall": { "version": "4.0.11", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", @@ -18236,15 +18308,6 @@ } } }, - "strip-ansi-cjs": { - "version": "npm:strip-ansi@6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, "strip-bom": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", @@ -19871,75 +19934,6 @@ } } }, - "wrap-ansi-cjs": { - "version": "npm:wrap-ansi@7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/database/package-lock.json b/database/package-lock.json index 958af2e6..bc8a1ef2 100644 --- a/database/package-lock.json +++ b/database/package-lock.json @@ -526,7 +526,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, "colorette": { @@ -542,7 +542,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, "core-util-is": { @@ -1032,7 +1032,7 @@ "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, "fastq": { @@ -1101,7 +1101,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, "function-bind": { @@ -1124,7 +1124,7 @@ "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, "functions-have-names": { @@ -1273,7 +1273,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "has-property-descriptors": { @@ -1347,13 +1347,13 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { "once": "^1.3.0", @@ -1395,7 +1395,7 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, "is-bigint": { @@ -1452,7 +1452,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, "is-fullwidth-code-point": { @@ -1555,7 +1555,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, "js-tokens": { @@ -1595,7 +1595,7 @@ "json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, "jsonparse": { @@ -1647,7 +1647,7 @@ "load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -1670,7 +1670,7 @@ "lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", "dev": true }, "lru-cache": { @@ -1691,7 +1691,7 @@ "memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", "dev": true }, "merge2": { @@ -1727,7 +1727,7 @@ "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, "nice-try": { @@ -1798,7 +1798,7 @@ "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", "dev": true }, "semver": { @@ -1810,7 +1810,7 @@ "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { "shebang-regex": "^1.0.0" @@ -1819,7 +1819,7 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", "dev": true }, "which": { @@ -1860,7 +1860,7 @@ "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { "wrappy": "1" @@ -1897,7 +1897,7 @@ "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "dev": true, "requires": { "error-ex": "^1.3.1", @@ -1907,7 +1907,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, "path-key": { @@ -2003,7 +2003,7 @@ "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true }, "possible-typed-array-names": { @@ -2020,7 +2020,7 @@ "postgres-bytea": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==" + "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" }, "postgres-date": { "version": "1.0.7", @@ -2089,7 +2089,7 @@ "read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", "dev": true, "requires": { "load-json-file": "^4.0.0", @@ -2234,7 +2234,7 @@ "semver": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", - "integrity": "sha512-VyFUffiBx8hABJ9HYSTXLRwyZtdDHMzMtFmID1aiNAD2BZppBmJm0Hqw3p2jkgxP9BNt1pQ9RnC49P0EcXf6cA==" + "integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=" }, "set-function-length": { "version": "1.2.2", @@ -2378,7 +2378,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", "dev": true }, "string-width": { @@ -2459,7 +2459,7 @@ "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "dev": true }, "strip-json-comments": { @@ -2523,7 +2523,7 @@ "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, "through": { @@ -2748,7 +2748,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, "xtend": { From b85c5a282fa8af036a6d05bc2d54b2146b7113fb Mon Sep 17 00:00:00 2001 From: Nick Phura Date: Fri, 10 May 2024 11:01:36 -0700 Subject: [PATCH 24/27] Optimize sort query, had some minimal unit tests --- api/src/services/itis-service.ts | 4 +- api/src/utils/itis-sort.test.ts | 80 +++++++++++++++++ api/src/utils/itis-sort.ts | 148 +++++++++++++------------------ 3 files changed, 145 insertions(+), 87 deletions(-) create mode 100644 api/src/utils/itis-sort.test.ts diff --git a/api/src/services/itis-service.ts b/api/src/services/itis-service.ts index 11614ce3..9407d867 100644 --- a/api/src/services/itis-service.ts +++ b/api/src/services/itis-service.ts @@ -1,5 +1,5 @@ import axios from 'axios'; -import { sortExactMatches } from '../utils/itis-sort'; +import { sortTaxonSearchResults } from '../utils/itis-sort'; import { getLogger } from '../utils/logger'; import { TaxonSearchResult } from './taxonomy-service'; @@ -47,7 +47,7 @@ export class ItisService { const sanitizedResponse = this._sanitizeItisData(response.data.response.docs); // Sort the results to place exact matches at the top - const sortedResponse = sortExactMatches(sanitizedResponse, searchTerms); + const sortedResponse = sortTaxonSearchResults(sanitizedResponse, searchTerms); // Return only a subset of the records // More records than are returned here are requested from ITIS to help find and prioritize exact matches diff --git a/api/src/utils/itis-sort.test.ts b/api/src/utils/itis-sort.test.ts new file mode 100644 index 00000000..74e9e070 --- /dev/null +++ b/api/src/utils/itis-sort.test.ts @@ -0,0 +1,80 @@ +import { expect } from 'chai'; +import { describe } from 'mocha'; +import { TaxonSearchResult } from '../services/taxonomy-service'; +import { sortTaxonSearchResults } from './itis-sort'; + +describe.only('itis-sort', () => { + describe('sortTaxonSearchResults', () => { + it('Sorts the list when there is only 1 item', () => { + const data: TaxonSearchResult[] = [ + { + tsn: 1, + commonNames: ['Moose', 'moose'], + scientificName: 'Alces alces' + } + ]; + const searchTerms = ['Moose']; + + const result = sortTaxonSearchResults(data, searchTerms); + + expect(result.length).to.equal(data.length); + expect(result[0].tsn).to.equal(1); + }); + + it('Sorts the list when there are exact matches', () => { + const data: TaxonSearchResult[] = [ + { + tsn: 1, + commonNames: ['Goose', 'goose'], + scientificName: 'Goose goose' + }, + { + tsn: 2, + commonNames: ['Moose', 'moose'], + scientificName: 'Moose moose' + }, + { + tsn: 3, + commonNames: ['House'], + scientificName: 'House' + } + ]; + const searchTerms = ['Moose']; + + const result = sortTaxonSearchResults(data, searchTerms); + + expect(result.length).to.equal(data.length); + expect(result[0].tsn).to.equal(2); + expect(result[1].tsn).to.equal(1); + expect(result[2].tsn).to.equal(3); + }); + + it('Sorts the list when there are no exact matches', () => { + const data: TaxonSearchResult[] = [ + { + tsn: 1, + commonNames: ['Goose', 'goose'], + scientificName: 'Goose goose' + }, + { + tsn: 2, + commonNames: ['Moose', 'moose'], + scientificName: 'Moose moose' + }, + { + tsn: 3, + commonNames: ['House'], + scientificName: 'House' + } + ]; + const searchTerms = ['oose']; + + const result = sortTaxonSearchResults(data, searchTerms); + + expect(result.length).to.equal(data.length); + expect(result[0].tsn).to.equal(1); + expect(result[1].tsn).to.equal(2); + expect(result[2].tsn).to.equal(3); + }); + }); +}); diff --git a/api/src/utils/itis-sort.ts b/api/src/utils/itis-sort.ts index bd3c59ed..5e0d6038 100644 --- a/api/src/utils/itis-sort.ts +++ b/api/src/utils/itis-sort.ts @@ -3,103 +3,81 @@ import { TaxonSearchResult } from '../services/taxonomy-service'; /** * Sorts the ITIS response by how strongly records match the search terms * - * @param {ItisSolrSearchResponse[]} data + * @param {TaxonSearchResult[]} data * @param {string[]} searchTerms - * @memberof ItisService + * @return {*} {TaxonSearchResult[]} */ -export const sortExactMatches = (data: TaxonSearchResult[], searchTerms: string[]): TaxonSearchResult[] => { +export const sortTaxonSearchResults = (data: TaxonSearchResult[], searchTerms: string[]): TaxonSearchResult[] => { const searchTermsLower = searchTerms.map((item) => item.toLowerCase()); - const taxonNames = data.map((item) => { - item.scientificName = item.scientificName.toLowerCase().trim(); - item.commonNames = item.commonNames.map((name) => name.toLowerCase().trim()); - return item; - }); + const searchTermJoined = searchTermsLower.join(' '); - // Prioritize taxa where any word in the scientific or common name matches ANY of the search terms - // eg. ['Black', 'bear'] -> "Black" matches on "Black widow" - const containsAnyMatch = customSortContainsAnyMatchingSearchTerm(taxonNames, searchTermsLower); + // Caches the scientific name data + const scientificNameDataMap = new Map(); + // Caches the common name data + const commonNamesDataMap = new Map(); - // Prioritize taxa where either the scientific name or any common name CONTAINS the search terms joined - // eg. ['Black', 'bear'] -> "Black bear" matches on "American black bear" - const containsAnyMatchJoined = customSortContainsSearchTermsJoinedExact(containsAnyMatch, searchTermsLower); + // Returns the scientific name data, adding it to the cache if it doesn't exist + const getScientificNameData = (scientificName: string) => { + if (!scientificNameDataMap.has(scientificName)) { + const lowercased = scientificName.toLowerCase(); + scientificNameDataMap.set(scientificName, { words: lowercased.trim().split(' '), lowercased }); + } - // Prioritize taxa where either the scientific name or any common name is EXACTLY EQUAL to the search terms joined - // eg. ['Wolf'] -> "Wolf" is prioritized over "Forest Wolf" - const exactlyEquals = customSortEqualsSearchTermsExact(containsAnyMatchJoined, searchTermsLower); + return scientificNameDataMap.get(scientificName) as { words: string[]; lowercased: string }; + }; - return exactlyEquals; -}; + // Returns the common names data, adding it to the cache if it doesn't exist + const getCommonNamesData = (commonNames: string[]) => { + return commonNames.map((name) => { + if (!commonNamesDataMap.has(name)) { + const lowercased = name.toLowerCase(); + commonNamesDataMap.set(name, { words: lowercased.trim().split(' '), lowercased }); + } -/** - * Sorts the ITIS response to prioritize records where any word in the scientific or - * common name matches ANY of the search terms - * - * @param {ItisSolrSearchResponse[]} data - * @param {string[]} searchTerms - * @memberof ItisService - */ -export const customSortContainsAnyMatchingSearchTerm = ( - data: TaxonSearchResult[], - searchTerms: string[] -): TaxonSearchResult[] => - data.sort((a, b) => { - const checkForMatch = (item: TaxonSearchResult) => - searchTerms.some( - (searchTerm) => - item.scientificName.split(' ').includes(searchTerm) || - item.commonNames?.flatMap((name) => name.split(' ')).includes(searchTerm) - ); + return commonNamesDataMap.get(name) as { words: string[]; lowercased: string }; + }); + }; - const aInReference = checkForMatch(a); - const bInReference = checkForMatch(b); + /** + * Custom scoring function to determine how well a record matches the search terms + * + * @param {TaxonSearchResult} item + * @return {*} + */ + const calculateScore = (item: TaxonSearchResult) => { + let score = 0; - return aInReference && !bInReference ? -1 : !aInReference && bInReference ? 1 : 0; - }); + const scientificNameData = getScientificNameData(item.scientificName); + const commonNamesData = getCommonNamesData(item.commonNames); -/** - * Sorts the ITIS response to prioritize records where either the scientific name or - * any common name CONTAINS the search terms joined - * - * @param {ItisSolrSearchResponse[]} data - * @param {string[]} searchTerms - * @memberof ItisService - */ -export const customSortContainsSearchTermsJoinedExact = ( - data: TaxonSearchResult[], - searchTerms: string[] -): TaxonSearchResult[] => - data.sort((a, b) => { - const checkForMatch = (item: TaxonSearchResult) => { - return ( - item.commonNames.some((name) => name.includes(searchTerms.join(' '))) || - item.scientificName === searchTerms.join(' ') - ); - }; + // Check if any word in the scientific or common name matches ANY of the search terms + if ( + searchTermsLower.some( + (term) => scientificNameData.words.includes(term) || commonNamesData.some((data) => data.words.includes(term)) + ) + ) { + score += 1; + } - const aInReference = checkForMatch(a); - const bInReference = checkForMatch(b); + // Check if either the scientific name or any common name CONTAINS the search terms joined + if ( + scientificNameData.lowercased.includes(searchTermJoined) || + commonNamesData.some((data) => data.lowercased.includes(searchTermJoined)) + ) { + score += 2; + } - return aInReference && !bInReference ? -1 : 0; - }); + // Check if either the scientific name or any common name is EXACTLY EQUAL to the search terms joined + if ( + scientificNameData.lowercased === searchTermJoined || + commonNamesData.some((data) => data.lowercased === searchTermJoined) + ) { + score += 3; + } -/** - * Sorts the ITIS response to prioritize taxa where either the scientific name or - * any common name is EXACTLY EQUAL to the search terms joined - * - * @param {ItisSolrSearchResponse[]} data - * @param {string[]} searchTerms - * @memberof ItisService - */ -export const customSortEqualsSearchTermsExact = ( - data: TaxonSearchResult[], - searchTerms: string[] -): TaxonSearchResult[] => - data.sort((a, b) => { - const checkForMatch = (item: TaxonSearchResult) => - item.scientificName === searchTerms.join(' ') || item.commonNames.includes(searchTerms.join(' ')); - - const aInReference = checkForMatch(a); - const bInReference = checkForMatch(b); + return score; + }; - return aInReference && !bInReference ? -1 : 0; - }); + // Sort the data by the score + return data.sort((a, b) => calculateScore(b) - calculateScore(a)); +}; From 2717a93a0f0c77a9aa976b7710c9e47a36d8e7cd Mon Sep 17 00:00:00 2001 From: Nick Phura Date: Fri, 10 May 2024 11:23:08 -0700 Subject: [PATCH 25/27] Remove .only --- api/src/utils/itis-sort.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/utils/itis-sort.test.ts b/api/src/utils/itis-sort.test.ts index 74e9e070..94eb506b 100644 --- a/api/src/utils/itis-sort.test.ts +++ b/api/src/utils/itis-sort.test.ts @@ -3,7 +3,7 @@ import { describe } from 'mocha'; import { TaxonSearchResult } from '../services/taxonomy-service'; import { sortTaxonSearchResults } from './itis-sort'; -describe.only('itis-sort', () => { +describe('itis-sort', () => { describe('sortTaxonSearchResults', () => { it('Sorts the list when there is only 1 item', () => { const data: TaxonSearchResult[] = [ From 16a72a7e8539c8ed0beb69cbd6184222346aed77 Mon Sep 17 00:00:00 2001 From: Nick Phura Date: Fri, 10 May 2024 13:45:41 -0700 Subject: [PATCH 26/27] Better doc --- api/src/utils/itis-sort.ts | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/api/src/utils/itis-sort.ts b/api/src/utils/itis-sort.ts index 5e0d6038..a4c4ec70 100644 --- a/api/src/utils/itis-sort.ts +++ b/api/src/utils/itis-sort.ts @@ -3,11 +3,14 @@ import { TaxonSearchResult } from '../services/taxonomy-service'; /** * Sorts the ITIS response by how strongly records match the search terms * - * @param {TaxonSearchResult[]} data + * @param {TaxonSearchResult[]} taxonSearchResults * @param {string[]} searchTerms * @return {*} {TaxonSearchResult[]} */ -export const sortTaxonSearchResults = (data: TaxonSearchResult[], searchTerms: string[]): TaxonSearchResult[] => { +export const sortTaxonSearchResults = ( + taxonSearchResults: TaxonSearchResult[], + searchTerms: string[] +): TaxonSearchResult[] => { const searchTermsLower = searchTerms.map((item) => item.toLowerCase()); const searchTermJoined = searchTermsLower.join(' '); @@ -28,29 +31,30 @@ export const sortTaxonSearchResults = (data: TaxonSearchResult[], searchTerms: s // Returns the common names data, adding it to the cache if it doesn't exist const getCommonNamesData = (commonNames: string[]) => { - return commonNames.map((name) => { - if (!commonNamesDataMap.has(name)) { - const lowercased = name.toLowerCase(); - commonNamesDataMap.set(name, { words: lowercased.trim().split(' '), lowercased }); + return commonNames.map((commonName) => { + if (!commonNamesDataMap.has(commonName)) { + const lowercased = commonName.toLowerCase(); + commonNamesDataMap.set(commonName, { words: lowercased.trim().split(' '), lowercased }); } - return commonNamesDataMap.get(name) as { words: string[]; lowercased: string }; + return commonNamesDataMap.get(commonName) as { words: string[]; lowercased: string }; }); }; /** - * Custom scoring function to determine how well a record matches the search terms + * Custom scoring function to determine how well a record matches the search terms. * - * @param {TaxonSearchResult} item + * @param {TaxonSearchResult} taxonSearchResult * @return {*} */ - const calculateScore = (item: TaxonSearchResult) => { + const calculateScore = (taxonSearchResult: TaxonSearchResult) => { let score = 0; - const scientificNameData = getScientificNameData(item.scientificName); - const commonNamesData = getCommonNamesData(item.commonNames); + const scientificNameData = getScientificNameData(taxonSearchResult.scientificName); + const commonNamesData = getCommonNamesData(taxonSearchResult.commonNames); // Check if any word in the scientific or common name matches ANY of the search terms + // eg. ['Black', 'bear'] -> "Black" matches on "Black widow" if ( searchTermsLower.some( (term) => scientificNameData.words.includes(term) || commonNamesData.some((data) => data.words.includes(term)) @@ -60,6 +64,7 @@ export const sortTaxonSearchResults = (data: TaxonSearchResult[], searchTerms: s } // Check if either the scientific name or any common name CONTAINS the search terms joined + // eg. ['Black', 'bear'] -> "Black bear" matches on "American black bear" if ( scientificNameData.lowercased.includes(searchTermJoined) || commonNamesData.some((data) => data.lowercased.includes(searchTermJoined)) @@ -68,6 +73,7 @@ export const sortTaxonSearchResults = (data: TaxonSearchResult[], searchTerms: s } // Check if either the scientific name or any common name is EXACTLY EQUAL to the search terms joined + // eg. ['Wolf'] -> "Wolf" is prioritized over "Forest Wolf" if ( scientificNameData.lowercased === searchTermJoined || commonNamesData.some((data) => data.lowercased === searchTermJoined) @@ -79,5 +85,5 @@ export const sortTaxonSearchResults = (data: TaxonSearchResult[], searchTerms: s }; // Sort the data by the score - return data.sort((a, b) => calculateScore(b) - calculateScore(a)); + return taxonSearchResults.sort((a, b) => calculateScore(b) - calculateScore(a)); }; From 1dddfa502245c91084369a1cac7527c776357948 Mon Sep 17 00:00:00 2001 From: Nick Phura Date: Fri, 10 May 2024 14:34:56 -0700 Subject: [PATCH 27/27] Fix tests --- api/src/services/itis-service.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/src/services/itis-service.test.ts b/api/src/services/itis-service.test.ts index a82cfc14..be8b8dd7 100644 --- a/api/src/services/itis-service.test.ts +++ b/api/src/services/itis-service.test.ts @@ -68,8 +68,8 @@ describe('ItisService', () => { expect(response).to.eql([ { tsn: 123, - commonNames: ['commonnames'], - scientificName: 'scientificname', + commonNames: ['commonNames'], + scientificName: 'scientificName', rank: 'kingdom', kingdom: 'kingdom' } @@ -128,7 +128,7 @@ describe('ItisService', () => { kingdom: 'kingdom', name: 'name', parentTSN: 'parentTSN', - scientificName: 'scientificname', + scientificName: 'scientificName', tsn: '123', updateDate: 'updateDate', usage: '', @@ -153,7 +153,7 @@ describe('ItisService', () => { kingdom: 'kingdom', name: 'name', parentTSN: 'parentTSN', - scientificName: 'scientificname', + scientificName: 'scientificName', tsn: '123', updateDate: 'updateDate', usage: '',