Cook history and recipe recommendations#89
Merged
Merged
Conversation
Adds cook event tracking (one event per user/recipe/day, idempotent) and Jaccard-similarity-based recipe recommendations weighted by ingredient overlap, ownership, favorites, and novelty. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cook history section on recipe pages, recommended recipes carousel, and a History page listing all cook events with delete support. Pins Node.js to v22 via .nvmrc (crypto.hash requires 21.7+). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
launchSettings.json had applicationUrl: http://+:5000 which dotnet watch overrode the ASPNETCORE_URLS env var, binding the API to port 5000 while the webapp proxy targeted 5001. All API calls failed silently, leaving the app stuck on "Loading...". Align both to port 5001. Also document the webapp's ASPNETCORE_URLS as a Vite proxy setting, not an ASP.NET setting, and restore the correct proxy target (http://api:5001). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ations Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
IsGreaterThan(lowerBound, value) checks value > lowerBound; both calls had the arguments reversed, causing the assertions to check the opposite of what was intended. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3 tasks
ghidalgo3
added a commit
that referenced
this pull request
Jun 15, 2026
Follow-up to #89. ## Summary Relocates the recipe recommendation algorithm from a single PostgreSQL function into the C# application, and adjusts the algorithm: the ownership signal is gone and a recipe must share at least 2 ingredients with the source to be recommended. **Why:** the database is great at indexes and set-membership lookups, but the recommendation *policy* (scoring, weighting, thresholds, ranking) is easier to read, unit-test, and evolve in application code without a migration. The DB now exposes primitives; the app composes them. ## Changes **Database** — new migration `018_recommendation_primitives.sql`: - Drops `cooktime.recommend_recipes`. - Adds four data-only primitives: `get_candidate_ingredient_sets`, `get_user_favorite_recipe_ids`, `get_user_last_cooked`, `get_recipe_summaries`. **Application** — new `RecommendationService`: - Owns the algorithm. Pure static `ScoreCandidates(...)` does Jaccard similarity, the >=2-overlap gate, weighting, and ranking with no DB access (directly unit-testable). - `CookTimeDB` drops `GetRecipeRecommendationsAsync`, gains the four primitive fetch methods. - Endpoint resolves `RecommendationService`; the service performs the existence check (`null` -> 404). **Algorithm changes** - Ownership signal removed entirely. - A recipe must share **>=2** ingredients with the source to appear at all; favorite/novelty only re-rank within that set. - Weights renormalized to sum to 1.0: ingredient similarity **0.70**, favorite **0.18**, novelty **0.12**. **Contract + frontend** — `ownedByUser` / "Your recipe" removed from the DTO, the TS type, and the admin score breakdown. **Docs** — blog post updated to describe the app-side algorithm, the overlap gate, and new weights. ## Tests - `scripts/test` -> 47/47 pass (43 existing + 4 new pure unit tests in `TestRecommendationService`). - Rewrote the two recommendation integration tests for the new weights and gate. - `scripts/test --lint` -> 0 errors. ## Test plan - [ ] On a recipe with neighbors sharing >=2 ingredients, recommendations appear; recipes with only 1 shared ingredient do not. - [ ] Signed in as Administrator, the score breakdown rows sum to the displayed score and there is no "Your recipe" row. - [ ] Anonymous users still get similarity-only recommendations. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
launchSettings.jsonhadapplicationUrl: http://+:5000whichdotnet watch runused to override the container'sASPNETCORE_URLS=http://+:5001, binding the API to port 5000. The Vite proxy targets 5001, so all API calls silently failed and the app was perpetually stuck on "Loading...". Aligned both to 5001..nvmrcpinning Node 22 —crypto.hashused by Vite requires Node 21.7+.Changes
Backend
017_recipe_cook_history_and_recommendations.sql— new migration:recipe_cook_eventstable, unique index for dedup, and SQL functions for CRUD +recommend_recipesRecipeRecommendationDto.cs— new DTOCookTimeDB.cs— six new DB methodsProgram.cs— six new API endpointsFrontend
CookHistorySection.tsx— "I cooked this" button + cook count summary on recipe pagesRecommendedRecipes.tsx— recommendation cards with admin score breakdownHistory.tsx+ route — full cook history page with deleteNavigationBar.tsx— History link in user dropdownInfrastructure
launchSettings.json—applicationUrlchanged from 5000 → 5001docker-compose.yml— restored correct webapp proxy target (http://api:5001) with clarifying comment; port mapping corrected to5001:5001Test plan
http://localhost:3000loads without getting stuck on "Loading..."🤖 Generated with Claude Code