mirror of
https://github.com/R0m1k3/Priceflow.git
synced 2026-10-12 18:00:00 +02:00
- Remove duckduckgo-search dependency (unreliable results) - Add direct_search_service.py that scrapes sites' search pages directly - Add search_url and product_link_selector fields to SearchSite model - Add beautifulsoup4 and lxml for HTML parsing - Update schemas with new fields - Add database migration for new columns Direct search is more reliable as it queries each site's own search functionality instead of relying on external search engines.
99 lines
4.9 KiB
Python
99 lines
4.9 KiB
Python
from datetime import UTC, datetime
|
|
|
|
from sqlalchemy import Boolean, Column, DateTime, Float, ForeignKey, Integer, String, Text
|
|
from sqlalchemy.orm import relationship
|
|
|
|
from .database import Base
|
|
|
|
|
|
class NotificationProfile(Base):
|
|
__tablename__ = "notification_profiles"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
name = Column(String, unique=True, index=True)
|
|
apprise_url = Column(String)
|
|
notify_on_price_drop = Column(Boolean, default=True)
|
|
notify_on_target_price = Column(Boolean, default=True)
|
|
price_drop_threshold_percent = Column(Float, default=10.0)
|
|
notify_on_stock_change = Column(Boolean, default=True)
|
|
check_interval_minutes = Column(Integer, default=60)
|
|
|
|
items = relationship("Item", back_populates="notification_profile")
|
|
|
|
|
|
class Item(Base):
|
|
__tablename__ = "items"
|
|
|
|
id: int = Column(Integer, primary_key=True, index=True) # type: ignore
|
|
url: str = Column(String, index=True) # type: ignore
|
|
name: str = Column(String) # type: ignore
|
|
selector: str | None = Column(String, nullable=True) # type: ignore
|
|
target_price: float | None = Column(Float, nullable=True) # type: ignore
|
|
check_interval_minutes: int = Column(Integer, default=60) # type: ignore
|
|
|
|
# New fields
|
|
current_price: float | None = Column(Float, nullable=True) # type: ignore
|
|
in_stock: bool | None = Column(Boolean, nullable=True) # type: ignore
|
|
tags: str | None = Column(String, nullable=True) # type: ignore
|
|
description: str | None = Column(String, nullable=True) # type: ignore
|
|
|
|
# Confidence scores for latest extraction
|
|
current_price_confidence: float | None = Column(Float, nullable=True) # type: ignore
|
|
in_stock_confidence: float | None = Column(Float, nullable=True) # type: ignore
|
|
|
|
is_active: bool = Column(Boolean, default=True) # type: ignore
|
|
last_checked: datetime | None = Column(DateTime, nullable=True) # type: ignore
|
|
is_refreshing: bool = Column(Boolean, default=False) # type: ignore
|
|
last_error: str | None = Column(String, nullable=True) # type: ignore
|
|
|
|
notification_profile_id: int | None = Column(Integer, ForeignKey("notification_profiles.id"), nullable=True) # type: ignore
|
|
notification_profile = relationship("NotificationProfile", back_populates="items")
|
|
|
|
price_history = relationship("PriceHistory", back_populates="item", cascade="all, delete-orphan")
|
|
|
|
|
|
class PriceHistory(Base):
|
|
__tablename__ = "price_history"
|
|
|
|
id: int = Column(Integer, primary_key=True, index=True) # type: ignore
|
|
item_id: int = Column(Integer, ForeignKey("items.id")) # type: ignore
|
|
price: float = Column(Float) # type: ignore
|
|
timestamp: datetime = Column(DateTime, default=lambda: datetime.now(UTC)) # type: ignore
|
|
screenshot_path: str | None = Column(String, nullable=True) # type: ignore
|
|
|
|
# Confidence scores and AI metadata
|
|
price_confidence: float | None = Column(Float, nullable=True) # type: ignore
|
|
in_stock_confidence: float | None = Column(Float, nullable=True) # type: ignore
|
|
ai_model: str | None = Column(String, nullable=True) # type: ignore
|
|
ai_provider: str | None = Column(String, nullable=True) # type: ignore
|
|
prompt_version: str | None = Column(String, nullable=True) # type: ignore
|
|
repair_used: bool | None = Column(Boolean, nullable=True, default=False) # type: ignore
|
|
|
|
item = relationship("Item", back_populates="price_history")
|
|
|
|
|
|
class Settings(Base):
|
|
__tablename__ = "settings"
|
|
|
|
key = Column(String, primary_key=True, index=True)
|
|
value = Column(Text)
|
|
|
|
|
|
class SearchSite(Base):
|
|
"""Sites configurés pour la recherche de produits"""
|
|
__tablename__ = "search_sites"
|
|
|
|
id: int = Column(Integer, primary_key=True, index=True) # type: ignore
|
|
name: str = Column(String, nullable=False) # type: ignore # "Amazon France"
|
|
domain: str = Column(String, unique=True, nullable=False, index=True) # type: ignore # "amazon.fr"
|
|
logo_url: str | None = Column(String, nullable=True) # type: ignore
|
|
category: str | None = Column(String, nullable=True) # type: ignore # "Tech", "Déco", "Généraliste"
|
|
is_active: bool = Column(Boolean, default=True) # type: ignore
|
|
priority: int = Column(Integer, default=0) # type: ignore # Ordre d'affichage
|
|
requires_js: bool = Column(Boolean, default=False) # type: ignore # Force Browserless si True
|
|
price_selector: str | None = Column(String, nullable=True) # type: ignore # Sélecteur CSS pour le prix
|
|
search_url: str | None = Column(String, nullable=True) # type: ignore # URL de recherche avec {query} placeholder
|
|
product_link_selector: str | None = Column(String, nullable=True) # type: ignore # Sélecteur CSS pour les liens produits
|
|
created_at: datetime = Column(DateTime, default=lambda: datetime.now(UTC)) # type: ignore
|
|
updated_at: datetime = Column(DateTime, default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC)) # type: ignore
|