AuthorBot Cursor commited on
Commit
4e43a29
·
1 Parent(s): 66a04fa

fix: don't re-show unlabeled price table when format is unknown

Browse files
app/services/intent.py CHANGED
@@ -103,6 +103,7 @@ _PRICE_INQUIRY_SIGNALS: tuple[str, ...] = (
103
  "ebook price", "ebook prices", "e-book price",
104
  "hardcover price", "hardcover prices", "paperback price", "paperback prices",
105
  "what about audio", "what about ebook", "what about hardcover",
 
106
  )
107
 
108
  _PURCHASE_SIGNALS: tuple[str, ...] = (
 
103
  "ebook price", "ebook prices", "e-book price",
104
  "hardcover price", "hardcover prices", "paperback price", "paperback prices",
105
  "what about audio", "what about ebook", "what about hardcover",
106
+ "is this price", "is that price", "price of hard",
107
  )
108
 
109
  _PURCHASE_SIGNALS: tuple[str, ...] = (
app/services/price_catalog_service.py CHANGED
@@ -133,7 +133,10 @@ def classify_price_query(message: str) -> PriceQuery:
133
  "ebook or", "e-book or", "paperback or",
134
  "which format", "what format", "are those", "are these",
135
  "is that the", "is that hardcover", "is that audiobook",
136
- "is that ebook", "format are", "those prices",
 
 
 
137
  )
138
  if any(s in q for s in clarify):
139
  return PriceQuery(kind="format_clarify", platform_ids=platforms, format_hint=fmt)
@@ -344,27 +347,36 @@ class PriceCatalogService:
344
  book_title: str,
345
  format_hint: str,
346
  ) -> dict[str, Any]:
347
- """Honest reply when a named format has no stored prices; show what is stored."""
 
 
 
 
348
  available = await self.catalog_for_book(author_id, book_id, format_hint="")
349
- formats = sorted({o.format for o in available if o.format})
 
350
  if formats:
351
  named = ", ".join(formats)
352
  text = (
353
  f"I don't have {format_hint} prices stored for {book_title} yet. "
354
  f"On file I have: {named}. Pick a Buy button for those."
355
  )
356
- else:
357
- text = (
358
- f"I don't have {format_hint} prices stored for {book_title} yet. "
359
- "I only list formats I have on file — nothing invented."
360
- )
 
 
 
 
 
 
361
  return {
362
  "text": text[:380],
363
  "links": [],
364
  "has_links": False,
365
- "platform_offers": [
366
- offer_to_platform_item(o) for o in available
367
- ] if available else None,
368
  }
369
 
370
  async def _build_empty_fallback(
@@ -418,17 +430,22 @@ class PriceCatalogService:
418
  f"Here’s each listed price for {book_title} with its format "
419
  f"({named}). I only show formats I have stored — nothing invented."
420
  )
421
- else:
422
- text = (
423
- f"Here are the listed prices I have for {book_title}. "
424
- "I don’t have separate format labels for these yet — "
425
- "check the retailer page for binding details."
426
- )
 
 
 
 
 
427
  return {
428
  "text": text[:380],
429
  "links": [],
430
  "has_links": False,
431
- "platform_offers": [offer_to_platform_item(o) for o in offers],
432
  }
433
 
434
  def _build_cheapest_answer(self, offers: list[Offer], book_title: str) -> dict[str, Any]:
 
133
  "ebook or", "e-book or", "paperback or",
134
  "which format", "what format", "are those", "are these",
135
  "is that the", "is that hardcover", "is that audiobook",
136
+ "is that ebook", "is that hard", "is this hard",
137
+ "is this price", "is that price", "is this the",
138
+ "price of hard", "price of audio", "price of e-book", "price of ebook",
139
+ "format are", "those prices",
140
  )
141
  if any(s in q for s in clarify):
142
  return PriceQuery(kind="format_clarify", platform_ids=platforms, format_hint=fmt)
 
347
  book_title: str,
348
  format_hint: str,
349
  ) -> dict[str, Any]:
350
+ """Honest reply when a named format has no stored prices.
351
+
352
+ Only attach platform_offers when other formats are labeled — never re-show
353
+ an unlabeled price grid that contradicts 'I don't have that format'.
354
+ """
355
  available = await self.catalog_for_book(author_id, book_id, format_hint="")
356
+ labeled = [o for o in available if o.format]
357
+ formats = sorted({o.format for o in labeled})
358
  if formats:
359
  named = ", ".join(formats)
360
  text = (
361
  f"I don't have {format_hint} prices stored for {book_title} yet. "
362
  f"On file I have: {named}. Pick a Buy button for those."
363
  )
