Add delivery upsert endpoint to prevent duplicates

PUT /api/deliveries/upsert matches by tracking_number (exact) or
description+retailer (fuzzy 50% word overlap). Updates existing
delivery if found, creates new if not. Status only progresses
forward (shipped→in_transit→out_for_delivery→delivered).
This commit is contained in:
James 2026-02-13 17:22:09 -05:00
parent ec3e5eea0b
commit baf9db9c1e
6 changed files with 182 additions and 357 deletions

Binary file not shown.

View File

@ -833,5 +833,15 @@
"session_percent": 2, "session_percent": 2,
"timestamp": "2026-02-13T21:19:34.051622Z", "timestamp": "2026-02-13T21:19:34.051622Z",
"weekly_percent": 67 "weekly_percent": 67
},
{
"session_percent": 2,
"timestamp": "2026-02-13T22:00:01.669723Z",
"weekly_percent": 67
},
{
"session_percent": 2,
"timestamp": "2026-02-13T22:00:02.638257Z",
"weekly_percent": 67
} }
] ]

View File

@ -1,357 +1,3 @@
{ {
"deliveries": [ "deliveries": []
{
"id": "0e67333b",
"carrier": "Pediatric Home Service",
"retailer": "All About Pediatrics",
"description": "Sophia monthly supplies (Feb 2026) - 4 boxes",
"tracking_number": "75175",
"expected_date": "2026-02-06",
"status": "delivered",
"notes": "Shipped Feb 3, 4 boxes. Expected Feb 6-7.",
"created_at": "2026-02-04T14:36:17.040575584-05:00",
"updated_at": "2026-02-08T03:48:44.120692938-05:00"
},
{
"id": "4d72150d",
"carrier": "Amazon",
"retailer": "Amazon",
"description": "World Organic Chlorophyll Liquid",
"tracking_number": "112-4979107-5850639",
"expected_date": "2026-02-08",
"status": "shipped",
"notes": "Order #112-4979107-5850639, $13.42. Arriving Sunday.",
"created_at": "2026-02-04T14:36:17.076836995-05:00",
"updated_at": "2026-02-04T14:36:17.076837035-05:00"
},
{
"id": "795c2095",
"carrier": "Amazon",
"retailer": "Amazon",
"description": "ARG ButyrEn Colon Lining, INCAS Butterfly Pea",
"tracking_number": "113-8193397-8031467",
"expected_date": "2026-02-07",
"status": "delivered",
"notes": "Arriving today 7 AM - 11 AM. Likely delivered Feb 7.",
"created_at": "2026-02-07T02:18:56.918641023-05:00",
"updated_at": "2026-02-08T03:48:34.238948912-05:00"
},
{
"id": "a7c36153",
"carrier": "Amazon",
"retailer": "Amazon",
"description": "Pure Encapsulations Vitamin D3 \u0026 K2 Liquid",
"tracking_number": "113-1880998-3453029",
"expected_date": "2026-02-07",
"status": "delivered",
"notes": "Likely delivered Feb 7.",
"created_at": "2026-02-07T02:51:07.360704007-05:00",
"updated_at": "2026-02-08T03:48:34.250472485-05:00"
},
{
"id": "9e4540ab",
"carrier": "Colorado Camel Milk",
"retailer": "Colorado Camel Milk",
"description": "Raw Camel Milk (6 Pints, Frozen)",
"status": "shipped",
"notes": "Order #16698, $115.00 (paid via Venmo)",
"created_at": "2026-02-07T17:42:53.341962693-05:00",
"updated_at": "2026-02-08T03:50:24.733529085-05:00"
},
{
"id": "29790fb7",
"carrier": "UPS",
"description": "Package from Jimmy Le (signature required)",
"tracking_number": "1ZX866D54223587093",
"tracking_url": "https://www.ups.com/track?tracknum=1ZX866D54223587093",
"expected_date": "2026-02-07",
"status": "delivered",
"notes": "Signature required. ETA 2-6 PM",
"created_at": "2026-02-07T23:46:33.876959687-05:00",
"updated_at": "2026-02-08T03:48:34.244859442-05:00"
},
{
"id": "30314356",
"carrier": "Amazon",
"retailer": "Amazon",
"description": "MagiCare 75% Large Alcohol Wipes",
"expected_date": "2026-02-09",
"status": "shipped",
"notes": "Order #112-6757146-7896258, $29.95",
"created_at": "2026-02-08T01:09:32.878277346-05:00",
"updated_at": "2026-02-08T03:50:24.739469433-05:00"
},
{
"id": "e72c423c",
"carrier": "Amazon",
"retailer": "Amazon",
"description": "Master Supplements TruBifido - 30ct",
"tracking_number": "113-3964151-5665049",
"expected_date": "2026-02-10",
"status": "shipped",
"notes": "Sophia supplement (from Monica Rodriguez list). $37.46",
"created_at": "2026-02-08T01:34:44.900771986-05:00",
"updated_at": "2026-02-08T01:34:44.900772032-05:00"
},
{
"id": "e67d6e34",
"carrier": "Amazon",
"retailer": "Amazon",
"description": "ButyrEn Colon Lining + INCAS Organic Butterfly Pea",
"expected_date": "2026-02-08",
"status": "out_for_delivery",
"notes": "Order 113-8193397-8031467, arriving today 7-11AM",
"created_at": "2026-02-08T07:37:44.383098362-05:00",
"updated_at": "2026-02-08T07:37:44.38309841-05:00"
},
{
"id": "eb31bd8c",
"carrier": "Amazon",
"retailer": "Amazon",
"description": "OM Mushroom Cordyceps Powder",
"expected_date": "2026-02-08",
"status": "out_for_delivery",
"notes": "Order 113-3964151-5665049, arriving today",
"created_at": "2026-02-08T07:37:44.389777982-05:00",
"updated_at": "2026-02-08T07:37:44.389778035-05:00"
},
{
"id": "1cc821ec",
"carrier": "Amazon",
"retailer": "Amazon",
"description": "ARG ButyrEn + INCAS Organic Butterfly Pea",
"tracking_number": "113-8193397-8031467",
"status": "delivered",
"notes": "Arrived Feb 7",
"created_at": "2026-02-08T09:07:54.018512029-05:00",
"updated_at": "2026-02-08T09:07:54.01851207-05:00"
},
{
"id": "0bcd9ff8",
"carrier": "Amazon",
"retailer": "Amazon",
"description": "OM Mushroom Cordyceps Powder",
"tracking_number": "113-3964151-5665049",
"status": "delivered",
"notes": "Arrived Feb 7",
"created_at": "2026-02-08T09:07:54.023912332-05:00",
"updated_at": "2026-02-08T09:07:54.023912377-05:00"
},
{
"id": "41944abc",
"carrier": "Amazon",
"retailer": "Amazon",
"description": "MagiCare 75% Large Alcohol Wipes",
"tracking_number": "112-6757146-7896258",
"expected_date": "2026-02-09",
"status": "in_transit",
"created_at": "2026-02-08T09:07:54.029203598-05:00",
"updated_at": "2026-02-08T09:07:54.02920364-05:00"
},
{
"id": "1c992ade",
"carrier": "Amazon",
"retailer": "Amazon",
"description": "Aveda Men Pure-Formance Shampoo",
"tracking_number": "112-5748597-2525838",
"expected_date": "2026-02-09",
"status": "in_transit",
"created_at": "2026-02-08T09:07:54.034733552-05:00",
"updated_at": "2026-02-08T09:07:54.03473359-05:00"
},
{
"id": "e37775f6",
"carrier": "3-day shipping",
"retailer": "Colorado Camel Milk",
"description": "Raw Camel Milk (6 Pints, frozen)",
"tracking_number": "16698",
"expected_date": "2026-02-10",
"status": "shipped",
"created_at": "2026-02-08T09:07:54.039884643-05:00",
"updated_at": "2026-02-08T09:07:54.039884684-05:00"
},
{
"id": "15d39860",
"carrier": "Amazon",
"retailer": "Amazon",
"description": "MagiCare 75% Large Alcohol Wipes",
"tracking_number": "112-6757146-7896258",
"expected_date": "2026-02-09",
"status": "shipped",
"notes": "Arriving tomorrow per email",
"created_at": "2026-02-08T12:21:01.509773967-05:00",
"updated_at": "2026-02-08T12:21:01.509774016-05:00"
},
{
"id": "11eabe68",
"carrier": "Amazon",
"retailer": "Amazon",
"description": "World Organic Chlorophyll",
"status": "delivered",
"notes": "Order #112-4979107-5850639. Delivered 2026-02-08, left near front door.",
"created_at": "2026-02-08T17:27:11.616850452-05:00",
"updated_at": "2026-02-08T17:27:11.616850498-05:00"
},
{
"id": "73113b30",
"carrier": "Amazon",
"retailer": "Amazon",
"description": "MagiCare 75% Large Alcohol Wipes",
"expected_date": "2026-02-09",
"status": "shipped",
"notes": "Order #112-6757146-7896258. $29.95 total.",
"created_at": "2026-02-08T17:27:11.677036538-05:00",
"updated_at": "2026-02-08T17:27:11.677036594-05:00"
},
{
"id": "a0c2d2a4",
"carrier": "Amazon",
"retailer": "Amazon",
"description": "ARG ButyrEn Colon Lining + INCAS Organic Butterfly Pea",
"expected_date": "2026-02-07",
"status": "delivered",
"notes": "Order #113-8193397-8031467. $46.18 total. Was arriving Feb 7.",
"created_at": "2026-02-08T17:27:11.735634844-05:00",
"updated_at": "2026-02-08T17:27:11.735634885-05:00"
},
{
"id": "fd8973ed",
"carrier": "Amazon",
"retailer": "Amazon",
"description": "OM Mushroom Cordyceps Powder",
"expected_date": "2026-02-07",
"status": "delivered",
"notes": "Order #113-3964151-5665049. $32.38 total. Was arriving Feb 7.",
"created_at": "2026-02-08T17:27:11.792476354-05:00",
"updated_at": "2026-02-08T17:27:11.792476408-05:00"
},
{
"id": "59a4837a",
"carrier": "Amazon",
"retailer": "Amazon",
"description": "Aveda Men Pure-Formance Shampoo",
"expected_date": "2026-02-09",
"status": "shipped",
"notes": "Order #112-5748597-2525838, $34.24",
"created_at": "2026-02-08T19:44:36.545183521-05:00",
"updated_at": "2026-02-08T19:44:36.545183561-05:00"
},
{
"id": "1f84c0fa",
"carrier": "Amazon",
"retailer": "Amazon",
"description": "Aveda Men Pure-Formance Shampoo",
"expected_date": "2026-02-10",
"status": "shipped",
"created_at": "2026-02-09T06:05:19.42294246-05:00",
"updated_at": "2026-02-09T06:05:19.422942506-05:00"
},
{
"id": "15658ca7",
"carrier": "UPS",
"retailer": "BeeVitamin",
"description": "RAW Wild Collected Organic Bee Pollen - Wellness Pack 13.5oz",
"tracking_number": "1ZG772R41219164330",
"tracking_url": "https://www.ups.com/track?tracknum=1ZG772R41219164330",
"expected_date": "2026-02-10",
"status": "delivered",
"notes": "Delivered Feb 10 at 12:22 PM",
"created_at": "2026-02-09T09:41:34.259459825-05:00",
"updated_at": "2026-02-10T13:48:06.246068906-05:00"
},
{
"id": "46798d2a",
"carrier": "UPS",
"description": "MELBOURNE RETAIL LLC package",
"tracking_number": "1ZG772R41219164330",
"expected_date": "2026-02-10",
"status": "delivered",
"notes": "Delivered Tue 2/10 at 12:22 PM. BeeVitamin RAW Wild Collected Organic Bee Pollen Wellness Pack.",
"created_at": "2026-02-09T11:57:33.581373456-05:00",
"updated_at": "2026-02-10T14:10:06.549296379-05:00"
},
{
"id": "2d1cd36b",
"carrier": "UPS",
"description": "Melbourne Retail LLC package",
"expected_date": "2026-02-10",
"status": "in_transit",
"notes": "Signature required. Delivery window 11am-3pm. Ship to 851 Brightwaters Blvd NE.",
"created_at": "2026-02-09T11:58:46.928219974-05:00",
"updated_at": "2026-02-09T11:58:46.928220012-05:00"
},
{
"id": "66cdfe4f",
"carrier": "Amazon",
"retailer": "Amazon",
"description": "Frontier Co-op Organic Oatstraw",
"tracking_number": "114-4974113-8321869",
"expected_date": "2026-02-10",
"status": "shipped",
"notes": "$24.58",
"created_at": "2026-02-09T20:09:15.823706776-05:00",
"updated_at": "2026-02-09T20:09:15.823706816-05:00"
},
{
"id": "ad240775",
"carrier": "Amazon",
"retailer": "Amazon",
"description": "Goodnites Girls Bedwetting Underwear x2",
"tracking_number": "113-7309601-6593066",
"tracking_url": "https://www.amazon.com/gp/your-account/order-details?orderID=113-7309601-6593066",
"expected_date": "2026-02-10",
"status": "in_transit",
"notes": "$116.98 total",
"created_at": "2026-02-10T01:13:15.372694153-05:00",
"updated_at": "2026-02-10T01:13:15.37269427-05:00"
},
{
"id": "26af60bd",
"carrier": "Amazon",
"retailer": "Amazon",
"description": "Rubbermaid Brilliance Glass Storage",
"tracking_number": "112-3200486-0340244",
"expected_date": "2026-02-10",
"status": "out_for_delivery",
"notes": "$37.44 - Late, now expected today (Feb 11) by 10 PM",
"created_at": "2026-02-10T15:43:39.951663798-05:00",
"updated_at": "2026-02-11T04:19:48.557977901-05:00"
},
{
"id": "00ffdf0c",
"carrier": "Amazon",
"retailer": "Amazon",
"description": "Rubbermaid Brilliance Glass Storage",
"tracking_number": "112-3200486-0340244",
"expected_date": "2026-02-10",
"status": "out_for_delivery",
"notes": "Arriving today 5-10 PM",
"created_at": "2026-02-10T18:07:38.038611526-05:00",
"updated_at": "2026-02-10T18:07:38.038611577-05:00"
},
{
"id": "ffabc266",
"carrier": "Amazon",
"retailer": "Amazon",
"description": "500 Removable Freezer Pantry Labels",
"tracking_number": "113-3740111-3149065",
"expected_date": "2026-02-11",
"status": "in_transit",
"notes": "$8.55",
"created_at": "2026-02-10T22:03:38.220701198-05:00",
"updated_at": "2026-02-10T22:03:38.220701243-05:00"
},
{
"id": "9cb9de1f",
"carrier": "Amazon",
"retailer": "Amazon",
"description": "EZVALO 3 Pack Motion Sensor Night Light",
"tracking_number": "112-9663114-1823458",
"expected_date": "2026-02-13",
"status": "delivered",
"notes": "Delivered Feb 13, left near front door. Order #112-9663114-1823458, $20.32",
"created_at": "2026-02-12T10:33:24.342828313-05:00",
"updated_at": "2026-02-13T16:05:39.180806618-05:00"
}
]
} }

