feat: Implement scheduled item price tracking with AI analysis, core backend services, and a dashboard item management modal.

This commit is contained in:
Michael committed 2025-11-23 22:48:01 +01:00
1 parent c9a5f91dd2
commit b5e3b44e5a
10 files changed
+57 -232

No files matched your search

+1 -7
View File
@@ -80,7 +80,6 @@ class ItemService:
return None, None
settings = {s.key: s.value for s in db.query(models.Settings).all()}
profile = item.notification_profile
item_data = {
"id": item.id,
@@ -90,7 +89,6 @@ class ItemService:
"current_price": item.current_price,
"in_stock": item.in_stock,
"target_price": item.target_price,
"notification_profile": profile.__dict__ if profile else None,
}
config = {
@@ -115,11 +113,7 @@ class ItemService:
if item.is_refreshing:
continue
interval = (
item.notification_profile.check_interval_minutes
if item.notification_profile and item.notification_profile.check_interval_minutes
else global_interval
)
interval = item.check_interval_minutes if item.check_interval_minutes else global_interval
if not item.last_checked:
due_items.append((item.id, interval, -1))
-89
View File
@@ -1,89 +0,0 @@
from fastapi import HTTPException
from sqlalchemy.orm import Session
from app import models, notification_sender, schemas
class NotificationService:
@staticmethod
def get_notification_profiles(db: Session):
return db.query(models.NotificationProfile).all()
@staticmethod
def create_notification_profile(db: Session, profile: schemas.NotificationProfileCreate):
db_profile = models.NotificationProfile(**profile.model_dump())
db.add(db_profile)
db.commit()
db.refresh(db_profile)
return db_profile
@staticmethod
def delete_notification_profile(db: Session, profile_id: int):
profile = db.query(models.NotificationProfile).filter(models.NotificationProfile.id == profile_id).first()
if not profile:
raise HTTPException(status_code=404, detail="Profile not found")
db.delete(profile)
db.commit()
db.delete(profile)
db.commit()
return {"ok": True}
@staticmethod
def update_notification_profile(db: Session, profile_id: int, profile_data: schemas.NotificationProfileUpdate):
profile = db.query(models.NotificationProfile).filter(models.NotificationProfile.id == profile_id).first()
if not profile:
raise HTTPException(status_code=404, detail="Profile not found")
for key, value in profile_data.model_dump().items():
setattr(profile, key, value)
db.commit()
db.refresh(profile)
return profile
@staticmethod
async def send_item_notifications(
item_data: dict,
price: float | None,
old_price: float | None,
in_stock: bool | None,
old_stock: bool | None,
):
profile = item_data["notification_profile"]
if not profile:
return
if profile["notify_on_price_drop"] and price is not None and old_price is not None:
if price < old_price:
drop_percent = ((old_price - price) / old_price) * 100
if drop_percent >= profile["price_drop_threshold_percent"]:
await notification_sender.send_notification(
[profile["apprise_url"]],
f"Price Drop Alert: {item_data['name']}",
f"Price dropped by {drop_percent:.1f}%! Now ${price} (was ${old_price})",
)
if (
profile["notify_on_target_price"]
and price is not None
and item_data["target_price"]
and price <= item_data["target_price"]
):
await notification_sender.send_notification(
[profile["apprise_url"]],
f"Target Price Alert: {item_data['name']}",
f"Price is ${price} (Target: ${item_data['target_price']})",
)
if (
profile["notify_on_stock_change"]
and in_stock is not None
and old_stock is not None
and in_stock != old_stock
):
status = "In Stock" if in_stock else "Out of Stock"
await notification_sender.send_notification(
[profile["apprise_url"]],
f"Stock Alert: {item_data['name']}",
f"Item is now {status}",
)
+1 -4
View File
@@ -8,7 +8,6 @@ from app import database, models
from app.ai_schema import AIExtractionMetadata, AIExtractionResponse
from app.services.ai_service import AIService
from app.services.item_service import ItemService
from app.services.notification_service import NotificationService
from app.services.scraper_service import ScraperService
logger = logging.getLogger(__name__)
@@ -134,9 +133,7 @@ async def process_item_check(item_id: int):
None, _update_db_result, item_id, extraction, metadata, thresholds, screenshot_path
)
await NotificationService.send_item_notifications(
item_data, extraction.price, old_price, extraction.in_stock, old_stock
)
except Exception as e:
logger.error(f"Error in process_item_check: {e}")