364
+ return {
365
+ "text": text[:380],
366
+ "links": [],
367
+ "has_links": False,
368
+ "platform_offers": [offer_to_platform_item(o) for o in labeled],
369
+ }
370
+ text = (
371
+ f"I can't confirm whether that price is {format_hint} for {book_title} — "
372
+ "I don't have format labels stored for these listings yet. "
373
+ "Check the retailer page for binding or edition details."
374
+ )
375
  return {
376
  "text": text[:380],
377
  "links": [],
378
  "has_links": False,
379
+ "platform_offers": None,
 
 
380
  }
381
 
382
  async def _build_empty_fallback(
 
430
  f"Here’s each listed price for {book_title} with its format "
431
  f"({named}). I only show formats I have stored — nothing invented."
432
  )
433
+ return {
434
+ "text": text[:380],
435
+ "links": [],
436
+ "has_links": False,
437
+ "platform_offers": [offer_to_platform_item(o) for o in offers],
438
+ }
439
+ text = (
440
+ f"I can't confirm the format for these prices on {book_title} — "
441
+ "I don't have format labels stored yet. "
442
+ "Check the retailer page for hardcover, ebook, or audiobook details."
443
+ )
444
  return {
445
  "text": text[:380],
446
  "links": [],
447
  "has_links": False,
448
+ "platform_offers": None,
449
  }
450
 
451
  def _build_cheapest_answer(self, offers: list[Offer], book_title: str) -> dict[str, Any]:
tests/unit/test_price_catalog_service.py CHANGED
@@ -322,6 +322,60 @@ async def test_missing_audiobook_is_honest_and_shows_available():
322
  assert "Buy Book" not in (result.get("links") or [])
323
 
324
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325
  @pytest.mark.asyncio
326
  async def test_catalog_uses_book_format_when_no_format_prices():
327
  svc = PriceCatalogService(MagicMock())
 
322
  assert "Buy Book" not in (result.get("links") or [])
323
 
324
 
325
+ @pytest.mark.asyncio
326
+ async def test_missing_hardcover_unlabeled_hides_table():
327
+ svc = PriceCatalogService(MagicMock())
328
+
329
+ async def _catalog(_a, _b, *, format_hint=""):
330
+ if format_hint == "Hardcover":
331
+ return []
332
+ return [
333
+ Offer("amazon", "Amazon", "$35.00", 3500, "https://a.com", True, ""),
334
+ Offer("apple_books", "Apple Books", "$14.99", 1499, "https://apple.com", False, ""),
335
+ ]
336
+
337
+ svc.catalog_for_book = _catalog
338
+ result = await svc.handle(
339
+ "hardcover prices", "a1", "b1", "All We Say",
340
+ )
341
+ assert result["platform_offers"] is None
342
+ assert "hardcover" in result["text"].lower()
343
+ assert "can't confirm" in result["text"].lower() or "cannot confirm" in result["text"].lower()
344
+
345
+
346
+ def test_is_this_price_hard_copy_is_format_clarify():
347
+ pq = classify_price_query("is this price of hard copy")
348
+ assert pq.kind == "format_clarify"
349
+ assert pq.format_hint == "Hardcover"
350
+
351
+
352
+ @pytest.mark.asyncio
353
+ async def test_is_this_hard_copy_unlabeled_hides_table():
354
+ svc = PriceCatalogService(MagicMock())
355
+ svc.catalog_for_book = AsyncMock(return_value=[
356
+ Offer("amazon", "Amazon", "$35.00", 3500, "https://a.com", True, ""),
357
+ Offer("apple_books", "Apple Books", "$14.99", 1499, "https://apple.com", False, ""),
358
+ ])
359
+ result = await svc.handle(
360
+ "is this price of hard copy", "a1", "b1", "All We Say",
361
+ )
362
+ assert result["platform_offers"] is None
363
+ assert "format" in result["text"].lower()
364
+ assert _classify_by_rules("is this price of hard copy") == "price_inquiry"
365
+
366
+
367
+ @pytest.mark.asyncio
368
+ async def test_format_clarify_unlabeled_hides_table():
369
+ svc = PriceCatalogService.__new__(PriceCatalogService)
370
+ offers = [
371
+ Offer("amazon", "Amazon", "$35.00", 3500, "https://a.com", True, ""),
372
+ Offer("apple_books", "Apple Books", "$14.99", 1499, "https://apple.com", False, ""),
373
+ ]
374
+ result = svc._build_format_clarify_answer(offers, "All We Say")
375
+ assert result["platform_offers"] is None
376
+ assert "format" in result["text"].lower()
377
+
378
+
379
  @pytest.mark.asyncio
380
  async def test_catalog_uses_book_format_when_no_format_prices():
381
  svc = PriceCatalogService(MagicMock())