Assess this code;

import re
import logging
import time
from typing import List, Tuple, Optional, Dict
from datetime import datetime, timedelta
import requests
from bs4 import BeautifulSoup
from rapidfuzz import fuzz
from dataclasses import dataclass, asdict
from concurrent.futures import ThreadPoolExecutor, as_completed
import pandas as pd
import matplotlib.pyplot as plt
from tqdm import tqdm
import sqlite3
import argparse
import json
import os
import sys

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.StreamHandler(sys.stdout),
        logging.FileHandler("ebay_demand_assessor.log")
    ]
)
logger = logging.getLogger(__name__)

@dataclass
class ItemDetail:
    title: str
    price: float
    condition: str
    shipping: float
    url: str
    date_sold: datetime

class EbayDemandAssessor:
    BASE_URL = "https://www.ebay.com/sch/i.html"

    def __init__(self, max_retries: int = 3, timeout: int = 10, rate_limit: float = 1.0, db_path: str = 'ebay_demand.db'):
        self.max_retries = max_retries
        self.timeout = timeout
        self.rate_limit = rate_limit
        self.session = requests.Session()
        self.db_conn = sqlite3.connect(db_path)
        self._create_database()

    def _create_database(self):
        with self.db_conn:
            cursor = self.db_conn.cursor()
            cursor.execute('''
                CREATE TABLE IF NOT EXISTS demand_assessments (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    item TEXT,
                    days INTEGER,
                    sold_count INTEGER,
                    avg_price REAL,
                    min_price REAL,
                    max_price REAL,
                    avg_shipping REAL,
                    assessment_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
                )
            ''')

    def fuzzy_match(self, search_term: str, item_title: str, threshold: int = 80) -> bool:
        search_term_clean = re.sub(r'[^\w\s]', '', search_term.lower())
        item_title_clean = re.sub(r'[^\w\s]', '', item_title.lower())
        ratio = fuzz.partial_ratio(search_term_clean, item_title_clean)
        return ratio >= threshold

    def search_ebay(self, item: str, days: int) -> Optional[BeautifulSoup]:
        params = {
            "_nkw": item,
            "_sacat": "0",
            "LH_Sold": "1",
            "LH_Complete": "1",
            "_fsrp": "1",
            "LH_PrefLoc": "1",
            "_ipg": "200",
            "_sop": "13"
        }
        if days > 0:
            date = datetime.now() - timedelta(days=days)
            params["_fdate"] = date.strftime("%d%m%Y")

        for attempt in range(1, self.max_retries + 1):
            try:
                response = self.session.get(self.BASE_URL, params=params, timeout=self.timeout)
                response.raise_for_status()
                time.sleep(self.rate_limit)  # Respect rate limiting
                return BeautifulSoup(response.text, 'html.parser')
            except requests.RequestException as e:
                logger.warning(f"Attempt {attempt} failed: {e}")
                if attempt == self.max_retries:
                    logger.error(f"Failed to fetch data after {self.max_retries} attempts for item: {item}")
                    return None

    def extract_item_details(self, item: BeautifulSoup, search_term: str) -> Optional[ItemDetail]:
        try:
            title_elem = item.find('div', class_='s-item__title')
            title = title_elem.text.strip() if title_elem else "Unknown Title"
            if not self.fuzzy_match(search_term, title):
                return None

            price_elem = item.find('span', class_='s-item__price')
            price = self._parse_price(price_elem.text.strip() if price_elem else "$0")

            condition_elem = item.find('span', class_='SECONDARY_INFO')
            condition = condition_elem.text.strip() if condition_elem else "Unknown Condition"

            shipping_elem = item.find('span', class_='s-item__shipping s-item__logisticsCost')
            shipping = self._parse_shipping(shipping_elem.text.strip() if shipping_elem else "Free Shipping")

            url_elem = item.find('a', class_='s-item__link')
            url = url_elem['href'] if url_elem else ""

            date_elem = item.find('span', class_='s-item__ended-date')
            date_sold = self._parse_date_sold(date_elem.text.strip() if date_elem else "")

            return ItemDetail(title, price, condition, shipping, url, date_sold)
        except Exception as e:
            logger.warning(f"Error processing item details: {e}")
            return None

    def _parse_price(self, price_str: str) -> float:
        price = re.findall(r'[0-9.,]+', price_str.replace(',', ''))
        return float(price[0].replace(',', '')) if price else 0.0

    def _parse_shipping(self, shipping_str: str) -> float:
        if "Free" in shipping_str:
            return 0.0
        shipping = re.findall(r'[0-9.,]+', shipping_str.replace(',', ''))
        return float(shipping[0].replace(',', '')) if shipping else 0.0

    def _parse_date_sold(self, date_str: str) -> datetime:
        try:
            return datetime.strptime(date_str, "Sold  %b %d,  %Y")
        except ValueError:
            try:
                return datetime.strptime(date_str, "Sold  %b %d, %Y")
            except ValueError:
                logger.warning(f"Unknown date format: {date_str}")
                return datetime.now()

    def extract_data(self, soup: BeautifulSoup, search_term: str) -> Tuple[int, List[ItemDetail]]:
        items = soup.find_all('li', class_='s-item')
        item_details = []
        
        with ThreadPoolExecutor(max_workers=10) as executor:
            futures = {executor.submit(self.extract_item_details, item, search_term): item for item in items}
            for future in as_completed(futures):
                result = future.result()
                if result:
                    item_details.append(result)
                    
        return len(item_details), item_details

    def calculate_statistics(self, item_details: List[ItemDetail]) -> Dict[str, float]:
        if not item_details:
            return {"avg_price": 0.0, "min_price": 0.0, "max_price": 0.0, "avg_shipping": 0.0}

        prices = [item.price for item in item_details if item.price > 0]
        shipping_costs = [item.shipping for item in item_details]

        return {
            "avg_price": round(sum(prices) / len(prices), 2) if prices else 0.0,
            "min_price": round(min(prices), 2) if prices else 0.0,
            "max_price": round(max(prices), 2) if prices else 0.0,
            "avg_shipping": round(sum(shipping_costs) / len(shipping_costs), 2) if shipping_costs else 0.0
        }

    def assess_demand(self, item: str, days: int = 30) -> Optional[Dict]:
        soup = self.search_ebay(item, days)
        if not soup:
            return None

        sold_count, item_details = self.extract_data(soup, item)
        statistics = self.calculate_statistics(item_details)

        result = {
            'item': item,
            'days': days,
            'sold_count': sold_count,
            **statistics,
            'item_details': [asdict(detail) for detail in item_details]
        }
        self._save_to_database(result)
        return result

    def _save_to_database(self, result: Dict):
        with self.db_conn:
            cursor = self.db_conn.cursor()
            cursor.execute('''
                INSERT INTO demand_assessments 
                (item, days, sold_count, avg_price, min_price, max_price, avg_shipping) 
                VALUES (?, ?, ?, ?, ?, ?, ?)
            ''', (
                result['item'],
                result['days'],
                result['sold_count'],
                result['avg_price'],
                result['min_price'],
                result['max_price'],
                result['avg_shipping']
            ))

    def analyze_market(self, items: List[str], days: int = 30) -> List[Dict]:
        results = []
        with ThreadPoolExecutor(max_workers=5) as executor:
            future_to_item = {executor.submit(self.assess_demand, item, days): item for item in items}
            for future in tqdm(as_completed(future_to_item), total=len(items), desc="Analyzing items"):
                item = future_to_item[future]
                try:
                    result = future.result()
                    if result:
                        results.append(result)
                    else:
                        logger.warning(f"Failed to assess demand for item: {item}")
                except Exception as e:
                    logger.error(f"Error assessing item {item}: {e}")
        return results

    def visualize_results(self, results: List[Dict], output_dir: str):
        if not results:
            logger.warning("No results to visualize.")
            return

        df = pd.DataFrame(results)

        # Plot Sold Count
        plt.figure(figsize=(12, 6))
        plt.bar(df['item'], df['sold_count'], color='skyblue')
        plt.title('Number of Items Sold')
        plt.xlabel('Item')
        plt.ylabel('Sold Count')
        plt.xticks(rotation=45, ha='right')
        plt.tight_layout()
        sold_count_path = os.path.join(output_dir, 'sold_count.png')
        plt.savefig(sold_count_path)
        logger.info(f"Sold count visualization saved to {sold_count_path}")
        plt.close()

        # Plot Average Price
        plt.figure(figsize=(12, 6))
        plt.bar(df['item'], df['avg_price'], color='green')
        plt.title('Average Price of Items')
        plt.xlabel('Item')
        plt.ylabel('Average Price ($)')
        plt.xticks(rotation=45, ha='right')
        plt.tight_layout()
        avg_price_path = os.path.join(output_dir, 'avg_price.png')
        plt.savefig(avg_price_path)
        logger.info(f"Average price visualization saved to {avg_price_path}")
        plt.close()

    def export_results(self, results: List[Dict], output_format: str, output_dir: str):
        if output_format == 'csv':
            df = pd.DataFrame(results)
            csv_path = os.path.join(output_dir, 'results.csv')
            df.to_csv(csv_path, index=False)
            logger.info(f"Results exported to {csv_path}")
        elif output_format == 'json':
            json_path = os.path.join(output_dir, 'results.json')
            with open(json_path, 'w') as f:
                json.dump(results, f, indent=2, default=str)
            logger.info(f"Results exported to {json_path}")
        else:
            logger.warning(f"Unsupported output format: {output_format}")

    def close(self):
        self.db_conn.close()

def parse_arguments() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="eBay Demand Assessment Tool")
    parser.add_argument("items", nargs="+", help="Items to assess")
    parser.add_argument("--days", type=int, default=30, help="Number of days to analyze")
    parser.add_argument("--output", choices=['csv', 'json'], default='csv', help="Output format")
    parser.add_argument("--output-dir", default="output", help="Output directory")
    return parser.parse_args()

def main():
    args = parse_arguments()

    os.makedirs(args.output_dir, exist_ok=True)

    assessor = EbayDemandAssessor()
    try:
        market_analysis = assessor.analyze_market(args.items, args.days)
        assessor.visualize_results(market_analysis, args.output_dir)
        assessor.export_results(market_analysis, args.output, args.output_dir)
    except Exception as e:
        logger.error(f"An error occurred: {e}")
    finally:
        assessor.close()

if __name__ == "__main__":
    main()