| import sqlite3 |
| import pandas as pd |
| from datetime import datetime |
| from pathlib import Path |
| import json |
| from typing import List, Dict, Any, Optional |
|
|
| class DatabaseManager: |
| def __init__(self, db_path: str = "business_database.db"): |
| self.db_path = db_path |
| self.init_database() |
| |
| def init_database(self): |
| """Initialize database with required tables""" |
| with sqlite3.connect(self.db_path) as conn: |
| |
| conn.execute(""" |
| CREATE TABLE IF NOT EXISTS companies ( |
| id INTEGER PRIMARY KEY AUTOINCREMENT, |
| name TEXT NOT NULL, |
| address TEXT, |
| phone TEXT, |
| website TEXT, |
| email TEXT, |
| rating TEXT, |
| reviews TEXT, |
| category TEXT, |
| hours TEXT, |
| price TEXT, |
| description TEXT, |
| dataset_name TEXT NOT NULL, |
| business_type TEXT, |
| location TEXT, |
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, |
| updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, |
| UNIQUE(name, address, dataset_name) |
| ) |
| """) |
| |
| |
| conn.execute(""" |
| CREATE TABLE IF NOT EXISTS company_categories ( |
| id INTEGER PRIMARY KEY AUTOINCREMENT, |
| company_id INTEGER, |
| category TEXT, |
| FOREIGN KEY (company_id) REFERENCES companies (id), |
| UNIQUE(company_id, category) |
| ) |
| """) |
| |
| |
| conn.execute(""" |
| CREATE TABLE IF NOT EXISTS contact_collection_status ( |
| id INTEGER PRIMARY KEY AUTOINCREMENT, |
| company_id INTEGER, |
| website_scraped BOOLEAN DEFAULT FALSE, |
| email_found BOOLEAN DEFAULT FALSE, |
| phone_found BOOLEAN DEFAULT FALSE, |
| last_scraped TIMESTAMP, |
| FOREIGN KEY (company_id) REFERENCES companies (id) |
| ) |
| """) |
| |
| |
| conn.execute(""" |
| CREATE TABLE IF NOT EXISTS datasets ( |
| id INTEGER PRIMARY KEY AUTOINCREMENT, |
| name TEXT UNIQUE NOT NULL, |
| description TEXT, |
| business_type TEXT, |
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, |
| total_companies INTEGER DEFAULT 0 |
| ) |
| """) |
| |
| conn.commit() |
| |
| def create_dataset(self, name: str, description: str = "", business_type: str = "") -> bool: |
| """Create a new dataset""" |
| try: |
| with sqlite3.connect(self.db_path) as conn: |
| conn.execute(""" |
| INSERT INTO datasets (name, description, business_type) |
| VALUES (?, ?, ?) |
| """, (name, description, business_type)) |
| conn.commit() |
| return True |
| except sqlite3.IntegrityError: |
| return False |
| |
| def get_datasets(self) -> List[Dict]: |
| """Get all datasets""" |
| with sqlite3.connect(self.db_path) as conn: |
| conn.row_factory = sqlite3.Row |
| cursor = conn.execute(""" |
| SELECT d.*, COUNT(c.id) as company_count |
| FROM datasets d |
| LEFT JOIN companies c ON d.name = c.dataset_name |
| GROUP BY d.id |
| ORDER BY d.created_at DESC |
| """) |
| return [dict(row) for row in cursor.fetchall()] |
| |
| def add_companies(self, companies: List[Dict], dataset_name: str, business_type: str = "") -> int: |
| """Add companies to database, return number of new companies added""" |
| added_count = 0 |
| |
| with sqlite3.connect(self.db_path) as conn: |
| for company in companies: |
| try: |
| |
| cursor = conn.execute(""" |
| INSERT INTO companies ( |
| name, address, phone, website, email, rating, reviews, |
| category, hours, price, description, dataset_name, business_type, location |
| ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) |
| """, ( |
| company.get('name', ''), |
| company.get('address', ''), |
| company.get('phone', ''), |
| company.get('website', ''), |
| company.get('email', ''), |
| company.get('rating', ''), |
| company.get('reviews', ''), |
| company.get('category', ''), |
| company.get('hours', ''), |
| company.get('price', ''), |
| company.get('description', ''), |
| dataset_name, |
| business_type, |
| company.get('location', '') |
| )) |
| |
| company_id = cursor.lastrowid |
| added_count += 1 |
| |
| |
| categories = company.get('categories', []) |
| if not categories and company.get('category'): |
| categories = [company.get('category')] |
| |
| for category in categories: |
| if category: |
| conn.execute(""" |
| INSERT OR IGNORE INTO company_categories (company_id, category) |
| VALUES (?, ?) |
| """, (company_id, category)) |
| |
| |
| conn.execute(""" |
| INSERT OR IGNORE INTO contact_collection_status (company_id) |
| VALUES (?) |
| """, (company_id,)) |
| |
| except sqlite3.IntegrityError: |
| |
| continue |
| |
| |
| conn.execute(""" |
| UPDATE datasets |
| SET total_companies = ( |
| SELECT COUNT(*) FROM companies WHERE dataset_name = ? |
| ) |
| WHERE name = ? |
| """, (dataset_name, dataset_name)) |
| |
| conn.commit() |
| |
| return added_count |
| |
| def get_companies(self, dataset_name: str = None, business_type: str = None) -> pd.DataFrame: |
| """Get companies from database""" |
| with sqlite3.connect(self.db_path) as conn: |
| query = """ |
| SELECT c.*, |
| GROUP_CONCAT(cc.category, '; ') as all_categories, |
| ccs.website_scraped, ccs.email_found, ccs.phone_found, ccs.last_scraped |
| FROM companies c |
| LEFT JOIN company_categories cc ON c.id = cc.company_id |
| LEFT JOIN contact_collection_status ccs ON c.id = ccs.company_id |
| """ |
| params = [] |
| conditions = [] |
| |
| if dataset_name: |
| conditions.append("c.dataset_name = ?") |
| params.append(dataset_name) |
| |
| if business_type: |
| conditions.append("c.business_type = ?") |
| params.append(business_type) |
| |
| if conditions: |
| query += " WHERE " + " AND ".join(conditions) |
| |
| query += " GROUP BY c.id ORDER BY c.created_at DESC" |
| |
| return pd.read_sql_query(query, conn, params=params) |
| |
| def update_company(self, company_id: int, updates: Dict) -> bool: |
| """Update company information""" |
| try: |
| with sqlite3.connect(self.db_path) as conn: |
| if not updates: |
| return True |
| |
| |
| updates['updated_at'] = datetime.now().isoformat() |
| |
| |
| set_clause = ", ".join([f"{key} = ?" for key in updates.keys()]) |
| values = list(updates.values()) + [company_id] |
| |
| query = f""" |
| UPDATE companies |
| SET {set_clause} |
| WHERE id = ? |
| """ |
| |
| conn.execute(query, values) |
| conn.commit() |
| return True |
| except Exception as e: |
| print(f"Error updating company {company_id}: {e}") |
| print(f"Updates: {updates}") |
| print(f"Values: {values}") |
| return False |
| |
| def delete_company(self, company_id: int) -> bool: |
| """Delete a company from database""" |
| try: |
| with sqlite3.connect(self.db_path) as conn: |
| |
| conn.execute("DELETE FROM contact_collection_status WHERE company_id = ?", (company_id,)) |
| |
| |
| conn.execute("DELETE FROM company_categories WHERE company_id = ?", (company_id,)) |
| |
| |
| conn.execute("DELETE FROM companies WHERE id = ?", (company_id,)) |
| conn.commit() |
| return True |
| except Exception as e: |
| print(f"Error deleting company {company_id}: {e}") |
| return False |
| |
| def mark_contact_collected(self, company_id: int, email_found: bool = False, phone_found: bool = False): |
| """Mark contact collection status""" |
| with sqlite3.connect(self.db_path) as conn: |
| conn.execute(""" |
| UPDATE contact_collection_status |
| SET website_scraped = TRUE, |
| email_found = ?, |
| phone_found = ?, |
| last_scraped = CURRENT_TIMESTAMP |
| WHERE company_id = ? |
| """, (email_found, phone_found, company_id)) |
| conn.commit() |
| |
| def get_companies_for_contact_collection(self, dataset_name: str = None, search_type: str = "both") -> List[Dict]: |
| """Get companies that need contact information collection - only those missing emails or phones""" |
| with sqlite3.connect(self.db_path) as conn: |
| conn.row_factory = sqlite3.Row |
| |
| |
| conditions = ["c.website IS NOT NULL AND c.website != ''"] |
| |
| if search_type == "emails_only": |
| conditions.append("(c.email IS NULL OR c.email = '')") |
| elif search_type == "phones_only": |
| conditions.append("(c.phone IS NULL OR c.phone = '')") |
| else: |
| conditions.append(""" |
| ( |
| (c.email IS NULL OR c.email = '') OR |
| (c.phone IS NULL OR c.phone = '') OR |
| (ccs.website_scraped IS NULL OR ccs.website_scraped = 0) |
| ) |
| """) |
| |
| query = f""" |
| SELECT c.*, ccs.website_scraped, ccs.email_found, ccs.phone_found |
| FROM companies c |
| LEFT JOIN contact_collection_status ccs ON c.id = ccs.company_id |
| WHERE {' AND '.join(conditions)} |
| """ |
| params = [] |
| |
| if dataset_name: |
| query += " AND c.dataset_name = ?" |
| params.append(dataset_name) |
| |
| query += " ORDER BY ccs.last_scraped ASC NULLS FIRST" |
| |
| cursor = conn.execute(query, params) |
| return [dict(row) for row in cursor.fetchall()] |
| |
| def delete_dataset(self, dataset_name: str) -> bool: |
| """Delete a dataset and all its companies""" |
| try: |
| with sqlite3.connect(self.db_path) as conn: |
| |
| cursor = conn.execute("SELECT id FROM companies WHERE dataset_name = ?", (dataset_name,)) |
| company_ids = [row[0] for row in cursor.fetchall()] |
| |
| if company_ids: |
| |
| conn.execute("DELETE FROM company_categories WHERE company_id IN ({})".format( |
| ','.join('?' * len(company_ids))), company_ids) |
| conn.execute("DELETE FROM contact_collection_status WHERE company_id IN ({})".format( |
| ','.join('?' * len(company_ids))), company_ids) |
| conn.execute("DELETE FROM companies WHERE dataset_name = ?", (dataset_name,)) |
| |
| |
| conn.execute("DELETE FROM datasets WHERE name = ?", (dataset_name,)) |
| conn.commit() |
| return True |
| except Exception as e: |
| print(f"Error deleting dataset: {e}") |
| return False |
| |
| def get_dataset_stats(self, dataset_name: str) -> Dict: |
| """Get statistics for a dataset""" |
| with sqlite3.connect(self.db_path) as conn: |
| conn.row_factory = sqlite3.Row |
| |
| |
| cursor = conn.execute(""" |
| SELECT |
| COUNT(*) as total_companies, |
| COUNT(CASE WHEN email IS NOT NULL AND email != '' THEN 1 END) as with_email, |
| COUNT(CASE WHEN phone IS NOT NULL AND phone != '' THEN 1 END) as with_phone, |
| COUNT(CASE WHEN website IS NOT NULL AND website != '' THEN 1 END) as with_website |
| FROM companies |
| WHERE dataset_name = ? |
| """, (dataset_name,)) |
| basic_stats = dict(cursor.fetchone()) |
| |
| |
| cursor = conn.execute(""" |
| SELECT |
| COUNT(*) as scraped_count, |
| COUNT(CASE WHEN email_found = 1 THEN 1 END) as email_found_count, |
| COUNT(CASE WHEN phone_found = 1 THEN 1 END) as phone_found_count |
| FROM companies c |
| JOIN contact_collection_status ccs ON c.id = ccs.company_id |
| WHERE c.dataset_name = ? AND ccs.website_scraped = 1 |
| """, (dataset_name,)) |
| contact_stats = dict(cursor.fetchone()) |
| |
| |
| cursor = conn.execute(""" |
| SELECT COUNT(*) as need_contact_collection |
| FROM companies c |
| LEFT JOIN contact_collection_status ccs ON c.id = ccs.company_id |
| WHERE c.dataset_name = ? |
| AND c.website IS NOT NULL AND c.website != '' |
| AND ( |
| (c.email IS NULL OR c.email = '') OR |
| (c.phone IS NULL OR c.phone = '') OR |
| (ccs.website_scraped IS NULL OR ccs.website_scraped = 0) |
| ) |
| """, (dataset_name,)) |
| need_collection = dict(cursor.fetchone()) |
| |
| return {**basic_stats, **contact_stats, **need_collection} |
|
|