fix: avoid recording a duplicate price_history row on every scheduled check - #45
Open
Osamaali313 wants to merge 1 commit into
Open
fix: avoid recording a duplicate price_history row on every scheduled check#45Osamaali313 wants to merge 1 commit into
Osamaali313 wants to merge 1 commit into
Conversation
The dedup guard in the price scheduler compared the previous price with the freshly scraped one using a strict `!==`. `latestPrice.price` comes back from Postgres as a string (the column is DECIMAL), while `scrapedData.price.price` is a number, so `"19.99" !== 19.99` is always true and the guard never matched. As a result a new, identical price_history row was inserted on every scheduled check even when the price had not changed, and the "Price unchanged" branch was dead code. The surrounding price-drop and target-price checks already coerce with `parseFloat(String(latestPrice.price))`; apply the same coercion here so the comparison works on numbers.
There was a problem hiding this comment.
Pull request overview
This PR fixes the scheduler’s “record price only when changed” dedup guard by coercing the latest stored price_history.price (returned from pg as a string for DECIMAL/NUMERIC) to a number before comparing it to the freshly scraped numeric price. This prevents writing redundant price_history rows on every scheduled run when the price has not changed.
Changes:
- Coerce
latestPrice.pricewithparseFloat(String(...))before comparing toscrapedData.price.price. - Add an inline comment explaining the pg
DECIMALstring vs number mismatch and why coercion is needed.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+108
to
+112
| // Only record if price has changed or it's the first entry. | ||
| // latestPrice.price comes back from pg as a string (DECIMAL), while | ||
| // scrapedData.price.price is a number, so a raw !== is always true; | ||
| // coerce first (as the notification checks below already do). | ||
| if (!latestPrice || parseFloat(String(latestPrice.price)) !== scrapedData.price.price) { |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The scheduler's dedup guard never matches, so an identical
price_historyrow is written on every scheduled check even when the price hasn't changed.backend/src/services/scheduler.ts:price_history.priceisDECIMAL(10,2), andnode-postgresreturnsDECIMAL/NUMERICas a string (there's nopg.types.setTypeParserin the repo). SolatestPrice.priceis"19.99"whilescrapedData.price.priceis the number19.99.!==does no coercion, so"19.99" !== 19.99is alwaystrueand the guard passes every time.The intent is clearly to skip unchanged prices — the comment says so and the
elsebranch logsPrice unchanged for product ..., which is currently unreachable. The neighbouring price-drop and target-price checks already coerce withparseFloat(String(latestPrice.price)); this one comparison just missed it.Impact
At the default 1-hour
refresh_interval, each product accumulates ~24 identical rows per day.price_historygrows without bound, the "Price unchanged" optimization is dead code, and the price chart /price_countstat get padded with redundant points. (Notifications are unaffected — the drop/target checks useparseFloat, so a zero delta correctly fails the threshold.)Fix
Coerce the stored value before comparing, matching what the surrounding checks already do:
An actual price change still records a new row; an unchanged price now correctly hits the
elsebranch.Testing
The repo has no test harness, so I verified by tracing the runtime types:
getLatestreturns the raw pg row (price as string),PriceInfo.priceis typednumberand produced viaparseFloat, and there is no global decimal type parser. Happy to add a smallpriceChanged()helper + unit test if you'd like a harness set up.