Wellfound-AI / core /excel_handler.py
Zoey7Web's picture
Upload 26 files
67c9c05 verified
Raw
History Blame Contribute Delete
4.39 kB
"""
Excel Handler - Read/write Excel files with checkpoint/resume support.
Handles:
- Loading Excel files with column detection
- Incremental save with checkpoint tracking
- Resume from last processed row
- Column mapping (Wellfound export format)
"""
import json
import os
import threading
from pathlib import Path
from typing import Optional, Dict, Any, List, Tuple
import pandas as pd
import openpyxl
from openpyxl.utils import get_column_letter
class ExcelHandler:
"""Handles Excel file operations with checkpoint support."""
# Wellfound export column mapping
COLUMN_MAP = {
"company_name": "Company Name",
"location": "Location",
"internal_link": "Internal Link",
"external_link": "External Link",
"valuation": "Valuation",
"rounds": "Rounds",
"series": "Series",
"total_raised": "Total Raised",
"location_apply": "location.apply",
"state_apply": "state.apply",
"region": "Region",
"contact": "contact",
}
def __init__(self, checkpoint_dir: str = "data/checkpoints"):
self.checkpoint_dir = Path(checkpoint_dir)
self.checkpoint_dir.mkdir(parents=True, exist_ok=True)
self._lock = threading.Lock()
def load(self, file_path: str) -> pd.DataFrame:
"""Load Excel file into DataFrame."""
df = pd.read_excel(file_path, engine="openpyxl")
# Ensure all expected columns exist
for col in self.COLUMN_MAP.values():
if col not in df.columns:
df[col] = None
return df
def get_checkpoint(self, file_hash: str) -> Optional[Dict[str, Any]]:
"""Get checkpoint state for a file."""
checkpoint_file = self.checkpoint_dir / f"{file_hash}.json"
if checkpoint_file.exists():
with open(checkpoint_file, "r", encoding="utf-8") as f:
return json.load(f)
return None
def save_checkpoint(self, file_hash: str, state: Dict[str, Any]):
"""Save checkpoint state."""
checkpoint_file = self.checkpoint_dir / f"{file_hash}.json"
with self._lock:
with open(checkpoint_file, "w", encoding="utf-8") as f:
json.dump(state, f, ensure_ascii=False, indent=2)
def save_row(self, file_path: str, row_idx: int, updates: Dict[str, Any]):
"""Save a single row's updates to Excel (incremental write)."""
with self._lock:
wb = openpyxl.load_workbook(file_path)
ws = wb.active
# Find column indices
col_indices = {}
for cell in ws[1]:
if cell.value:
col_indices[str(cell.value)] = cell.column
excel_row = row_idx + 2 # +2 for header and 0-index
for col_name, value in updates.items():
if col_name in col_indices:
col_idx = col_indices[col_name]
ws.cell(row=excel_row, column=col_idx, value=value)
wb.save(file_path)
wb.close()
def save_all_rows(self, file_path: str, df: pd.DataFrame):
"""Save entire DataFrame to Excel."""
with self._lock:
df.to_excel(file_path, index=False, engine="openpyxl")
def get_resume_index(self, file_hash: str) -> int:
"""Get the row index to resume from."""
checkpoint = self.get_checkpoint(file_hash)
if checkpoint and "last_processed_index" in checkpoint:
return checkpoint["last_processed_index"] + 1
return 0
def create_output_copy(self, source_path: str, output_dir: str = "data/results") -> str:
"""Create a working copy of the Excel file for incremental saves."""
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
source_name = Path(source_path).stem
output_path = output_dir / f"{source_name}_processed.xlsx"
df = self.load(source_path)
df.to_excel(str(output_path), index=False, engine="openpyxl")
return str(output_path)
@staticmethod
def compute_file_hash(file_path: str) -> str:
"""Compute a simple hash for the file."""
import hashlib
stat = os.stat(file_path)
name = Path(file_path).name
raw = f"{name}_{stat.st_size}_{stat.st_mtime}"
return hashlib.md5(raw.encode()).hexdigest()[:12]