feat: Integrate Folk CRM API for investor synchronization and compatibility scoring

This commit is contained in:
bolade
2025-10-08 19:21:46 +01:00
parent cefe89bb67
commit 64f9364fcd
9 changed files with 1055 additions and 14 deletions
+39 -8
View File
@@ -1,8 +1,8 @@
import os
from typing import List
from typing import List, Optional
from db.db import DATABASE_URL, get_db
from db.models import FundTable, InvestorTable
from db.models import FundTable, InvestorTable, ProjectTable
from langchain import hub
from langchain_community.agent_toolkits import SQLDatabaseToolkit
from langchain_community.utilities import SQLDatabase
@@ -16,6 +16,8 @@ from schemas.router_schemas import (
)
from sqlalchemy.orm import selectinload
from services.compatibility_score import calculate_project_investor_compatibility
# Connect to SQLite
prompt_template = hub.pull("langchain-ai/sql-agent-system-prompt")
db = SQLDatabase.from_uri(DATABASE_URL)
@@ -44,8 +46,15 @@ class QueryProcessor:
prompt=system_message_updated,
)
def process_query(self, question: str) -> PaginatedResponse[InvestmentResponse]:
"""Process a query using the LLM and return investment response data."""
def process_query(
self, question: str, project_id: Optional[int] = None
) -> PaginatedResponse[InvestmentResponse]:
"""Process a query using the LLM and return investment response data.
Args:
question: The natural language query to process
project_id: Optional project ID for compatibility scoring
"""
# Let the LLM handle all database interactions and filtering to get fund IDs
response = self.agent.invoke(
{"messages": [("user", question)]},
@@ -60,7 +69,7 @@ class QueryProcessor:
fund_ids = self._extract_fund_ids_from_response(ai_response)
# Fetch full fund data with investor relationships using the IDs
return self._fetch_funds_by_ids(fund_ids)
return self._fetch_funds_by_ids(fund_ids, project_id)
def _extract_fund_ids_from_response(self, ai_response: str) -> List[int]:
"""Extract fund IDs from AI response."""
@@ -85,10 +94,15 @@ class QueryProcessor:
return fund_ids
def _fetch_funds_by_ids(
self, fund_ids: List[int]
self, fund_ids: List[int], project_id: Optional[int] = None
) -> PaginatedResponse[InvestmentResponse]:
"""Fetch funds with all their relationships from the database using fund IDs.
Constructs response similar to read_investors but starting from funds."""
Constructs response similar to read_investors but starting from funds.
Args:
fund_ids: List of fund IDs to fetch
project_id: Optional project ID for compatibility scoring
"""
if not fund_ids:
return PaginatedResponse(
items=[],
@@ -102,6 +116,16 @@ class QueryProcessor:
db_session = next(get_db())
try:
# Load project if project_id provided
project = None
if project_id is not None:
project = (
db_session.query(ProjectTable)
.options(selectinload(ProjectTable.sector))
.filter(ProjectTable.id == project_id)
.first()
)
# Query funds with all necessary relationships loaded
funds = (
db_session.query(FundTable)
@@ -127,6 +151,13 @@ class QueryProcessor:
for fund in funds:
investor = fund.investor
# Calculate compatibility score if project provided
compatibility_score = 1.0
if project is not None:
compatibility_score = calculate_project_investor_compatibility(
project=project, investor=investor, use_funds=True
)
# Get top 3 portfolio companies (id and name only)
portfolio_companies = [
CompanyMinimal(id=company.id, name=company.name)
@@ -158,7 +189,7 @@ class QueryProcessor:
stage_focus=stage_focus,
portfolio_companies=portfolio_companies,
sectors=fund_sectors,
compatibility_score=1.0,
compatibility_score=compatibility_score,
)
investment_responses.append(investment_response)