Skip to content

Commit

Permalink
common name from string to array
Browse files Browse the repository at this point in the history
  • Loading branch information
mauberti-bc committed Mar 5, 2024
1 parent 978933b commit 2571f2d
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 58 deletions.
2 changes: 1 addition & 1 deletion api/src/paths/taxonomy/taxon/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down
6 changes: 4 additions & 2 deletions api/src/paths/taxonomy/taxon/tsn/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ GET.apiDoc = {
type: 'integer'
},
commonNames: {
type: 'string',
nullable: true
type: 'array',
items: {
type: 'string'
}
},
scientificName: {
type: 'string'
Expand Down
2 changes: 1 addition & 1 deletion api/src/repositories/taxonomy-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
3 changes: 2 additions & 1 deletion api/src/services/itis-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
4 changes: 2 additions & 2 deletions api/src/services/taxonomy-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const defaultLog = getLogger('services/taxonomy-service');

export type TaxonSearchResult = {
tsn: number;
commonNames: string[];
commonNames: string[] | [];
scientificName: string;
};

Expand Down Expand Up @@ -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
}));
}
Expand Down
104 changes: 53 additions & 51 deletions api/src/utils/itis.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
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
*/
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;
};
Expand Down Expand Up @@ -41,88 +48,83 @@ 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
*
* @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);

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

Expand Down

0 comments on commit 2571f2d

Please sign in to comment.