Rotation check: tried dinners and keep/drop decisions (#116)
CI / ci (push) Failing after 5s
CI / ci (push) Failing after 5s
Co-authored-by: orchestrator-agent <20+orchestrator-agent@code.cubecraftcreations.com>
This commit was merged in pull request #116.
This commit is contained in:
@@ -43,7 +43,7 @@ type batchResult struct {
|
||||
func (r batchResult) PublicJSON() ([]byte, error) { return json.Marshal(r) }
|
||||
|
||||
// runBatch runs batch p.BatchID: it asks suggest_side_quests for BatchSize
|
||||
// Side quests given the household context, the deck and every declined
|
||||
// Side quests given the household context, the deck and every passed
|
||||
// name, then grounds them in the model's order, at most the number still
|
||||
// wanted at a time, until BatchSize are kept or none are left. A Side quest
|
||||
// no recipe grounds is dropped; any other grounding failure fails the
|
||||
@@ -132,7 +132,7 @@ func (s *Service) fill(ctx context.Context, batchID string, publish func(batchRe
|
||||
}
|
||||
|
||||
// request reads what suggest_side_quests is given: the household context,
|
||||
// the deck's names and every declined name. A household with no Regulars
|
||||
// the deck's names and every passed name (ListPassedNames). A household with no Regulars
|
||||
// fails with ReasonNoRegulars.
|
||||
func (s *Service) request(ctx context.Context) (skills.SideQuestsRequest, error) {
|
||||
hctx, err := harness.BuildContext(ctx, s.st, time.Now(), ContextBudget)
|
||||
@@ -151,9 +151,9 @@ func (s *Service) request(ctx context.Context) (skills.SideQuestsRequest, error)
|
||||
for _, row := range deck {
|
||||
pending = append(pending, row.Dinner.Name)
|
||||
}
|
||||
declined, err := q.ListDeclinedSuggestionNames(ctx)
|
||||
declined, err := q.ListPassedNames(ctx)
|
||||
if err != nil {
|
||||
return skills.SideQuestsRequest{}, fmt.Errorf("suggest: list declined names: %w", err)
|
||||
return skills.SideQuestsRequest{}, fmt.Errorf("suggest: list passed names: %w", err)
|
||||
}
|
||||
return skills.SideQuestsRequest{Context: hctx, Needed: BatchSize, Pending: pending, Declined: declined}, nil
|
||||
}
|
||||
@@ -201,9 +201,9 @@ func (s *Service) ground(ctx context.Context, quests []skills.SideQuest) ([]grou
|
||||
// suggestion citing its Regulars by id, all in one transaction, and
|
||||
// returns the suggestion ids. A Side quest is left out when, since
|
||||
// suggest_side_quests ran, a dinner it repeats was added, a suggestion it
|
||||
// repeats passed, or a Regular it cites renamed or archived. Declined
|
||||
// names are read in the same transaction as the store, so a Pass committed
|
||||
// before it is never missed.
|
||||
// repeats passed or a Rotation check dropped, or a Regular it cites renamed
|
||||
// or archived. Passed names are read in the same transaction as the store,
|
||||
// so a Pass or drop committed before it is never missed.
|
||||
func (s *Service) store(ctx context.Context, batchID string, quests []groundedQuest) ([]string, error) {
|
||||
ids := []string{}
|
||||
err := s.st.WithTx(ctx, func(tx *sql.Tx) error {
|
||||
@@ -212,9 +212,9 @@ func (s *Service) store(ctx context.Context, batchID string, quests []groundedQu
|
||||
if err != nil {
|
||||
return fmt.Errorf("suggest: list dinners for dedupe: %w", err)
|
||||
}
|
||||
names, err := q.ListDeclinedSuggestionNames(ctx)
|
||||
names, err := q.ListPassedNames(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("suggest: list declined names for dedupe: %w", err)
|
||||
return fmt.Errorf("suggest: list passed names for dedupe: %w", err)
|
||||
}
|
||||
regulars := map[string][]string{}
|
||||
for _, row := range existing {
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/dinners"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/household"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/jobs"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/plan"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/recipes"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/recipes/recipestest"
|
||||
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/store"
|
||||
@@ -646,7 +647,7 @@ func TestDeclineIsIdempotent(t *testing.T) {
|
||||
if got := e.dinnerStatus(t, s.DinnerID); got != "archived" {
|
||||
t.Errorf("dinner status after refused accept = %s, want archived", got)
|
||||
}
|
||||
names, err := db.New(e.st.Reader).ListDeclinedSuggestionNames(t.Context())
|
||||
names, err := db.New(e.st.Reader).ListPassedNames(t.Context())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -912,6 +913,74 @@ func TestLaterBatchNeverResuggestsRenamedPass(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestLaterBatchNeverResuggestsRenamedDrop drops a tried dinner at its
|
||||
// Rotation check ("Not for us"), renames its archived dinner, and checks
|
||||
// the next batch still refuses the name it was dropped under.
|
||||
func TestLaterBatchNeverResuggestsRenamedDrop(t *testing.T) {
|
||||
e := newEnv(t)
|
||||
ctx := t.Context()
|
||||
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||
dinnerSvc, err := dinners.NewService(e.st, e.grounder, e.runner, logger)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fail := func(context.Context, string, skills.WeekRequest) (skills.Week, error) {
|
||||
return skills.Week{}, errors.New("no draft in this test")
|
||||
}
|
||||
planSvc, err := plan.NewService(e.st, fail, e.runner, logger)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
id := e.dinner(t, "Pad Thai", "quest_log")
|
||||
q := db.New(e.st.Writer)
|
||||
now := store.Now().String()
|
||||
planID := store.NewID()
|
||||
if err := q.CreateWeekPlan(ctx, db.CreateWeekPlanParams{ID: planID, WeekStart: "2026-10-12", CreatedAt: now, UpdatedAt: now}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := q.PutPlanDay(ctx, db.PutPlanDayParams{ID: store.NewID(), PlanID: planID, Date: "2026-10-14",
|
||||
DinnerID: sql.NullString{String: id, Valid: true}, CreatedAt: now, UpdatedAt: now}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
day, err := store.ParseLocalDate("2026-10-14")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n, err := planSvc.MarkTried(ctx, day); err != nil || n != 1 {
|
||||
t.Fatalf("MarkTried = %d, %v, want 1", n, err)
|
||||
}
|
||||
if _, err := planSvc.DecideReview(ctx, id, plan.DecisionDrop, member); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
renamed := "Peanut Noodles"
|
||||
if _, err := dinnerSvc.Update(ctx, id, dinners.Patch{Name: &renamed}, member); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// The respelled dropped dish would ground, so only the passed names
|
||||
// keep it out of the deck.
|
||||
e.recipe(t, "pad thai!")
|
||||
e.recipe(t, "Beef Ragu")
|
||||
e.reply(
|
||||
sideQuest("pad thai!", "Noodles, like your Chicken Fajitas.", "Chicken Fajitas"),
|
||||
sideQuest("Beef Ragu", "Slow beef like your Beef Chili.", "Beef Chili"),
|
||||
)
|
||||
e.start(t)
|
||||
res := e.runBatch(t)
|
||||
if len(res.SuggestionIDs) != 1 || res.Dropped != 1 {
|
||||
t.Errorf("batch result = %+v, want 1 kept and the dropped dish dropped", res)
|
||||
}
|
||||
if got := e.deckNames(t); !slices.Equal(got, []string{"Beef Ragu"}) {
|
||||
t.Errorf("deck = %v, want [Beef Ragu]", got)
|
||||
}
|
||||
e.mu.Lock()
|
||||
in := e.inputs[0]
|
||||
e.mu.Unlock()
|
||||
if !slices.Equal(in.Declined, []string{"Pad Thai"}) || !slices.Equal(in.Context.Passed, []string{"Pad Thai"}) {
|
||||
t.Errorf("batch declined %v, passed %v; want the name it was dropped under in both", in.Declined, in.Context.Passed)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDecisionNeverCommitsWithoutItsReplenish makes the replenish batch's
|
||||
// job fail to store: the decision must not be recorded either, so the
|
||||
// retry decides again and starts the replenish.
|
||||
|
||||
Reference in New Issue
Block a user