Compare commits

..
18 Commits
Author SHA1 Message Date
TPV bd5df3a4c2 Merge pull request 'small cleanup of comments/added optional impersonate into run_bounded_cycle for monitors like bhphoto that need different clients than what we support/dont need to rotate' (#24) from development into main
Reviewed-on: #24
2026-09-30 01:56:40 +00:00
TPV 1b3f860ff3 Merge pull request 'updated tls profiles' (#23) from development into main
Reviewed-on: #23
2026-09-27 04:11:53 +00:00
TPV 7032f5b9d6 Merge pull request 'added get_response_json function to return json from aioweb' (#22) from development into main
Reviewed-on: #22
2026-09-26 03:00:57 +00:00
TPV 4729a62ae2 Merge pull request 'renamed monitor_name to monitor' (#21) from development into main
Reviewed-on: #21
2026-09-26 00:29:32 +00:00
TPV 2bf2c613a9 Merge pull request 'added asins to build_product_details' (#20) from development into main
Reviewed-on: #20
2026-09-10 13:39:02 +00:00
TPV 0fceafb364 Merge pull request 'added price drops to alerts/fixed race condition where late claimed docs got their tags wiped and sent to the api without tags' (#19) from development into main
Reviewed-on: #19
2026-09-09 16:52:30 +00:00
TPV 69484c5ae0 Merge pull request 'added asins for woot listings' (#18) from development into main
Reviewed-on: #18
2026-09-09 03:15:06 +00:00
TPV c9545ec9d8 Merge pull request 'updated the db name for dsql mongo' (#17) from development into main
Reviewed-on: #17
2026-08-31 00:59:22 +00:00
TPV 7f30a8a5cc Merge pull request 'updated the mongo_url to dsqls new url/small type hint fixes' (#16) from development into main
Reviewed-on: #16
2026-08-31 00:55:21 +00:00
TPV 87d751cd82 Merge pull request 'added optional mongo client for already configured monitors' (#15) from development into main
Reviewed-on: #15
2026-08-21 15:22:35 +00:00
TPV 2a5d649f90 Merge pull request 'added optional mongo url' (#14) from development into main
Reviewed-on: #14
2026-08-21 14:52:50 +00:00
TPV 04ce7b50f3 Merge pull request 'bug fix to avoid swallowing exceptions/failed pages inside fetch_all_pages/small refactor/added new listings' (#13) from development into main
Reviewed-on: #13
2026-08-13 20:23:00 +00:00
TPV cc46e3d409 Merge pull request 'removed retry_failed_pages from docstrings' (#12) from development into main
Reviewed-on: #12
2026-08-09 02:00:14 +00:00
TPV 5db82e3a59 Merge pull request 'removed failed page retrying' (#11) from development into main
Reviewed-on: #11
2026-08-08 20:51:09 +00:00
TPV 95af8a37e1 Merge pull request 'added no msrp prirority so they don't starve in queue' (#10) from development into main
Reviewed-on: #10
2026-08-04 20:54:19 +00:00
TPV 2ede6c77c7 Merge pull request 'use .get for has_msrp' (#9) from development into main
Reviewed-on: #9
2026-08-04 18:49:45 +00:00
TPV b34db200cc Merge pull request 'small typo fix' (#8) from development into main
Reviewed-on: #8
2026-08-04 17:07:56 +00:00
TPV 7c420059ee Merge pull request 'added has_msrp for no msrp based products(new lists)' (#7) from development into main
Reviewed-on: #7
2026-08-04 17:05:36 +00:00
4 changed files with 8 additions and 99 deletions

No files matched your search

+5 -16
View File
@@ -7,7 +7,6 @@ alert references for new products and price changes.
""" """
import logging import logging
import math
from dataclasses import asdict from dataclasses import asdict
from datetime import datetime, timezone from datetime import datetime, timezone
from functools import cache from functools import cache
@@ -319,13 +318,10 @@ def calculate_priority(document: dict) -> int:
Always recalculated from current and original price so the priority Always recalculated from current and original price so the priority
reflects the prices at alert time rather than any stale stored reflects the prices at alert time rather than any stale stored
percentage. Missing prices, zero original prices, non-finite prices, a percentage. Missing prices, zero original prices, and price increases
discount too large to represent, and price increases resolve to priority resolve to priority 0. A product with no MSRP has no percentage to rank
0, so one product's bad pricing can never raise out of the whole cycle (a on and takes NO_MSRP_PRIORITY instead, since a real 0 would leave it
retailer field like "Infinity" survives float() and used to crash the permanently behind every other alert in the claim query's sort. A new
monitor with an OverflowError). A product with no MSRP has no percentage
to rank on and takes NO_MSRP_PRIORITY instead, since a real 0 would leave
it permanently behind every other alert in the claim query's sort. A new
listing is floored at NEW_LISTING_PRIORITY for the same reason: it may listing is floored at NEW_LISTING_PRIORITY for the same reason: it may
carry any discount at all, and its percentage rank still wins when higher. carry any discount at all, and its percentage rank still wins when higher.
@@ -341,17 +337,10 @@ def calculate_priority(document: dict) -> int:
if current_price is None or not original_price: if current_price is None or not original_price:
return 0 return 0
if not (math.isfinite(current_price) and math.isfinite(original_price)):
return 0
if not document.get("hasMsrp", True): if not document.get("hasMsrp", True):
return NO_MSRP_PRIORITY return NO_MSRP_PRIORITY
discount = ((original_price - current_price) / original_price) * 100 percentage = int(((original_price - current_price) / original_price) * 100)
if not math.isfinite(discount):
return 0
percentage = int(discount)
priority = min(max(percentage // 10, 0), 10) priority = min(max(percentage // 10, 0), 10)
if document.get("newListing"): if document.get("newListing"):
return max(priority, NEW_LISTING_PRIORITY) return max(priority, NEW_LISTING_PRIORITY)
+3 -28
View File
@@ -1,6 +1,5 @@
import asyncio import asyncio
import logging import logging
import math
from collections.abc import Awaitable, Callable, Iterable from collections.abc import Awaitable, Callable, Iterable
from contextlib import asynccontextmanager, nullcontext from contextlib import asynccontextmanager, nullcontext
@@ -161,10 +160,7 @@ def build_product_details(
) -> Product | None: ) -> Product | None:
"""Build a Product from a Mongo document if it passes discount thresholds. """Build a Product from a Mongo document if it passes discount thresholds.
Skips documents with a missing or zero original price, a non-finite Skips documents with a missing or zero original price. Passing requires
price, or a discount too large to represent: a retailer field like
"Infinity" survives float(), and the percentage math used to raise
OverflowError out of every caller. Passing requires
meeting min_percentage, or min_price_drop when provided. The caller may meeting min_percentage, or min_price_drop when provided. The caller may
bypass both thresholds (new listings send at any discount for monitors bypass both thresholds (new listings send at any discount for monitors
that opt in); which alerts qualify is the caller's decision, not read that opt in); which alerts qualify is the caller's decision, not read
@@ -177,7 +173,7 @@ def build_product_details(
min_percentage: Minimum discount percentage to pass. min_percentage: Minimum discount percentage to pass.
min_price_drop: Optional minimum absolute price drop to pass. min_price_drop: Optional minimum absolute price drop to pass.
bypass_thresholds: Build the product even when it fails both bypass_thresholds: Build the product even when it fails both
thresholds. The missing- and non-finite-price skips still apply. thresholds. The missing-price skip still applies.
Returns: Returns:
The built Product, or None when skipped or below thresholds. The built Product, or None when skipped or below thresholds.
@@ -195,19 +191,6 @@ def build_product_details(
) )
return return
# Skipped on BOTH paths, not just the percentage one: an MSRP-less product
# would otherwise carry the infinite price into its payload. Only floats can
# be non-finite, so ints and other types keep their existing behaviour.
if any(
isinstance(price, float) and not math.isfinite(price)
for price in (current_price, original_price)
):
LOGGER.warning(
"non-finite price, skipping",
extra={"product_id": product_id},
)
return
percentage = 0 percentage = 0
# Defaulted, not indexed: documents written before the field existed have no # Defaulted, not indexed: documents written before the field existed have no
# hasMsrp at all, and stale ones that drop out of a monitor's feed are never # hasMsrp at all, and stale ones that drop out of a monitor's feed are never
@@ -215,15 +198,7 @@ def build_product_details(
# pre-existing threshold path rather than silently routing them as MSRP-less. # pre-existing threshold path rather than silently routing them as MSRP-less.
has_msrp = product.get("hasMsrp", True) has_msrp = product.get("hasMsrp", True)
if has_msrp: if has_msrp:
discount = ((original_price - current_price) / original_price) * 100 percentage = int(((original_price - current_price) / original_price) * 100)
if not math.isfinite(discount):
LOGGER.warning(
"discount not representable, skipping",
extra={"product_id": product_id},
)
return
percentage = int(discount)
price_drop = original_price - current_price price_drop = original_price - current_price
# A percentage code compounds onto the sale price, so an item can clear the # A percentage code compounds onto the sale price, so an item can clear the
# threshold on the combined discount even when the base discount would not. # threshold on the combined discount even when the base discount would not.
-17
View File
@@ -50,23 +50,6 @@ def test_broken_prices_still_outrank_nothing():
assert calculate_priority(document(None, 100.0, hasMsrp=False)) == 0 assert calculate_priority(document(None, 100.0, hasMsrp=False)) == 0
def test_non_finite_prices_resolve_to_zero_instead_of_raising():
"""float("Infinity") from a retailer field once crashed the whole monitor."""
inf, nan = float("inf"), float("nan")
assert calculate_priority(document(50.0, inf)) == 0
assert calculate_priority(document(inf, 100.0)) == 0
assert calculate_priority(document(-inf, 100.0)) == 0
assert calculate_priority(document(nan, 100.0)) == 0
assert calculate_priority(document(50.0, nan)) == 0
assert calculate_priority(document(50.0, inf, newListing=True)) == 0
def test_a_discount_too_large_to_represent_resolves_to_zero():
"""Finite prices can still overflow the ratio (a subnormal original)."""
assert calculate_priority(document(-1e308, 5e-324)) == 0
assert calculate_priority(document(1e308, 5e-324)) == 0
def test_new_listing_is_floored_not_buried(): def test_new_listing_is_floored_not_buried():
"""A low-discount new listing sends regardless, so it must not sort last.""" """A low-discount new listing sends regardless, so it must not sort last."""
assert ( assert (
-38
View File
@@ -203,41 +203,3 @@ def test_msrpless_doc_with_prices_builds_unthresholded():
product = build_product_details(doc, min_percentage=40) product = build_product_details(doc, min_percentage=40)
assert product is not None assert product is not None
assert product.pricing.has_msrp is False assert product.pricing.has_msrp is False
# ------------------------------------------------------------ non-finite prices
def test_infinite_price_skips_instead_of_raising():
"""Regression (2026-10-09 06:25Z): float("Infinity") from a retailer field
raised OverflowError here and stopped the consumer's API deal loop."""
inf = float("inf")
assert build_product_details(_doc(originalPrice=inf), min_percentage=40) is None
assert build_product_details(_doc(currentPrice=inf), min_percentage=40) is None
assert build_product_details(_doc(currentPrice=-inf), min_percentage=40) is None
def test_nan_price_skips_instead_of_raising():
nan = float("nan")
assert build_product_details(_doc(currentPrice=nan), min_percentage=40) is None
assert build_product_details(_doc(originalPrice=nan), min_percentage=40) is None
def test_msrpless_infinite_price_skips_rather_than_reaching_the_payload():
"""Without the skip the MSRP-less path would carry inf into the Product."""
doc = _doc(hasMsrp=False, currentPrice=float("inf"))
assert build_product_details(doc, min_percentage=40) is None
def test_bypass_does_not_rescue_a_non_finite_price():
doc = _doc(originalPrice=float("inf"))
assert build_product_details(doc, min_percentage=40, bypass_thresholds=True) is None
def test_unrepresentable_discount_skips_instead_of_raising():
"""Finite prices can still overflow the ratio (a subnormal original)."""
doc = _doc(currentPrice=-1e308, originalPrice=5e-324)
assert build_product_details(doc, min_percentage=40) is None