fix(planning): replace existing slot in simulation instead of appending
simulateVarietyScore was adding the candidate recipe on top of the existing slot for slotDate, keeping the old recipe's tag-repeat penalty in the score. Now the existing slot is excluded before simulating, so swapping a recipe for one with better variety correctly shows positive scoreDelta and hasConflict=false. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -174,7 +174,9 @@ public class PlanningService {
|
|||||||
VarietyScoreConfig config, Set<UUID> recentlyCookedIds) {
|
VarietyScoreConfig config, Set<UUID> recentlyCookedIds) {
|
||||||
List<SimulatedSlot> simulatedSlots = new ArrayList<>();
|
List<SimulatedSlot> simulatedSlots = new ArrayList<>();
|
||||||
for (WeekPlanSlot slot : plan.getSlots()) {
|
for (WeekPlanSlot slot : plan.getSlots()) {
|
||||||
simulatedSlots.add(new SimulatedSlot(slot.getRecipe(), slot.getSlotDate()));
|
if (!slot.getSlotDate().equals(slotDate)) {
|
||||||
|
simulatedSlots.add(new SimulatedSlot(slot.getRecipe(), slot.getSlotDate()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
simulatedSlots.add(new SimulatedSlot(candidate, slotDate));
|
simulatedSlots.add(new SimulatedSlot(candidate, slotDate));
|
||||||
return scoreFromSimulatedSlots(simulatedSlots, config, recentlyCookedIds);
|
return scoreFromSimulatedSlots(simulatedSlots, config, recentlyCookedIds);
|
||||||
|
|||||||
@@ -329,6 +329,37 @@ class SuggestionsTest {
|
|||||||
assertThat(item.hasConflict()).isTrue();
|
assertThat(item.hasConflict()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void swappingExistingSlotForCleanRecipeShouldHavePositiveDelta() {
|
||||||
|
// Plan has Mon=ItalianA, Tue=ItalianB → consecutive cuisine tag repeat → currentScore = 8.5
|
||||||
|
// Asking for suggestions for Mon (swap scenario).
|
||||||
|
// CleanRecipe (no Italian tag) → correct simulation: [Mon:CleanRecipe, Tue:ItalianB] → no repeat → 10.0
|
||||||
|
// scoreDelta = +1.5 → hasConflict = false
|
||||||
|
var plan = createPlan();
|
||||||
|
var italianTag = createTag("Italienisch", "cuisine");
|
||||||
|
var italianA = createRecipe("Spaghetti Carbonara");
|
||||||
|
addTag(italianA, italianTag);
|
||||||
|
addSlot(plan, italianA, MONDAY);
|
||||||
|
var italianB = createRecipe("Penne Arrabiata");
|
||||||
|
addTag(italianB, italianTag);
|
||||||
|
addSlot(plan, italianB, MONDAY.plusDays(1));
|
||||||
|
var cleanRecipe = createRecipe("Grillhähnchen");
|
||||||
|
|
||||||
|
stubPlan(plan);
|
||||||
|
stubDefaultConfig();
|
||||||
|
stubRecipes(italianA, italianB, cleanRecipe);
|
||||||
|
stubNoCookingLogs();
|
||||||
|
|
||||||
|
SuggestionResponse result = planningService.getSuggestions(
|
||||||
|
HOUSEHOLD_ID, plan.getId(), MONDAY, List.of(), 5);
|
||||||
|
|
||||||
|
assertThat(result.suggestions()).hasSize(1);
|
||||||
|
var item = result.suggestions().getFirst();
|
||||||
|
assertThat(item.recipe().name()).isEqualTo("Grillhähnchen");
|
||||||
|
assertThat(item.scoreDelta()).isCloseTo(1.5, within(0.001));
|
||||||
|
assertThat(item.hasConflict()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void scoreDeltaIsSortedDescendingCleanBeforeConflicting() {
|
void scoreDeltaIsSortedDescendingCleanBeforeConflicting() {
|
||||||
// Clean recipe (scoreDelta = 0.0) should rank above conflicting (scoreDelta < 0).
|
// Clean recipe (scoreDelta = 0.0) should rank above conflicting (scoreDelta < 0).
|
||||||
|
|||||||
Reference in New Issue
Block a user