fixed price drops/moved logging outside of init_monitor

This commit is contained in:
TPV committed 2026-07-13 18:57:23 -07:00
1 parent 7d070dec40
commit 00d3c12ee0
3 files changed
+98 -27

No files matched your search

+34 -4
View File
@@ -94,10 +94,12 @@ async def add_products_to_database(
):
"""Upsert products, tracking deduplicated price history and emitting alerts.
Rewrites current fields on each upsert. Prices are recorded in a
pastPrices array of price/date entries; a price already present in the
array is never recorded again, so each distinct price keeps only its
first-seen timestamp. New products and never-before-recorded prices
Rewrites current fields on each upsert. priceBeforeDrop is derived here,
not by monitors: it is set to the previous stored currentPrice when the new
price is lower (a drop) and cleared otherwise, so any price_before_drop on
the incoming product is ignored. Prices are recorded in a pastPrices array
of price/date entries; a price already present in the array is never
recorded again, so each distinct price keeps only its first-seen timestamp. New products and never-before-recorded prices
upsert an alert referencing the product; a price returning to a value
already in pastPrices emits nothing, so per-source price oscillation
(member vs non-member quotes for the same SKU) alerts once per distinct
@@ -133,6 +135,11 @@ async def add_products_to_database(
document["_id"] = doc_id
existing_doc = existing.get(doc_id)
new_price = document.get("currentPrice")
previous_price = existing_doc.get("currentPrice") if existing_doc else None
document["priceBeforeDrop"] = resolve_price_before_drop(
previous_price, new_price
)
recorded_prices = extract_recorded_prices(existing_doc)
never_seen_price = new_price is not None and new_price not in recorded_prices
set_on_insert = {"discoveredDate": now}
@@ -193,6 +200,29 @@ async def write_alert_ops(
raise
def resolve_price_before_drop(
previous_price: float | None, new_price: float | None
) -> float | None:
"""Return the pre-drop price when new_price is a drop from previous_price.
Owns price-drop detection so monitors do not: a drop is a new price strictly
below the previously stored one, and its priceBeforeDrop is that prior price.
Any other case (no prior price, unchanged, or a rise) is not a drop.
Args:
previous_price: The product's last stored currentPrice, or None when new.
new_price: The incoming currentPrice.
Returns:
previous_price on a drop, otherwise None.
"""
if previous_price is not None and new_price is not None and new_price < previous_price:
return previous_price
return None
def extract_recorded_prices(existing_doc: dict | None) -> set[float]:
"""Return the set of prices already recorded in a document's pastPrices.
+34 -23
View File
@@ -28,34 +28,19 @@ from monitor_utils.sessions import get_base_session, get_tls_session
LOGGER = logging.getLogger(__name__)
async def init_monitor(
monitor_name: str,
development_mode: DevelopmentMode,
production_proxy_path: str = "proxies.txt",
testing_proxy_path: str = "configs/proxies.txt",
logging_level=LOGGING_LEVEL,
) -> tuple[list[str] | dict[str, str], AsyncIOMotorCollection, AioProxies]:
"""Run shared monitor startup and return categories, collection, and proxies.
def setup_monitor_logging(monitor_name: str, logging_level=LOGGING_LEVEL):
"""Configure JSON logging for a monitor and quiet noisy libraries.
Configures JSON logging under the monitor's name, applies logger levels,
loads the monitor config and Mongo collection, and loads proxies from the
production or testing path based on development_mode. Async because the
config now lives in Mongo; call it from inside the monitor's event loop
(in main), not at module import.
Call once at module import, at global scope, before creating module loggers,
so logging is configured synchronously at startup rather than inside the
async init_monitor. Logs go to logs/latest.log with rotated history keyed by
monitor_name.
Args:
monitor_name: Monitor identifier used for logging and config lookup.
development_mode: Selects production or testing config, collection,
and proxy path.
production_proxy_path: Proxy file path used in production mode.
testing_proxy_path: Proxy file path used in testing mode.
logging_level: Level passed to setup_logging.
Returns:
Tuple of category names, Mongo collection, and loaded proxies.
monitor_name: Monitor name used as the rotated-history base.
logging_level: Root logging level.
"""
use_production = is_production(development_mode)
setup_logging(
name="logs/latest.log",
history_name=monitor_name,
@@ -63,6 +48,32 @@ async def init_monitor(
output="json",
)
set_logger_levels()
async def init_monitor(
monitor_name: str,
development_mode: DevelopmentMode,
production_proxy_path: str = "proxies.txt",
testing_proxy_path: str = "configs/proxies.txt",
) -> tuple[list[str] | dict[str, str], AsyncIOMotorCollection, AioProxies]:
"""Load a monitor's config, collection, and proxies for the mode.
Async because config now lives in Mongo; call it from inside the monitor's
event loop (in main), not at module import. Logging is configured separately
by setup_monitor_logging at module scope and is not touched here.
Args:
monitor_name: Monitor identifier used for config and collection lookup.
development_mode: Selects production or testing config, collection,
and proxy path.
production_proxy_path: Proxy file path used in production mode.
testing_proxy_path: Proxy file path used in testing mode.
Returns:
Tuple of category names, Mongo collection, and loaded proxies.
"""
use_production = is_production(development_mode)
category_names, _, collection = await setup_config(
monitor_name=monitor_name, development_mode=development_mode
)