diff --git a/changedetectionio/processors/restock_diff/__init__.py b/changedetectionio/processors/restock_diff/__init__.py index a2402e99c..6fcf19a32 100644 --- a/changedetectionio/processors/restock_diff/__init__.py +++ b/changedetectionio/processors/restock_diff/__init__.py @@ -16,6 +16,11 @@ _price_re = re.compile(r"Price:\s*(\d+(?:\.\d+)?)", re.IGNORECASE) class Restock(dict): + # The one place price text becomes a number - keep it that way, don't strip to [^\d.] and float() + # at the call site, that turns "39,99" into 3999 (#4493). Used by: + # processor.py _deduplicate_prices() (itemprop/JSON-LD prices) + # pure_python_extractor.py MicrodataExtractor, query_price_availability() + # plugins/llm_restock.py get_itemprop_availability_override() (price the LLM returns as a string) def parse_currency(self, raw_value: str) -> Union[float, None]: # Clean and standardize the value (ie 1,400.00 should be 1400.00), even better would be store the whole thing as an integer. standardized_value = raw_value diff --git a/changedetectionio/processors/restock_diff/plugins/llm_restock.py b/changedetectionio/processors/restock_diff/plugins/llm_restock.py index ba90500b0..74b617d38 100644 --- a/changedetectionio/processors/restock_diff/plugins/llm_restock.py +++ b/changedetectionio/processors/restock_diff/plugins/llm_restock.py @@ -335,9 +335,10 @@ def get_itemprop_availability_override(content, fetcher_name, fetcher_instance, try: if isinstance(price, str): # The model sometimes returns the price as page text ("1.299,95 €"), stripping - # everything but digits and dots would turn that into 1.29995 - from price_parser import Price - price = Price.fromstring(price).amount_float + # everything but digits and dots would turn that into 1.29995. Same parser as the + # structured-data extractors, see Restock.parse_currency() for every caller. + from changedetectionio.processors.restock_diff import Restock + price = Restock().parse_currency(price) else: price = float(price) except (ValueError, TypeError): diff --git a/changedetectionio/processors/restock_diff/pure_python_extractor.py b/changedetectionio/processors/restock_diff/pure_python_extractor.py index d7d7cf6b1..a54587270 100644 --- a/changedetectionio/processors/restock_diff/pure_python_extractor.py +++ b/changedetectionio/processors/restock_diff/pure_python_extractor.py @@ -136,6 +136,7 @@ class MicrodataExtractor(HTMLParser): if self.current_itemprop == 'price': # Try to extract numeric price from text try: + # Not [^\d.] + float(), that breaks "39,99" (#4493), see Restock.parse_currency() for every caller price = Restock().parse_currency(data.strip()) if price is not None: self.microdata['price'] = price @@ -240,7 +241,7 @@ def query_price_availability(extracted_data): if isinstance(price_val, (int, float)): result['price'] = float(price_val) elif isinstance(price_val, str): - # Extract numeric value from string + # Extract numeric value from string, see Restock.parse_currency() for every caller try: result['price'] = Restock().parse_currency(price_val) except ValueError: diff --git a/changedetectionio/tests/llm/test_llm_restock_plugin.py b/changedetectionio/tests/llm/test_llm_restock_plugin.py index c0bee2382..46b3eb0d9 100644 --- a/changedetectionio/tests/llm/test_llm_restock_plugin.py +++ b/changedetectionio/tests/llm/test_llm_restock_plugin.py @@ -199,11 +199,10 @@ class TestLLMRestockPluginExtraction: ('1,299.95', 1299.95), ('10 000 Kč', 10000.0), ('10 000,50', 10000.5), - ('£1.299', 1299.0), ('$127.99', 127.99), ]) def test_handles_locale_formatted_string_price(self, price_str, expected): - """A string price in page format must not be mangled by stripping its separators.""" + """A string price in page format must not be mangled by stripping its separators (same as #4493).""" llm_json = json.dumps({'price': price_str, 'currency': None, 'availability': 'instock'}) result = _call_plugin( f'{price_str}',