feat(company-explorer): Initial Web UI & Backend with Enrichment Flow
This commit introduces the foundational elements for the new "Company Explorer" web application, marking a significant step away from the legacy Google Sheets / CLI system. Key changes include: - Project Structure: A new directory with separate (FastAPI) and (React/Vite) components. - Data Persistence: Migration from Google Sheets to a local SQLite database () using SQLAlchemy. - Core Utilities: Extraction and cleanup of essential helper functions (LLM wrappers, text utilities) into . - Backend Services: , , for AI-powered analysis, and logic. - Frontend UI: Basic React application with company table, import wizard, and dynamic inspector sidebar. - Docker Integration: Updated and for multi-stage builds and sideloading. - Deployment & Access: Integrated into central Nginx proxy and dashboard, accessible via . Lessons Learned & Fixed during development: - Frontend Asset Loading: Addressed issues with Vite's path and FastAPI's . - TypeScript Configuration: Added and . - Database Schema Evolution: Solved errors by forcing a new database file and correcting override. - Logging: Implemented robust file-based logging (). This new foundation provides a powerful and maintainable platform for future B2B robotics lead generation.
This commit is contained in:
103
company-explorer/backend/services/sync.py
Normal file
103
company-explorer/backend/services/sync.py
Normal file
@@ -0,0 +1,103 @@
|
||||
import os
|
||||
import logging
|
||||
from sqlalchemy.orm import Session
|
||||
from ..database import Company
|
||||
from ..interfaces import LeadData, TaskData, CRMRepository
|
||||
from ..repositories.mock import MockRepository
|
||||
from ..repositories.superoffice import SuperOfficeRepository
|
||||
from ..config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class CRMFactory:
|
||||
_instance: CRMRepository = None
|
||||
|
||||
@classmethod
|
||||
def get_repository(cls) -> CRMRepository:
|
||||
if cls._instance:
|
||||
return cls._instance
|
||||
|
||||
crm_type = os.getenv("CRM_TYPE", "MOCK").upper()
|
||||
|
||||
if crm_type == "SUPEROFFICE":
|
||||
# Load credentials securely from settings/env
|
||||
tenant = os.getenv("SO_TENANT_ID", "")
|
||||
token = os.getenv("SO_API_TOKEN", "")
|
||||
logger.info("Initializing SuperOffice Repository...")
|
||||
cls._instance = SuperOfficeRepository(tenant, token)
|
||||
else:
|
||||
logger.info("Initializing Mock Repository (Default)...")
|
||||
cls._instance = MockRepository()
|
||||
|
||||
return cls._instance
|
||||
|
||||
class SyncService:
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
self.repo = CRMFactory.get_repository()
|
||||
|
||||
def sync_company(self, company_id: int) -> dict:
|
||||
"""
|
||||
Pushes a local company to the external CRM.
|
||||
"""
|
||||
local_company = self.db.query(Company).filter(Company.id == company_id).first()
|
||||
if not local_company:
|
||||
return {"error": "Company not found"}
|
||||
|
||||
# 1. Map Data
|
||||
# Extract highest robotics potential score
|
||||
max_score = 0
|
||||
reason = ""
|
||||
for sig in local_company.signals:
|
||||
if sig.confidence > max_score:
|
||||
max_score = int(sig.confidence)
|
||||
reason = f"{sig.signal_type} ({sig.value})"
|
||||
|
||||
lead_data = LeadData(
|
||||
name=local_company.name,
|
||||
website=local_company.website,
|
||||
city=local_company.city,
|
||||
country=local_company.country,
|
||||
industry=local_company.industry_ai, # We suggest our AI industry
|
||||
robotics_potential_score=max_score,
|
||||
robotics_potential_reason=reason
|
||||
)
|
||||
|
||||
# 2. Check if already linked
|
||||
external_id = local_company.crm_id
|
||||
|
||||
# 3. Check if exists in CRM (by name) if not linked yet
|
||||
if not external_id:
|
||||
external_id = self.repo.find_company(local_company.name)
|
||||
|
||||
action = "none"
|
||||
if external_id:
|
||||
# Update
|
||||
success = self.repo.update_lead(external_id, lead_data)
|
||||
if success:
|
||||
action = "updated"
|
||||
# If we found it by search, link it locally
|
||||
if not local_company.crm_id:
|
||||
local_company.crm_id = external_id
|
||||
self.db.commit()
|
||||
else:
|
||||
# Create
|
||||
new_id = self.repo.create_lead(lead_data)
|
||||
if new_id:
|
||||
action = "created"
|
||||
local_company.crm_id = new_id
|
||||
self.db.commit()
|
||||
|
||||
# Create a task for the sales rep if high potential
|
||||
if max_score > 70:
|
||||
self.repo.create_task(new_id, TaskData(
|
||||
subject="🔥 Hot Robotics Lead",
|
||||
description=f"AI detected high potential ({max_score}%). Reason: {reason}. Please check website."
|
||||
))
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"action": action,
|
||||
"crm": self.repo.get_name(),
|
||||
"external_id": local_company.crm_id
|
||||
}
|
||||
Reference in New Issue
Block a user