mirror of
https://github.com/clucraft/PriceGhost.git
synced 2026-06-08 15:05:16 +02:00
Fix stock status false positives for in-stock items
- Remove overly generic pre-order phrases that caused false positives
("available in", "coming in", "arriving in" matched normal text)
- Add in-stock phrase priority check - "in stock", "add to cart",
"add to basket" now take precedence over pre-order detection
- Add Magento 2 stock status detection using stock classes and
add-to-cart buttons
- Bump version to 1.0.2
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
2c8843ed8a
commit
afa4f0c96a
7 changed files with 89 additions and 26 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
|
@ -5,6 +5,15 @@ All notable changes to PriceGhost will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.0.2] - 2026-01-23
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Stock status false positives** - Fixed overly aggressive pre-order detection that incorrectly marked in-stock items as out of stock. Pages with "in stock", "add to cart", or "add to basket" text now correctly prioritize these indicators
|
||||
- **Magento 2 stock detection** - Added proper stock status detection for Magento 2 sites, checking for stock classes and add-to-cart buttons
|
||||
|
||||
---
|
||||
|
||||
## [1.0.1] - 2026-01-23
|
||||
|
||||
### Added
|
||||
|
|
@ -105,5 +114,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
| Version | Date | Description |
|
||||
|---------|------|-------------|
|
||||
| 1.0.2 | 2026-01-23 | Fixed stock status false positives for in-stock items |
|
||||
| 1.0.1 | 2026-01-23 | Bug fixes, JS-rendered price support, pre-order detection |
|
||||
| 1.0.0 | 2026-01-23 | Initial public release |
|
||||
|
|
|
|||
4
backend/package-lock.json
generated
4
backend/package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "priceghost-backend",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "priceghost-backend",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.2",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.24.0",
|
||||
"axios": "^1.6.0",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "priceghost-backend",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.2",
|
||||
"description": "PriceGhost price tracking API",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -742,9 +742,36 @@ const siteScrapers: SiteScraper[] = [
|
|||
$('.fotorama__stage img').first().attr('src') ||
|
||||
null;
|
||||
|
||||
// Stock status detection for Magento 2
|
||||
let stockStatus: StockStatus = 'unknown';
|
||||
|
||||
// Check for Magento's stock status elements
|
||||
const stockElement = $('.product-info-stock-sku .stock').first();
|
||||
const stockText = stockElement.text().toLowerCase();
|
||||
const stockClass = stockElement.attr('class')?.toLowerCase() || '';
|
||||
|
||||
// Magento uses "available" class for in-stock items
|
||||
if (stockClass.includes('available') || stockText.includes('in stock')) {
|
||||
stockStatus = 'in_stock';
|
||||
} else if (stockClass.includes('unavailable') || stockText.includes('out of stock')) {
|
||||
stockStatus = 'out_of_stock';
|
||||
}
|
||||
|
||||
// Also check for add to cart button as backup
|
||||
if (stockStatus === 'unknown') {
|
||||
const addToCartBtn = $('#product-addtocart-button, button.tocart, button[title="Add to Cart"], button[title="Add to Basket"]').length > 0;
|
||||
const outOfStockMsg = $('.out-of-stock, .unavailable, [class*="outofstock"]').length > 0;
|
||||
|
||||
if (addToCartBtn && !outOfStockMsg) {
|
||||
stockStatus = 'in_stock';
|
||||
} else if (outOfStockMsg) {
|
||||
stockStatus = 'out_of_stock';
|
||||
}
|
||||
}
|
||||
|
||||
// Only return if we found a price (indicates it's likely a Magento site)
|
||||
if (price) {
|
||||
return { name, price, imageUrl };
|
||||
return { name, price, imageUrl, stockStatus };
|
||||
}
|
||||
return {};
|
||||
},
|
||||
|
|
@ -1259,13 +1286,11 @@ function extractGenericStockStatus($: CheerioAPI): StockStatus {
|
|||
|
||||
// Check for pre-order / coming soon indicators BEFORE checking add to cart
|
||||
// Some sites show a "Pre-order" button that looks like add to cart
|
||||
// NOTE: Be careful with generic phrases - "available in" matches "available in stock"!
|
||||
const preOrderComingSoonPhrases = [
|
||||
'coming soon',
|
||||
'coming in',
|
||||
'available soon',
|
||||
'available in',
|
||||
'arriving soon',
|
||||
'arriving in',
|
||||
'releases on',
|
||||
'release date',
|
||||
'expected release',
|
||||
|
|
@ -1284,25 +1309,53 @@ function extractGenericStockStatus($: CheerioAPI): StockStatus {
|
|||
'join waitlist',
|
||||
'not yet released',
|
||||
'not yet available',
|
||||
'coming this',
|
||||
'coming next',
|
||||
// Specific future availability phrases (avoid generic "available in" which matches "available in stock")
|
||||
'available starting',
|
||||
'available from', // Usually followed by a date
|
||||
'ships in', // Usually indicates future shipping
|
||||
'expected to ship',
|
||||
'estimated arrival',
|
||||
];
|
||||
|
||||
for (const phrase of preOrderComingSoonPhrases) {
|
||||
if (textToCheck.includes(phrase)) {
|
||||
// Double check it's not just a section about pre-orders in general
|
||||
// by looking for the phrase near price/product context
|
||||
const phraseIndex = textToCheck.indexOf(phrase);
|
||||
const contextStart = Math.max(0, phraseIndex - 200);
|
||||
const contextEnd = Math.min(textToCheck.length, phraseIndex + 200);
|
||||
const context = textToCheck.substring(contextStart, contextEnd);
|
||||
// Phrases that indicate the product is NOT coming soon (should not trigger out of stock)
|
||||
const inStockPhrases = [
|
||||
'in stock',
|
||||
'add to cart',
|
||||
'add to basket',
|
||||
'buy now',
|
||||
'available now',
|
||||
'ships today',
|
||||
'ships immediately',
|
||||
'ready to ship',
|
||||
];
|
||||
|
||||
// If the context mentions price, buy, cart, or product, it's likely about this product
|
||||
if (context.includes('$') || context.includes('price') ||
|
||||
context.includes('buy') || context.includes('cart') ||
|
||||
context.includes('order') || context.includes('purchase')) {
|
||||
return 'out_of_stock';
|
||||
// First, check if the page has strong in-stock indicators
|
||||
// If so, don't let pre-order phrase matching override it
|
||||
let hasInStockIndicator = false;
|
||||
for (const phrase of inStockPhrases) {
|
||||
if (textToCheck.includes(phrase)) {
|
||||
hasInStockIndicator = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Only check for pre-order/coming soon if we don't have a clear in-stock indicator
|
||||
if (!hasInStockIndicator) {
|
||||
for (const phrase of preOrderComingSoonPhrases) {
|
||||
if (textToCheck.includes(phrase)) {
|
||||
// Double check it's not just a section about pre-orders in general
|
||||
// by looking for the phrase near price/product context
|
||||
const phraseIndex = textToCheck.indexOf(phrase);
|
||||
const contextStart = Math.max(0, phraseIndex - 200);
|
||||
const contextEnd = Math.min(textToCheck.length, phraseIndex + 200);
|
||||
const context = textToCheck.substring(contextStart, contextEnd);
|
||||
|
||||
// If the context mentions price, buy, cart, or product, it's likely about this product
|
||||
if (context.includes('$') || context.includes('price') ||
|
||||
context.includes('buy') || context.includes('cart') ||
|
||||
context.includes('order') || context.includes('purchase')) {
|
||||
return 'out_of_stock';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "priceghost-frontend",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "priceghost-frontend",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.2",
|
||||
"dependencies": {
|
||||
"axios": "^1.6.0",
|
||||
"react": "^18.2.0",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "priceghost-frontend",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.2",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"releaseDate": "2026-01-23"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue