Spaces:
Running
Running
AuthorBot Cursor commited on
Commit Β·
9109863
1
Parent(s): 1cd05b8
fix: cheapest/how-much answers use Buy button only, not full price grid
Browse files
.agent/PRICE_INTELLIGENCE_RULES.md
CHANGED
|
@@ -46,7 +46,8 @@ No new endpoints.
|
|
| 46 |
|
| 47 |
## 5. UI
|
| 48 |
|
| 49 |
-
- **Widget:** 3-column grid Platform | Price | Buy under bot bubble
|
|
|
|
| 50 |
- **Empty price:** display `See site`; Buy uses that platform's `listing_url`
|
| 51 |
- **Telegram:** stacked URL buttons (`Platform β $x.xx`), max 4
|
| 52 |
|
|
|
|
| 46 |
|
| 47 |
## 5. UI
|
| 48 |
|
| 49 |
+
- **Widget:** 3-column grid Platform | Price | Buy under bot bubble β **only** for list-all / compare turns
|
| 50 |
+
- **Single-winner turns** (cheapest, most expensive, how much, buy link for X): text + one Buy button; no grid
|
| 51 |
- **Empty price:** display `See site`; Buy uses that platform's `listing_url`
|
| 52 |
- **Telegram:** stacked URL buttons (`Platform β $x.xx`), max 4
|
| 53 |
|
app/services/price_catalog_service.py
CHANGED
|
@@ -306,13 +306,11 @@ class PriceCatalogService:
|
|
| 306 |
"platform_offers": [offer_to_platform_item(o) for o in offers],
|
| 307 |
}
|
| 308 |
best = min(priced, key=lambda o: o.price_cents or 10**9)
|
| 309 |
-
others = [o for o in offers if o.platform_id != best.platform_id]
|
| 310 |
text = (
|
| 311 |
f"The lowest listed price for {book_title} is {best.price_display} "
|
| 312 |
f"on {best.platform}."
|
| 313 |
)
|
| 314 |
-
|
| 315 |
-
platform_offers.extend(offer_to_platform_item(o) for o in others[:5])
|
| 316 |
return {
|
| 317 |
"text": text,
|
| 318 |
"links": [{
|
|
@@ -322,7 +320,7 @@ class PriceCatalogService:
|
|
| 322 |
"icon": "π",
|
| 323 |
}],
|
| 324 |
"has_links": True,
|
| 325 |
-
"platform_offers":
|
| 326 |
}
|
| 327 |
|
| 328 |
def _build_expensive_answer(self, offers: list[Offer], book_title: str) -> dict[str, Any]:
|
|
@@ -343,10 +341,7 @@ class PriceCatalogService:
|
|
| 343 |
"icon": "π",
|
| 344 |
}],
|
| 345 |
"has_links": True,
|
| 346 |
-
"platform_offers":
|
| 347 |
-
offer_to_platform_item(top, is_best=True),
|
| 348 |
-
*[offer_to_platform_item(o) for o in offers if o.platform_id != top.platform_id][:5],
|
| 349 |
-
],
|
| 350 |
}
|
| 351 |
|
| 352 |
def _build_compare_answer(
|
|
@@ -405,7 +400,7 @@ class PriceCatalogService:
|
|
| 405 |
"icon": "π",
|
| 406 |
}],
|
| 407 |
"has_links": True,
|
| 408 |
-
"platform_offers":
|
| 409 |
}
|
| 410 |
if target is None:
|
| 411 |
target = next((o for o in offers if o.is_source), offers[0])
|
|
@@ -427,7 +422,7 @@ class PriceCatalogService:
|
|
| 427 |
"icon": "π",
|
| 428 |
}],
|
| 429 |
"has_links": True,
|
| 430 |
-
"platform_offers":
|
| 431 |
}
|
| 432 |
|
| 433 |
def _build_how_much_answer(
|
|
@@ -462,5 +457,5 @@ class PriceCatalogService:
|
|
| 462 |
"icon": "π",
|
| 463 |
}],
|
| 464 |
"has_links": True,
|
| 465 |
-
"platform_offers":
|
| 466 |
}
|
|
|
|
| 306 |
"platform_offers": [offer_to_platform_item(o) for o in offers],
|
| 307 |
}
|
| 308 |
best = min(priced, key=lambda o: o.price_cents or 10**9)
|
|
|
|
| 309 |
text = (
|
| 310 |
f"The lowest listed price for {book_title} is {best.price_display} "
|
| 311 |
f"on {best.platform}."
|
| 312 |
)
|
| 313 |
+
# Single winner + Buy button only β no full Platform/Price/Buy grid.
|
|
|
|
| 314 |
return {
|
| 315 |
"text": text,
|
| 316 |
"links": [{
|
|
|
|
| 320 |
"icon": "π",
|
| 321 |
}],
|
| 322 |
"has_links": True,
|
| 323 |
+
"platform_offers": None,
|
| 324 |
}
|
| 325 |
|
| 326 |
def _build_expensive_answer(self, offers: list[Offer], book_title: str) -> dict[str, Any]:
|
|
|
|
| 341 |
"icon": "π",
|
| 342 |
}],
|
| 343 |
"has_links": True,
|
| 344 |
+
"platform_offers": None,
|
|
|
|
|
|
|
|
|
|
| 345 |
}
|
| 346 |
|
| 347 |
def _build_compare_answer(
|
|
|
|
| 400 |
"icon": "π",
|
| 401 |
}],
|
| 402 |
"has_links": True,
|
| 403 |
+
"platform_offers": None,
|
| 404 |
}
|
| 405 |
if target is None:
|
| 406 |
target = next((o for o in offers if o.is_source), offers[0])
|
|
|
|
| 422 |
"icon": "π",
|
| 423 |
}],
|
| 424 |
"has_links": True,
|
| 425 |
+
"platform_offers": None,
|
| 426 |
}
|
| 427 |
|
| 428 |
def _build_how_much_answer(
|
|
|
|
| 457 |
"icon": "π",
|
| 458 |
}],
|
| 459 |
"has_links": True,
|
| 460 |
+
"platform_offers": None,
|
| 461 |
}
|
tests/unit/test_price_catalog_service.py
CHANGED
|
@@ -93,8 +93,22 @@ async def test_build_cheapest_marks_is_best():
|
|
| 93 |
result = svc._build_cheapest_answer(offers, "Lethal Lines")
|
| 94 |
assert "Kobo" in result["text"]
|
| 95 |
assert "$9.99" in result["text"]
|
| 96 |
-
assert result["platform_offers"]
|
| 97 |
-
assert result["
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
|
| 99 |
|
| 100 |
@pytest.mark.asyncio
|
|
@@ -177,19 +191,6 @@ async def test_catalog_for_book_excludes_google_books():
|
|
| 177 |
assert [o.platform_id for o in offers] == ["amazon"]
|
| 178 |
|
| 179 |
|
| 180 |
-
@pytest.mark.asyncio
|
| 181 |
-
async def test_google_books_link_ask_redirects_to_retail():
|
| 182 |
-
svc = PriceCatalogService.__new__(PriceCatalogService)
|
| 183 |
-
offers = [
|
| 184 |
-
Offer("amazon", "Amazon", "$14.99", 1499, "https://a.com", True),
|
| 185 |
-
]
|
| 186 |
-
result = svc._build_platform_link_answer(offers, "Lethal Lines", ("google_books",))
|
| 187 |
-
assert "Google Books" in result["text"]
|
| 188 |
-
assert "Amazon" in result["text"]
|
| 189 |
-
assert result["links"][0]["url"] == "https://a.com"
|
| 190 |
-
assert result["platform_offers"][0]["platform_id"] == "amazon"
|
| 191 |
-
|
| 192 |
-
|
| 193 |
@pytest.mark.asyncio
|
| 194 |
async def test_handle_how_much_uses_source():
|
| 195 |
svc = PriceCatalogService(MagicMock())
|
|
@@ -201,3 +202,4 @@ async def test_handle_how_much_uses_source():
|
|
| 201 |
assert "$14.99" in result["text"]
|
| 202 |
assert "Amazon" in result["text"]
|
| 203 |
assert result["links"][0]["url"] == "https://a.com"
|
|
|
|
|
|
| 93 |
result = svc._build_cheapest_answer(offers, "Lethal Lines")
|
| 94 |
assert "Kobo" in result["text"]
|
| 95 |
assert "$9.99" in result["text"]
|
| 96 |
+
assert result["platform_offers"] is None
|
| 97 |
+
assert result["links"][0]["url"] == "https://k.com"
|
| 98 |
+
assert "Buy on Kobo" in result["links"][0]["label"]
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
@pytest.mark.asyncio
|
| 102 |
+
async def test_google_books_link_ask_redirects_to_retail():
|
| 103 |
+
svc = PriceCatalogService.__new__(PriceCatalogService)
|
| 104 |
+
offers = [
|
| 105 |
+
Offer("amazon", "Amazon", "$14.99", 1499, "https://a.com", True),
|
| 106 |
+
]
|
| 107 |
+
result = svc._build_platform_link_answer(offers, "Lethal Lines", ("google_books",))
|
| 108 |
+
assert "Google Books" in result["text"]
|
| 109 |
+
assert "Amazon" in result["text"]
|
| 110 |
+
assert result["links"][0]["url"] == "https://a.com"
|
| 111 |
+
assert result["platform_offers"] is None
|
| 112 |
|
| 113 |
|
| 114 |
@pytest.mark.asyncio
|
|
|
|
| 191 |
assert [o.platform_id for o in offers] == ["amazon"]
|
| 192 |
|
| 193 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
@pytest.mark.asyncio
|
| 195 |
async def test_handle_how_much_uses_source():
|
| 196 |
svc = PriceCatalogService(MagicMock())
|
|
|
|
| 202 |
assert "$14.99" in result["text"]
|
| 203 |
assert "Amazon" in result["text"]
|
| 204 |
assert result["links"][0]["url"] == "https://a.com"
|
| 205 |
+
assert result.get("platform_offers") is None
|