diff --git a/dashboard-server b/dashboard-server index f4ebd49..366e854 100755 Binary files a/dashboard-server and b/dashboard-server differ diff --git a/data/claude-usage-history.json b/data/claude-usage-history.json index 83b7a9b..5575516 100644 --- a/data/claude-usage-history.json +++ b/data/claude-usage-history.json @@ -833,5 +833,15 @@ "session_percent": 2, "timestamp": "2026-02-13T21:19:34.051622Z", "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 } ] \ No newline at end of file diff --git a/data/deliveries.json b/data/deliveries.json index f15b28a..ddd01cb 100644 --- a/data/deliveries.json +++ b/data/deliveries.json @@ -1,357 +1,3 @@ { - "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" - } - ] + "deliveries": [] } \ No newline at end of file diff --git a/data/status.json b/data/status.json index ca6966d..a313094 100644 --- a/data/status.json +++ b/data/status.json @@ -2,9 +2,9 @@ "items": { "claude": { "key": "claude", - "value": "67% used · 4:00 PM", + "value": "67% used · 5:00 PM", "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": { "key": "claude-code-update", diff --git a/james-dashboard b/james-dashboard index c2e04da..366e854 100755 Binary files a/james-dashboard and b/james-dashboard differ diff --git a/server.go b/server.go index 5c1718a..62ad0fd 100644 --- a/server.go +++ b/server.go @@ -529,6 +529,135 @@ func (s *DeliveryStore) ListAll() []Delivery { 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) { s.mu.RLock() 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 mux.HandleFunc("/api/deliveries/", func(w http.ResponseWriter, r *http.Request) { cors(w)