Skip to content

Restock & Price detection - Fix decimal-comma prices ("39,99") being read as 3999 - #4494

Open
jaime-king wants to merge 1 commit into
dgtlmoon:masterfrom
jaime-king:restock-price-decimal-comma
Open

jaime-king wants to merge 1 commit into
dgtlmoon:masterfrom
jaime-king:restock-price-decimal-comma

Conversation

@jaime-king

@jaime-king jaime-king commented Sep 25, 2026 •

Copy link
Copy Markdown

Fixes #4493

Root cause

Restock.parse_currency() (processors/restock_diff/__init__.py:19) already normalises both separator styles: "39,99" → 39.99, "1.299,00" → 1299.0, "1,299.00" → 1299.0. But the extractors never let it see the original value. Each one strips everything except [\d.] first and calls float() directly:

  • processors/restock_diff/processor.py:50 and :54, in _deduplicate_prices() (extruct path, microdata and JSON-LD)
  • processors/restock_diff/pure_python_extractor.py:244, the JSON-LD string fallback in query_price_availability()
  • processors/restock_diff/pure_python_extractor.py:138, the microdata text-node path

So a decimal comma is deleted before parsing: "39,99" → "3999" → 3999.0. Detection still fires because every price is off by the same factor. But the stored price, the watch overview, {{restock.price}} / {{restock.previous_price}} in notifications, and the absolute price_change_min / price_change_max limits are all 100× too high for any shop that writes prices this way. That's normal for EUR, e.g. every EUR-region Steam store page (example in #4493).

The fix

Route all four sites through Restock().parse_currency() instead of float(re.sub(...)). Values it can't parse are skipped rather than added as None.

Routing through parse_currency() would change one case that the old strip-first code handled correctly by accident: a value with only thousands commas, e.g. "1,299". parse_currency() reads a lone comma as a decimal separator, which would give 1.299. So parse_currency() now treats commas followed by exact three-digit groups (\d{1,3}(,\d{3})+) as thousands separators, and "1,299" stays 1299.0.

Source change: +14 / −8 across the three files; the rest is the test.

How I verified

Run inside the published dgtlmoon/changedetection.io:0.60.7 image, with this branch (based on d789fe3) copied in:

  • New tests/unit/test_restock_logic.py::TestDiffBuilder::test_price_decimal_comma covers 39,99, 12.56, 1.299,00, 1,299.00, 1,299, € 39,99, $159 and 159. Each goes through parse_currency(), extruct microdata, extruct JSON-LD, pure-Python JSON-LD, and pure-Python microdata text.
  • Differential: with the source files reverted to master, the new test fails (AssertionError: microdata '39,99'). With the fix it passes.
  • Regression: tests/test_restock_itemprop.py tests/test_automatic_follow_ldjson_price.py tests/test_notification_restock_token.py tests/unit/test_restock_logic.py tests/unit/test_restock_deal_filter.py: 22 passed, no failures.
  • End to end, before the fix: a live EUR Steam watch records 3999.0 for a content="39,99" price. A bol.com watch (JSON-LD "price":"12.56") records 12.56 and is unaffected.

What I did not run: the full run_basic_tests.sh / browser-fetcher suites. The change is confined to price parsing in restock_diff, and the CI run on this PR covers the rest.

Known limitations (unchanged by this PR)

These are genuinely ambiguous without knowing the page's locale; both behave exactly as on master today:

  • A dot as the only thousands separator with no decimals ("1.299") is read as 1.299.
  • A comma followed by exactly three digits ("0,299", "12,500") is read as thousands (299, 12500).

Disclosure: the change, its test and this description were prepared with an AI agent (Claude Code).

🤖 Generated with Claude Code

…read as 3999 (dgtlmoon#4493)

The extractors stripped everything but [\d.] before converting, so the
decimal comma was thrown away before Restock.parse_currency(), which
already understands it, ever saw the value. Route the values through
parse_currency() instead, and teach it that a lone "1,299" is a
thousands separator so that case does not regress.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Restock & Price detection - decimal-comma prices ("39,99") are read as 3999, the comma is stripped before parse_currency()

1 participant