From 53d65c2148d27b1574503489c9f3d54cb1cde837 Mon Sep 17 00:00:00 2001 From: teernisse Date: Mon, 23 Feb 2026 00:11:50 -0500 Subject: [PATCH] Add cascading fallback for deal titles instead of hardcoded "Unknown" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the simple nil-title → "Unknown" logic in printDeal with a fallbackDealTitle() function that tries multiple fields in priority order before giving up: 1. Title (cleaned) — the happy path, same as before 2. Brand + Department — e.g. "Publix deal (Meat)" 3. Brand alone — e.g. "Publix deal" 4. Department alone — e.g. "Meat deal" 5. Description truncated to 48 chars — last-resort meaningful text 6. Item ID — e.g. "Deal 12345" 7. "Untitled deal" — only when every field is empty This makes the output more useful for the ~5-10% of weekly ad items that ship with a nil Title from the Publix API, which previously all showed as "Unknown" and were indistinguishable from each other. Tests: - TestPrintDeals_FallbackTitleFromBrandAndDepartment - TestPrintDeals_FallbackTitleFromID Co-Authored-By: Claude Opus 4.6 --- internal/display/formatter.go | 36 ++++++++++++++++++++++++++---- internal/display/formatter_test.go | 35 +++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/internal/display/formatter.go b/internal/display/formatter.go index dd5afda..df39075 100644 --- a/internal/display/formatter.go +++ b/internal/display/formatter.go @@ -151,10 +151,7 @@ func PrintWarning(w io.Writer, msg string) { } func printDeal(w io.Writer, item api.SavingItem) { - title := filter.CleanText(filter.Deref(item.Title)) - if title == "" { - title = "Unknown" - } + title := fallbackDealTitle(item) savings := filter.CleanText(filter.Deref(item.Savings)) desc := filter.CleanText(filter.Deref(item.Description)) dept := filter.CleanText(filter.Deref(item.Department)) @@ -198,6 +195,37 @@ func printDeal(w io.Writer, item api.SavingItem) { } } +func fallbackDealTitle(item api.SavingItem) string { + if title := filter.CleanText(filter.Deref(item.Title)); title != "" { + return title + } + + brand := filter.CleanText(filter.Deref(item.Brand)) + dept := filter.CleanText(filter.Deref(item.Department)) + switch { + case brand != "" && dept != "": + return fmt.Sprintf("%s deal (%s)", brand, dept) + case brand != "": + return brand + " deal" + case dept != "": + return dept + " deal" + } + + if desc := filter.CleanText(filter.Deref(item.Description)); desc != "" { + const max = 48 + if len(desc) > max { + return desc[:max-3] + "..." + } + return desc + } + + if item.ID != "" { + return "Deal " + item.ID + } + + return "Untitled deal" +} + func toDealJSON(item api.SavingItem) DealJSON { categories := item.Categories if categories == nil { diff --git a/internal/display/formatter_test.go b/internal/display/formatter_test.go index f0ab272..9aa3307 100644 --- a/internal/display/formatter_test.go +++ b/internal/display/formatter_test.go @@ -55,6 +55,41 @@ func TestPrintDeals_ContainsExpectedContent(t *testing.T) { assert.NotContains(t, output, "&") } +func TestPrintDeals_FallbackTitleFromBrandAndDepartment(t *testing.T) { + items := []api.SavingItem{ + { + ID: "fallback-1", + Title: nil, + Brand: ptr("Publix"), + Department: ptr("Meat"), + Categories: []string{"meat"}, + }, + } + + var buf bytes.Buffer + display.PrintDeals(&buf, items) + output := buf.String() + + assert.Contains(t, output, "Publix deal (Meat)") + assert.NotContains(t, output, "Unknown") +} + +func TestPrintDeals_FallbackTitleFromID(t *testing.T) { + items := []api.SavingItem{ + { + ID: "fallback-2", + Title: nil, + }, + } + + var buf bytes.Buffer + display.PrintDeals(&buf, items) + output := buf.String() + + assert.Contains(t, output, "Deal fallback-2") + assert.NotContains(t, output, "Unknown") +} + func TestPrintDealsJSON(t *testing.T) { var buf bytes.Buffer err := display.PrintDealsJSON(&buf, sampleDeals())