From 0275f945003732fc722e9f31b1d04f5e3c2caa7f Mon Sep 17 00:00:00 2001 From: TPV Date: Wed, 9 Sep 2026 09:52:15 -0700 Subject: [PATCH] added price drops to alerts/fixed race condition where late claimed docs got their tags wiped and sent to the api without tags --- src/monitor_utils/database.py | 14 +++++++++----- tests/test_new_listing_stamp.py | 29 ++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/monitor_utils/database.py b/src/monitor_utils/database.py index 19157f7..2ec4291 100644 --- a/src/monitor_utils/database.py +++ b/src/monitor_utils/database.py @@ -282,16 +282,19 @@ def extract_recorded_prices(existing_doc: dict | None) -> set[float]: def build_alert(monitor_name: str, document: dict, now: datetime) -> dict: """Build an alert referencing a product for consumer delivery. - The alert carries its own copy of the document's newListing flag: the - product doc is rewritten every cycle, so its flag flips False one cycle - after discovery, while a discovery alert may sit in a backed-up queue - longer than that. The alert's copy is written once and cannot rot. + The alert carries its own copy of the document's newListing flag and + priceBeforeDrop: the product doc is rewritten every cycle, so its flag + flips False one cycle after discovery and its priceBeforeDrop is + cleared by the next cycle that sees no drop, while an alert may sit in + a backed-up queue longer than that. The alert's copies are written once + and cannot rot. Args: monitor_name: Monitor that produced the product, used by the consumer to resolve the owning collection and thresholds. document: Product document supplying the id, pricing for the - priority calculation, and the stamped newListing flag. + priority calculation, and the stamped newListing flag and + priceBeforeDrop. now: Detection timestamp stored as createdAt. Returns: @@ -306,6 +309,7 @@ def build_alert(monitor_name: str, document: dict, now: datetime) -> dict: "attempts": 0, "createdAt": now, "newListing": document.get("newListing", False), + "priceBeforeDrop": document.get("priceBeforeDrop"), } diff --git a/tests/test_new_listing_stamp.py b/tests/test_new_listing_stamp.py index 838bb3f..7e6b57e 100644 --- a/tests/test_new_listing_stamp.py +++ b/tests/test_new_listing_stamp.py @@ -99,6 +99,32 @@ def test_existing_doc_with_new_price_stamps_false(): assert alert_doc(collection)["newListing"] is False +def test_drop_alert_carries_price_before_drop(): + """the doc's priceBeforeDrop is cleared by the next no-drop cycle, so the + alert keeps its own copy from emit time — a drop claimed late still knows + what it dropped from""" + + existing = {"_id": "p1", "currentPrice": 100.0, "pastPrices": []} + collection = FakeCollection([existing]) + run(collection, [_product(current=95.0)]) + + assert alert_doc(collection)["priceBeforeDrop"] == 100.0 + + +def test_non_drop_alert_carries_none(): + """a never-seen higher price alerts without a drop; discoveries have no + prior price at all""" + + existing = {"_id": "p1", "currentPrice": 100.0, "pastPrices": []} + collection = FakeCollection([existing]) + run(collection, [_product(current=120.0)]) + assert alert_doc(collection)["priceBeforeDrop"] is None + + discovery = FakeCollection() + run(discovery, [_product()]) + assert alert_doc(discovery)["priceBeforeDrop"] is None + + def test_existing_doc_with_known_price_emits_no_alert(): existing = { "_id": "p1", @@ -112,8 +138,9 @@ def test_existing_doc_with_known_price_emits_no_alert(): assert collection.alerts is None -def test_build_alert_defaults_missing_flag_to_false(): +def test_build_alert_defaults_missing_fields(): """Direct callers passing an unstamped document must not KeyError.""" alert = build_alert("bestbuy", {"_id": "p1"}, NOW) assert alert["newListing"] is False + assert alert["priceBeforeDrop"] is None -- 2.54.0