mirror of
https://github.com/R0m1k3/Priceflow.git
synced 2026-10-23 15:18:12 +02:00
feat: Implement scheduled item price tracking with AI analysis, core backend services, and a dashboard item management modal.
This commit is contained in:
1 parent
c9a5f91dd2
commit
b5e3b44e5a
10 files changed
+57
-232
No files matched your search
@@ -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))
|
||||
|
||||
@@ -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}",
|
||||
)
|
||||
@@ -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}")
|
||||
|
||||
Reference in new issue
Block a user