Add SearXNG and Brave recipe search providers with ranking
recipes.SearchProvider.Search(ctx, query, n) returns []Candidate{URL,
Title, Snippet}, appending " recipe" when the query lacks the word.
SearXNG calls {MEALQUEST_SEARXNG_URL}/search?q=…&format=json&categories=general
directly (the configured endpoint may be internal); Brave calls the Web
Search API with the key decrypted from recipe_search_settings. Redirects
are not followed; 401/403, 429, other non-2xx and malformed bodies map to
distinct errors.
recipes.Rank fetches and extracts the top 5 candidates, rejects those
without ingredients or an image, and scores the rest by
harness.Similarity of the recipe title to the dish name plus
completeness (ingredients, image, steps, minutes). harness.Similarity
exposes the existing Jaccard score used by Similar.
Maestro task 10c25cb5-3774-419c-87e1-3f87a3d1391d.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -53,7 +53,14 @@ func Normalize(name string) string {
|
||||
// similarity of their normalized forms is at least SimilarThreshold. A name
|
||||
// with no words left after normalizing is similar to nothing.
|
||||
func Similar(a, b string) bool {
|
||||
return similarity(tokenSet(a), tokenSet(b)) >= SimilarThreshold
|
||||
return Similarity(a, b) >= SimilarThreshold
|
||||
}
|
||||
|
||||
// Similarity is the token-set (Jaccard) similarity of a's and b's normalized
|
||||
// forms, from 0 (no shared word) to 1 (the same words). A name with no words
|
||||
// left after normalizing scores 0 against everything.
|
||||
func Similarity(a, b string) float64 {
|
||||
return similarity(tokenSet(a), tokenSet(b))
|
||||
}
|
||||
|
||||
// tokenSet returns the distinct words of name's normalized form.
|
||||
|
||||
@@ -114,6 +114,22 @@ func TestSimilar(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimilarity(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
a, b string
|
||||
want float64
|
||||
}{
|
||||
{"Easy Chicken Curry", "chicken curry", 1},
|
||||
{"Thai Green Curry", "Chicken curry", 0.25},
|
||||
{"Beef Stew", "Chicken curry", 0},
|
||||
{"The Recipe", "chicken curry", 0},
|
||||
} {
|
||||
if got := Similarity(tc.a, tc.b); got != tc.want {
|
||||
t.Errorf("Similarity(%q, %q) = %v, want %v", tc.a, tc.b, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeduper(t *testing.T) {
|
||||
d := NewDeduper(DedupeSources{
|
||||
Regulars: []string{"Beef Tacos", "Spaghetti Bolognese"},
|
||||
|
||||
Reference in New Issue
Block a user