feat(amazon-shopping): parse filters and extract search candidates
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
import type { ParsedNaturalLanguageRequest, ProductFilters } from "./types.js";
|
||||
|
||||
function cleanQuery(text: string): string {
|
||||
return text
|
||||
.replace(/\b(?:that|and|with|have)\b/gi, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.replace(/\s+(and|or|a)$/i, "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
function removeMatched(text: string, match: RegExpMatchArray | null): string {
|
||||
if (!match) {
|
||||
return text;
|
||||
}
|
||||
return text.replace(match[0], " ");
|
||||
}
|
||||
|
||||
export function parseNaturalLanguageRequest(input: string): ParsedNaturalLanguageRequest {
|
||||
let remaining = input.trim();
|
||||
const filters: ProductFilters = {
|
||||
includeKeywords: [],
|
||||
excludeKeywords: []
|
||||
};
|
||||
let limit: number | undefined;
|
||||
|
||||
const limitMatch = remaining.match(/\b(?:return|limit|top)\s+(\d{1,3})\b/i);
|
||||
if (limitMatch) {
|
||||
limit = Number(limitMatch[1]);
|
||||
remaining = removeMatched(remaining, limitMatch);
|
||||
}
|
||||
|
||||
const unitPriceMatch = remaining.match(/\b(?:cost\s+)?(?:less than|under|below)\s+\$([0-9]+(?:\.[0-9]{1,2})?)\s*(?:each|per\b|\/\s*(?:count|unit|item))\b/i);
|
||||
if (unitPriceMatch) {
|
||||
filters.maxUnitPrice = Number(unitPriceMatch[1]);
|
||||
remaining = removeMatched(remaining, unitPriceMatch);
|
||||
}
|
||||
|
||||
const maxPriceMatch = remaining.match(/\b(?:cost\s+)?(?:less than|under|below)\s+\$([0-9]+(?:\.[0-9]{1,2})?)\b/i);
|
||||
if (maxPriceMatch) {
|
||||
filters.maxPrice = Number(maxPriceMatch[1]);
|
||||
remaining = removeMatched(remaining, maxPriceMatch);
|
||||
}
|
||||
|
||||
const exclusiveReviews = remaining.match(/\b(?:over|more than|above)\s+([0-9][0-9,]*)\s*(?:reviews?|ratings?)\b/i);
|
||||
const inclusiveReviews = remaining.match(/\b(?:at least|minimum|min\.?)\s+([0-9][0-9,]*)\s*(?:reviews?|ratings?)\b/i);
|
||||
const reviewMatch = exclusiveReviews ?? inclusiveReviews;
|
||||
if (reviewMatch) {
|
||||
filters.minReviews = Number(reviewMatch[1].replace(/,/g, ""));
|
||||
filters.reviewCountComparison = exclusiveReviews ? "gt" : "gte";
|
||||
remaining = removeMatched(remaining, reviewMatch);
|
||||
}
|
||||
|
||||
const exclusiveRating = remaining.match(/\b(?:a\s+)?(?:review score of\s+)?(?:more than|over|above|rated above)\s+([0-5](?:\.[0-9])?)\s*(?:stars?)?\b/i);
|
||||
const inclusiveRating = remaining.match(/\b([0-5](?:\.[0-9])?)\s*stars?\s+or\s+better\b/i)
|
||||
?? remaining.match(/\b(?:at least|minimum|min\.?)\s+([0-5](?:\.[0-9])?)\s*(?:stars?|rating)?\b/i);
|
||||
const ratingMatch = exclusiveRating ?? inclusiveRating;
|
||||
if (ratingMatch) {
|
||||
filters.minRating = Number(ratingMatch[1]);
|
||||
filters.ratingComparison = exclusiveRating ? "gt" : "gte";
|
||||
remaining = removeMatched(remaining, ratingMatch);
|
||||
}
|
||||
|
||||
return {
|
||||
query: cleanQuery(remaining),
|
||||
filters,
|
||||
limit
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user