Download database_manager.py from ZygmuntL/Leadfinder-Clean: direct link, hf CLI and curl.
- Browser
- Download file 15.8 kB
-
https://huggingface.co/ZygmuntL/Leadfinder-Clean/resolve/main/database_manager.py
- Command line
-
hf download hf://ZygmuntL/Leadfinder-Clean/database_manager.py
-
curl -L -o database_manager.py https://huggingface.co/ZygmuntL/Leadfinder-Clean/resolve/main/database_manager.py
15.8 kB
| 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: | |
| # Companies table | |
| 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) | |
| ) | |
| """) | |
| # Business categories table (for multi-category support) | |
| 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) | |
| ) | |
| """) | |
| # Contact collection status | |
| 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) | |
| ) | |
| """) | |
| # Datasets table | |
| 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 # Dataset already exists | |
| 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: | |
| # Insert company | |
| 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 | |
| # Add categories if multiple | |
| 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)) | |
| # Initialize contact collection status | |
| conn.execute(""" | |
| INSERT OR IGNORE INTO contact_collection_status (company_id) | |
| VALUES (?) | |
| """, (company_id,)) | |
| except sqlite3.IntegrityError: | |
| # Company already exists, skip | |
| continue | |
| # Update dataset company count | |
| 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 | |
| # Add updated_at to updates | |
| updates['updated_at'] = datetime.now().isoformat() | |
| # Build the SET clause and values | |
| 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: | |
| # Delete from contact_collection_status first (foreign key constraint) | |
| conn.execute("DELETE FROM contact_collection_status WHERE company_id = ?", (company_id,)) | |
| # Delete from company_categories | |
| conn.execute("DELETE FROM company_categories WHERE company_id = ?", (company_id,)) | |
| # Delete the company | |
| 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 | |
| # Build conditions based on search type | |
| 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: # both or all | |
| 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: | |
| # Get company IDs for this dataset | |
| cursor = conn.execute("SELECT id FROM companies WHERE dataset_name = ?", (dataset_name,)) | |
| company_ids = [row[0] for row in cursor.fetchall()] | |
| if company_ids: | |
| # Delete related records | |
| 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,)) | |
| # Delete dataset | |
| 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 | |
| # Basic stats | |
| 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()) | |
| # Contact collection stats | |
| 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()) | |
| # Companies needing contact collection | |
| 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} | |