View File

@ -2,9 +2,9 @@
"items": { "items": {
"claude": { "claude": {
"key": "claude", "key": "claude",
"value": "67% used · 4:00 PM", "value": "67% used · 5:00 PM",
"type": "info", "type": "info",
"updated_at": "2026-02-13T16:00:03.090690991-05:00" "updated_at": "2026-02-13T17:00:02.730387548-05:00"
}, },
"claude-code-update": { "claude-code-update": {
"key": "claude-code-update", "key": "claude-code-update",

Binary file not shown.

169
server.go
View File

@ -529,6 +529,135 @@ func (s *DeliveryStore) ListAll() []Delivery {
return s.Deliveries return s.Deliveries
} }
// Upsert finds an existing delivery by tracking_number (exact match) or
// description+retailer (fuzzy), then updates it. If no match, creates new.
// Returns the delivery, whether it was an update (vs create), and any error.
func (s *DeliveryStore) Upsert(d Delivery) (Delivery, bool, error) {
s.mu.Lock()
defer s.mu.Unlock()
// 1. Match by tracking_number (strongest signal)
if d.TrackingNumber != "" {
for i, existing := range s.Deliveries {
if existing.TrackingNumber == d.TrackingNumber {
s.mergeDelivery(i, d)
s.save()
return s.Deliveries[i], true, nil
}
}
}
// 2. Match by retailer + similar description (for orders without tracking yet)
if d.Retailer != "" && d.Description != "" {
for i, existing := range s.Deliveries {
if existing.Retailer == d.Retailer && existing.Status != "delivered" &&
deliveryDescSimilar(existing.Description, d.Description) {
s.mergeDelivery(i, d)
s.save()
return s.Deliveries[i], true, nil
}
}
}
// 3. No match — create new
d.ID = uuid.New().String()[:8]
d.CreatedAt = time.Now()
d.UpdatedAt = time.Now()
if d.Status == "" {
d.Status = "shipped"
}
s.Deliveries = append(s.Deliveries, d)
return d, false, s.save()
}
// mergeDelivery updates existing delivery fields with non-empty values from the update.
// Status always progresses forward (shipped→in_transit→out_for_delivery→delivered), never backward.
func (s *DeliveryStore) mergeDelivery(i int, u Delivery) {
if u.Carrier != "" {
s.Deliveries[i].Carrier = u.Carrier
}
if u.Retailer != "" {
s.Deliveries[i].Retailer = u.Retailer
}
if u.Description != "" {
s.Deliveries[i].Description = u.Description
}
if u.TrackingNumber != "" {
s.Deliveries[i].TrackingNumber = u.TrackingNumber
}
if u.TrackingURL != "" {
s.Deliveries[i].TrackingURL = u.TrackingURL
}
if u.ExpectedDate != "" {
s.Deliveries[i].ExpectedDate = u.ExpectedDate
}
if u.Status != "" && deliveryStatusRank(u.Status) >= deliveryStatusRank(s.Deliveries[i].Status) {
s.Deliveries[i].Status = u.Status
}
if u.Notes != "" {
s.Deliveries[i].Notes = u.Notes
}
s.Deliveries[i].UpdatedAt = time.Now()
}
var deliveryStatusOrder = map[string]int{
"shipped": 0, "in_transit": 1, "out_for_delivery": 2, "delayed": 1, "delivered": 3,
}
func deliveryStatusRank(s string) int {
if r, ok := deliveryStatusOrder[s]; ok {
return r
}
return 0
}
// deliveryDescSimilar checks if two descriptions likely refer to the same product.
// Compares normalized words — if 50%+ of shorter description's words appear in longer, it's a match.
func deliveryDescSimilar(a, b string) bool {
normalize := func(s string) []string {
s = strings.ToLower(s)
// Remove common noise
for _, noise := range []string{",", ".", "-", "(", ")", "+", "&", "x2", "x3", "x4"} {
s = strings.ReplaceAll(s, noise, " ")
}
words := strings.Fields(s)
// Filter short/common words
var result []string
for _, w := range words {
if len(w) > 2 {
result = append(result, w)
}
}
return result
}
wordsA := normalize(a)
wordsB := normalize(b)
if len(wordsA) == 0 || len(wordsB) == 0 {
return false
}
// Use shorter as reference
shorter, longer := wordsA, wordsB
if len(wordsA) > len(wordsB) {
shorter, longer = wordsB, wordsA
}
longerSet := make(map[string]bool)
for _, w := range longer {
longerSet[w] = true
}
matches := 0
for _, w := range shorter {
if longerSet[w] {
matches++
}
}
return float64(matches)/float64(len(shorter)) >= 0.5
}
func (s *DeliveryStore) Get(id string) (Delivery, bool) { func (s *DeliveryStore) Get(id string) (Delivery, bool) {
s.mu.RLock() s.mu.RLock()
defer s.mu.RUnlock() defer s.mu.RUnlock()
@ -1175,6 +1304,46 @@ func main() {
} }
}) })
// Delivery upsert — match by tracking_number or description, update or create
mux.HandleFunc("/api/deliveries/upsert", func(w http.ResponseWriter, r *http.Request) {
cors(w)
if r.Method == "OPTIONS" {
return
}
if r.Method != "PUT" && r.Method != "POST" {
http.Error(w, `{"error": "method not allowed"}`, http.StatusMethodNotAllowed)
return
}
var d Delivery
if err := json.NewDecoder(r.Body).Decode(&d); err != nil {
http.Error(w, `{"error": "invalid JSON"}`, http.StatusBadRequest)
return
}
if d.Description == "" {
http.Error(w, `{"error": "description required"}`, http.StatusBadRequest)
return
}
result, wasUpdate, err := deliveryStore.Upsert(d)
if err != nil {
http.Error(w, `{"error": "failed to save"}`, http.StatusInternalServerError)
return
}
status := http.StatusCreated
action := "created"
if wasUpdate {
status = http.StatusOK
action = "updated"
}
w.WriteHeader(status)
json.NewEncoder(w).Encode(map[string]interface{}{
"delivery": result,
"action": action,
})
})
// Delivery by ID // Delivery by ID
mux.HandleFunc("/api/deliveries/", func(w http.ResponseWriter, r *http.Request) { mux.HandleFunc("/api/deliveries/", func(w http.ResponseWriter, r *http.Request) {
cors(w) cors(w)