Agent: catalog and garden tools, and a ready date on every describe group
Build image / build-and-push (push) Successful in 11s
Gadfly review (reusable) / review (pull_request) Successful in 4m42s
Adversarial Review (Gadfly) / review (pull_request) Successful in 4m42s

- update_seed_lot / delete_seed_lot: correct or drop a recorded purchase
  ("it was three packets, not two"); the plant a lot is for stays fixed.
- delete_plant: remove a duplicate from the user's catalog. The service
  already refuses while plantings (past seasons included) or a lot reference
  it; the tool turns that sentinel into words the model can pass on, and
  tells it not to clear those references to get its way.
- create_garden: a new place, with the service's defaults; the prompt says a
  plan is still a copy_garden.
- describe_garden groups carry readyAround — planting date plus days to
  maturity for the plops still in the ground — so "what can I pick this
  week?" is a lookup rather than arithmetic the model got wrong live.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
2026-08-23 02:05:19 -04:00
co-authored by Claude Fable 5
parent 35b27de8a0
commit b4c8007977
7 changed files with 299 additions and 3 deletions
+29
View File
@@ -7,6 +7,7 @@ import (
"log/slog"
"math"
"strings"
"time"
"gitea.stevedudenhoeffer.com/steve/pansy/internal/domain"
)
@@ -614,6 +615,11 @@ type DescribeGroup struct {
// DaysToMaturity is the plant's, when the catalog knows it — with PlantedAt,
// enough to say when the harvest is due.
DaysToMaturity *int `json:"daysToMaturity,omitempty"`
// ReadyAround is that arithmetic done: planting date plus days to maturity
// for the plops still in the ground, as one date or "first…last". Absent
// when the catalog has no days for the plant or nothing is dated. The
// model was asked "what can I pick this week?" and got the sums wrong.
ReadyAround string `json:"readyAround,omitempty"`
// Removed counts the plops in the group that have been pulled, and RemovedAt
// is when ("first…last" when they differ). Only a season view lists pulled
// plops, so both are absent from a describe of what is growing now.
@@ -746,6 +752,15 @@ func describeGroups(o *domain.GardenObject, plops []domain.Planting, plantByID m
DaysToMaturity: plant.DaysToMaturity,
RemovedAt: dateRangeOf(members, func(pl domain.Planting) *string { return pl.RemovedAt }),
}
if plant.DaysToMaturity != nil {
days := *plant.DaysToMaturity
g.ReadyAround = dateRangeOf(members, func(pl domain.Planting) *string {
if pl.RemovedAt != nil {
return nil // pulled already; its harvest is not ahead of us
}
return readyDate(pl.PlantedAt, days)
})
}
for _, pl := range members {
g.Plants += effectiveCount(pl)
if pl.RemovedAt != nil {
@@ -778,6 +793,20 @@ func describePlanting(pl domain.Planting, plantName string) DescribePlanting {
return d
}
// readyDate is plantedAt plus days to maturity, or nil when the plop is undated
// (or its date is not one the store should have accepted).
func readyDate(plantedAt *string, days int) *string {
if plantedAt == nil || *plantedAt == "" {
return nil
}
t, err := time.Parse(dateLayout, *plantedAt)
if err != nil {
return nil
}
d := t.AddDate(0, 0, days).Format(dateLayout)
return &d
}
// effectiveCount is the plant count a plop stands for: its explicit count, else
// the one derived from its area and the plant's spacing.
func effectiveCount(pl domain.Planting) int {