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);