From 4494bfc36a347a9932b08eaa58463383a79e6a22 Mon Sep 17 00:00:00 2001 From: Vishwajeetsingh Desurkar Date: Tue, 9 Sep 2025 16:40:47 +0530 Subject: [PATCH 1/2] Add crm leads code and created upload crm audio api end points too --- service/config.py | 7 +- service/crm_client.py | 94 ++++++++++++++++++++ service/main.py | 202 +++++++++++++++++++++++++++++++++++++++++- service/summarizer.py | 185 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 486 insertions(+), 2 deletions(-) create mode 100644 service/crm_client.py diff --git a/service/config.py b/service/config.py index 8bba447..fd5463c 100644 --- a/service/config.py +++ b/service/config.py @@ -7,7 +7,12 @@ #model_id = os.getenv('MODEL_ID', 'large-v3') model_id = os.getenv('MODEL_ID','small') model_path = os.getenv('MODEL_PATH', './models') -ollama_host = os.getenv("OLLAMA_HOST", "http://ollama:11434") +ollama_host = os.getenv("OLLAMA_HOST", "http://localhost:11434") ollama_model_name = os.getenv("OLLAMA_MODEL_NAME", "llama3.2") open_ai_model_name = os.getenv("OPENAI_MODEL_NAME", "gpt-4") open_ai_temperature = os.getenv("OPENAI_TEMPERATURE", 0.2) + +odoo_url = os.getenv("ODOO_URL") +odoo_db = os.getenv("ODOO_DB") +odoo_username = os.getenv("ODOO_USERNAME") +odoo_password = os.getenv("ODOO_PASSWORD") diff --git a/service/crm_client.py b/service/crm_client.py new file mode 100644 index 0000000..c7a59a9 --- /dev/null +++ b/service/crm_client.py @@ -0,0 +1,94 @@ +import xmlrpc.client +from typing import Optional + + +class OdooCRMClient: + def __init__(self, url: str, db: str, username: str, password: str): + self.url = url + self.db = db + self.username = username + self.password = password + + + self.common = xmlrpc.client.ServerProxy(f"{url}/xmlrpc/2/common", allow_none=True) + self.uid = self.common.authenticate(db, username, password, {}) + if not self.uid: + raise Exception("Authentication failed. Check credentials or DB name.") + self.models = xmlrpc.client.ServerProxy(f"{url}/xmlrpc/2/object", allow_none=True) + + + def create_lead(self, name: str, email: Optional[str], phone: Optional[str], lead_type: str = "opportunity"): + lead_id = self.models.execute_kw( + self.db, self.uid, self.password, + "crm.lead", "create", + [{ + "name": name, + "contact_name": name, + "email_from": email, + "phone": phone, + "type": lead_type, + }] + ) + return lead_id + + def update_lead(self, lead_id: int, vals: dict): + return self.models.execute_kw( + self.db, self.uid, self.password, + "crm.lead", "write", + [[lead_id], vals] + ) + + def add_internal_note(self, lead_id: int, note_text: str): + return self.update_lead(lead_id, {"description": note_text}) + + def add_chatter_note(self, lead_id: int, note_text: str): + return self.models.execute_kw( + self.db, self.uid, self.password, + "mail.message", "create", + [{ + "model": "crm.lead", + "res_id": lead_id, + "body": note_text, + "message_type": "comment", + "subtype_id": 2, + }] + ) + + def add_contact_details(self, lead_id: int, contact_name: Optional[str] = None, email: Optional[str] = None, phone: Optional[str] = None): + vals = {} + if contact_name: + vals["name"] = contact_name + if email: + vals["email"] = email + if phone: + vals["phone"] = phone + + partner_id = self.models.execute_kw( + self.db, self.uid, self.password, + "res.partner", "create", + [vals] + ) + + self.update_lead(lead_id, {"partner_id": partner_id}) + return partner_id + + def update_contact_address(self, partner_id: int, street: Optional[str] = None, street2: Optional[str] = None, city: Optional[str] = None, state_id: Optional[int] = None, zip_code: Optional[str] = None, country_id: Optional[int] = None): + vals = {} + if street: + vals["street"] = street + if street2: + vals["street2"] = street2 + if city: + vals["city"] = city + if state_id: + vals["state_id"] = state_id + if zip_code: + vals["zip"] = zip_code + if country_id: + vals["country_id"] = country_id + + return self.models.execute_kw( + self.db, self.uid, self.password, + "res.partner", "write", + [[partner_id], vals] + ) \ No newline at end of file diff --git a/service/main.py b/service/main.py index 44f9981..7bf7216 100644 --- a/service/main.py +++ b/service/main.py @@ -7,13 +7,17 @@ from audio_service import translate_with_whisper_timestamped, translate_with_whisper_from_upload from detect_intent import detect_intent_with_llama, format_intent_response from summarizer import summarize_using_openai -from summarizer import summarize_using_ollama +from summarizer import summarize_using_ollama, extract_contact_detailed_using_ollama from pydantic import BaseModel import traceback from util import generate_timestamp_json from fastapi_versionizer.versionizer import Versionizer, api_version import json from core_banking_mock import router as core_banking_mock_router +import os +import requests +from config import odoo_url, odoo_db, odoo_username, odoo_password +from crm_client import OdooCRMClient app = FastAPI() @@ -139,3 +143,199 @@ async def transcribe_intent(audio: UploadFile = File(...), session_id: str = For except Exception as e: logger.info(traceback.format_exc()) return JSONResponse(content={"message": str(e)}, status_code=500) +async def save_crm_lead_data(lead_id, file_path, translation, extracted_data, summary, user_id=None, transcription_id=None): + + """ + Save CRM lead data to the database through the Next.js API. + """ + try: + # Get base URL from environment or use default + api_base_url = os.environ.get("NEXT_API_BASE_URL", "http://localhost:3000") + + # Extract just the filename from the file path + file_name = os.path.basename(file_path) + + # Prepare the data to send + crm_lead_data = { + "leadId": str(lead_id), + "crmUrl": odoo_url, # Using the odoo_url from config + "fileName": file_name, + "transcriptionId": transcription_id, # This might be None if not provided + "extractedData": extracted_data, + "translation": translation, + "userId": user_id, # This might be None if not provided + "isDefault": False # Adding the default field set to false + } + + # Make the API call + response = requests.post( + f"{api_base_url}/api/crm-leads", + json=crm_lead_data, + headers={"Content-Type": "application/json"} + ) + + if response.status_code == 200: + logger.info(f"CRM lead data saved successfully for lead_id={lead_id}") + return response.json() + else: + logger.error(f"Failed to save CRM lead data: {response.status_code} - {response.text}") + return None + + except Exception as e: + logger.error(f"Exception saving CRM lead data: {str(e)}") + return None + + + +# Add this function to retrieve default CRM leads +async def get_default_crm_leads(): + """ + Fetch CRM leads that are marked as default. + """ + try: + api_base_url = os.environ.get("NEXT_API_BASE_URL", "http://localhost:3000") + + response = requests.get( + f"{api_base_url}/api/crm-leads/default", + headers={"Content-Type": "application/json"} + ) + + if response.status_code == 200: + return response.json() + else: + logger.error(f"Failed to get default CRM leads: {response.status_code}") + return None + except Exception as e: + logger.error(f"Error fetching default CRM leads: {str(e)}") + return None + +# Add a route to expose this functionality +@app.get("/crm-leads/default") +async def fetch_default_crm_leads(): + try: + result = await get_default_crm_leads() + if result and result.get("success"): + return JSONResponse(content=result, status_code=200) + else: + return JSONResponse( + content={"message": "Failed to retrieve default CRM leads"}, + status_code=500 + ) + except Exception as e: + logger.error(f"Error in fetch_default_crm_leads: {str(e)}") + return JSONResponse(content={"message": str(e)}, status_code=500) +# Add this new, simpler endpoint + +@app.get("/crm-lead/{lead_id}") +async def get_crm_lead_direct(lead_id: str): + """ + Get CRM lead data directly by ID from the database. + Simple direct lookup without complex routing. + """ + try: + api_base_url = os.environ.get("NEXT_API_BASE_URL", "http://localhost:3000") + + # Make a direct GET request to a simple endpoint + simple_url = f"{api_base_url}/api/crm-leads/simple/{lead_id}" + logger.info(f"Making GET request to: {simple_url}") + + response = requests.get( + simple_url, + headers={"Content-Type": "application/json"} + ) + + if response.status_code == 200: + return JSONResponse(content=response.json(), status_code=200) + else: + return JSONResponse( + content={"message": "CRM lead not found", "id": lead_id}, + status_code=404 + ) + + except Exception as e: + logger.error(f"Error retrieving CRM lead: {str(e)}") + return JSONResponse(content={"message": str(e)}, status_code=500) + +@app.post("/upload-crm-audio") +async def upload_crm_audio(body: Body): + try: + if body.audio_file_link == "": + return JSONResponse(status_code=400, content={"message":"Invalid file link"}) + + translation = translate_with_whisper_timestamped(body.audio_file_link) + + # Extract detected language + detected_language = translation.get("detected_language", "unknown") + + logger.info("translation done") + summary = summarize_using_ollama(translation["text"]) + + logger.info("summary done") + + # Pass the translation object and detected_language to generate_timestamp_json + result = generate_timestamp_json(translation, summary, detected_language) + + + contact_info = extract_contact_detailed_using_ollama(translation["text"]) if "text" in translation else {"name": None, "phone": None, "address": None} + + logger.info(result) + + # Fire-and-forget CRM sync (do not block response) + lead_id = None + try: + if odoo_url and odoo_db and odoo_username and odoo_password: + client = OdooCRMClient(odoo_url, odoo_db, odoo_username, odoo_password) + lead_id = client.create_lead( + name=contact_info.get("name") or "Unknown", + email=None, + phone=contact_info.get("phone"), + ) + logger.info(f"CRM: Lead created lead_id={lead_id}") + partner_id = client.add_contact_details(lead_id, contact_info.get("name"), None, contact_info.get("phone")) + logger.info(f"CRM: Partner created/linked partner_id={partner_id} to lead_id={lead_id}") + + # Update: Use the complete street information from LLM extraction + street = contact_info.get("street") + logger.info(f"CRM: - Street: '{street}'") + + + street2 = None + city = contact_info.get("city") + state = contact_info.get("state") + zip_code = contact_info.get("zip") + country = contact_info.get("country") + + logger.info(f"CRM: Address components street={street}, city={city}") + + updated = client.update_contact_address( + partner_id, + street=street, + street2=street2, + city=city, + state_id=None, + zip_code=zip_code, + country_id=None + ) + logger.info(f"CRM: Address update result={updated} for partner_id={partner_id}") + + # Add CRM data to the result + result["leadId"] = str(lead_id) + result["crmUrl"] = odoo_url + result["extractedData"] = contact_info + result["transcriptionId"] = None # This will be determined when transcription is saved + result["translation"] = translation["text"] + result["userId"] = None # This will be set by frontend + result["isDefault"] = False + + + else: + logger.info("CRM: Odoo credentials not configured; skipping CRM sync") + except Exception as e: + logger.info(f"CRM: Exception during sync: {e}") + + return JSONResponse(content=result, status_code=200) + + except Exception as e: + logger.info(traceback.format_exc()) + return JSONResponse(content={"message": str(e)}, status_code=500) + diff --git a/service/summarizer.py b/service/summarizer.py index 46bd015..91fd78b 100644 --- a/service/summarizer.py +++ b/service/summarizer.py @@ -4,6 +4,8 @@ from template_config import get_summarization_template import logging import ollama +import json +import re from config import ollama_host, ollama_model_name logging.basicConfig(level=logging.INFO) @@ -45,3 +47,186 @@ def summarize_using_ollama(text): response = ollama.Client(host=ollama_host).generate(model=ollama_model_name, prompt = text+"\n \n""Provide highlights above conversation in Markdown bullet points, ready for direct inclusion in a file, with no pretext, and formatted as a multiline string.") summary = response["response"] return summary + + +def _extract_first_json_block(text: str) -> dict | None: + """Helper to find the first JSON block in a string.""" + try: + start_index = text.find('{') + end_index = text.rfind('}') + if start_index != -1 and end_index != -1 and end_index > start_index: + json_str = text[start_index:end_index + 1] + return json.loads(json_str) + except json.JSONDecodeError: + return None + return None + +def extract_contact_detailed_using_ollama(text: str): + """Use LLM to extract detailed contact and address fields for CRM.""" + if not text or not text.strip(): + return {"name": None, "phone": None, "address": None, "street": None, "city": None, "state": None, "zip": None, "country": None} + + # Try to extract name using regex first as a fallback + name_pattern = r'(?:this is|my name is|I am|I\'m) ([A-Z][a-z]+ [A-Z][a-z]+)' + name_match = re.search(name_pattern, text) + extracted_name = name_match.group(1) if name_match else None + + # Common patterns for addresses with apartment/flat information + address_patterns = [ + # Pattern for addresses with flat/apartment info and city + r'(?:residence|address|live at|located at)[,\s]+(\d+\s+[\w\s]+(?:Street|St|Avenue|Ave|Road|Rd|Boulevard|Blvd|Lane|Ln|Drive|Dr|Court|Ct|Place|Pl|Way)[,\s]+(?:flat|apartment|apt|suite|ste|unit|#)\s+[\w\d]+)[,\s]+([\w\s]+)', + + # Simpler pattern as fallback + r'(\d+\s+[\w\s]+(?:Street|St|Avenue|Ave|Road|Rd|Boulevard|Blvd|Lane|Ln|Drive|Dr|Court|Ct|Place|Pl|Way)[,\s]+(?:flat|apartment|apt|suite|ste|unit|#)\s+[\w\d]+)[,\s]+([\w\s]+)' + ] + + street_address = None + city = None + + for pattern in address_patterns: + matches = re.search(pattern, text, re.IGNORECASE) + if matches: + street_address = matches.group(1).strip() + city = matches.group(2).strip() + break + + # Use a more specific prompt for the LLM + instruction = ( + "Extract contact fields from the transcript. Return STRICT JSON with keys: name, phone, street, city, state, zip, country.\n\n" + "VERY IMPORTANT INSTRUCTIONS:\n" + "1. For name extraction, look for full names like 'Sofia Martinez' or 'John Smith'. " + " Extract both first name and last name. The name should NEVER be null if a name is mentioned.\n\n" + "2. For address extraction:\n" + " - The 'street' field should include ONLY the street address with building number and apartment/flat info\n" + " - Do NOT include the city name in the street field\n" + " - Put the city name ONLY in the city field\n\n" + "Example 1: 'Good morning, this is Sofia Martinez here. Call me on 555-123-4567. My residence, 89 Queen Street, flat 7C, London.'\n" + "Correct extraction:\n" + "{\n" + " \"name\": \"Sofia Martinez\",\n" + " \"phone\": \"+5551234567\",\n" + " \"street\": \"89 Queen Street, flat 7C\",\n" + " \"city\": \"London\",\n" + " \"state\": null,\n" + " \"zip\": null,\n" + " \"country\": null\n" + "}\n\n" + "Example 2: 'Hello, I'm Alice Johnson. You can reach me at 987-654-3210. I live at 56 Park Avenue Suite 12, Boston.'\n" + "Correct extraction:\n" + "{\n" + " \"name\": \"Alice Johnson\",\n" + " \"phone\": \"+9876543210\",\n" + " \"street\": \"56 Park Avenue Suite 12\",\n" + " \"city\": \"Boston\",\n" + " \"state\": null,\n" + " \"zip\": null,\n" + " \"country\": null\n" + "}\n" + ) + prompt = f"{instruction}\n\nTranscript:\n{text}\n\nExtract only these fields and return as JSON." + + try: + client = ollama.Client(host=ollama_host) + resp = client.generate(model=ollama_model_name, prompt=prompt) + raw = resp.get("response", "") + parsed = _extract_first_json_block(raw) + if not parsed: + # Fallback if _extract_first_json_block fails + parsed = json.loads(raw.strip()) + + if not isinstance(parsed, dict): + raise ValueError("LLM returned non-dict JSON") + + def get_str(key): + val = parsed.get(key) + return val.strip() if isinstance(val, str) and val.strip() else None + + name = get_str("name") + phone = get_str("phone") + llm_street = get_str("street") + llm_city = get_str("city") + state = get_str("state") + zip_code = get_str("zip") + country = get_str("country") + + # Post-processing to fix common issues + + # 1. Fix name if it's null but we found one with regex + if not name and extracted_name: + name = extracted_name + + # 2. Try to extract name directly if still null + if not name: + # Look for common name patterns in the text + name_patterns = [ + r'((?:[A-Z][a-z]+ ){1,2}[A-Z][a-z]+) (?:here|speaking)', + r'(?:this is|my name is|I am|I\'m) ([A-Z][a-z]+ [A-Z][a-z]+)', + r'(?:name|caller):? ([A-Z][a-z]+ [A-Z][a-z]+)' + ] + + for pattern in name_patterns: + name_match = re.search(pattern, text, re.IGNORECASE) + if name_match: + name = name_match.group(1).strip() + break + + # 3. Clean up street address - remove city name from street if it appears there + if llm_street and llm_city and llm_city in llm_street: + # Remove the city and any trailing commas/spaces + llm_street = re.sub(r',?\s*' + re.escape(llm_city) + r'(?:,|\s|$)', '', llm_street).strip().rstrip(',') + + # Use LLM values with fallbacks + final_street = llm_street or street_address + final_city = llm_city or city + + # Final normalization and cleaning for phone + if phone: + phone = re.sub(r"[^\d+]", "", phone) + if phone.startswith("00"): + phone = "+" + phone[2:] + if phone and phone[0] != "+" and len(phone) >= 10: + phone = "+" + phone + + # Create full address string, ensuring no duplicates + address_parts = [] + if final_street: + address_parts.append(final_street) + if final_city and final_city not in final_street: + address_parts.append(final_city) + if state: + address_parts.append(state) + if zip_code: + address_parts.append(zip_code) + if country: + address_parts.append(country) + + address = ", ".join(address_parts) if address_parts else None + + result = { + "name": name, + "phone": phone, + "address": address, + "street": final_street, + "city": final_city, + "state": state, + "zip": zip_code, + "country": country, + } + + # Debug log the extraction + logger.info(f"Address extraction results - Input: '{text}', Extracted: {json.dumps(result, indent=2)}") + + return result + except Exception as e: + logger.warning(f"LLM detailed extraction failed: {e}") + # Return a basic extraction using regex patterns if LLM fails + return { + "name": extracted_name, + "phone": re.search(r'(\d{3}[-\.\s]?\d{3}[-\.\s]?\d{4})', text).group(1).replace('-', '') if re.search(r'(\d{3}[-\.\s]?\d{3}[-\.\s]?\d{4})', text) else None, + "address": f"{street_address}, {city}" if street_address and city else None, + "street": street_address, + "city": city, + "state": None, + "zip": None, + "country": None + } From 807b201ac0f6ef1a760492476b2dec736741910e Mon Sep 17 00:00:00 2001 From: arpita-josh02 Date: Mon, 15 Sep 2025 23:18:11 +0530 Subject: [PATCH 2/2] review changes --- service/crm_client.py | 40 +++++++++++++--------- service/main.py | 78 ++++++++++++++++++++++--------------------- service/summarizer.py | 3 +- 3 files changed, 67 insertions(+), 54 deletions(-) diff --git a/service/crm_client.py b/service/crm_client.py index c7a59a9..6d798a6 100644 --- a/service/crm_client.py +++ b/service/crm_client.py @@ -2,24 +2,31 @@ from typing import Optional +class AuthenticationError(Exception): + """Raised when authentication with Odoo fails.""" + + class OdooCRMClient: def __init__(self, url: str, db: str, username: str, password: str): self.url = url self.db = db self.username = username - self.password = password - self.common = xmlrpc.client.ServerProxy(f"{url}/xmlrpc/2/common", allow_none=True) - self.uid = self.common.authenticate(db, username, password, {}) - if not self.uid: - raise Exception("Authentication failed. Check credentials or DB name.") + uid = self.common.authenticate(db, username, password, {}) + + if not uid: + raise AuthenticationError("Authentication failed. Check credentials or DB name.") + + self.uid = uid self.models = xmlrpc.client.ServerProxy(f"{url}/xmlrpc/2/object", allow_none=True) + # Store auth securely (don’t keep password separately) + self._auth = (db, uid, password) def create_lead(self, name: str, email: Optional[str], phone: Optional[str], lead_type: str = "opportunity"): - lead_id = self.models.execute_kw( - self.db, self.uid, self.password, + return self.models.execute_kw( + *self._auth, "crm.lead", "create", [{ "name": name, @@ -29,11 +36,10 @@ def create_lead(self, name: str, email: Optional[str], phone: Optional[str], lea "type": lead_type, }] ) - return lead_id def update_lead(self, lead_id: int, vals: dict): return self.models.execute_kw( - self.db, self.uid, self.password, + *self._auth, "crm.lead", "write", [[lead_id], vals] ) @@ -43,7 +49,7 @@ def add_internal_note(self, lead_id: int, note_text: str): def add_chatter_note(self, lead_id: int, note_text: str): return self.models.execute_kw( - self.db, self.uid, self.password, + *self._auth, "mail.message", "create", [{ "model": "crm.lead", @@ -54,7 +60,8 @@ def add_chatter_note(self, lead_id: int, note_text: str): }] ) - def add_contact_details(self, lead_id: int, contact_name: Optional[str] = None, email: Optional[str] = None, phone: Optional[str] = None): + def add_contact_details(self, lead_id: int, contact_name: Optional[str] = None, + email: Optional[str] = None, phone: Optional[str] = None): vals = {} if contact_name: vals["name"] = contact_name @@ -64,7 +71,7 @@ def add_contact_details(self, lead_id: int, contact_name: Optional[str] = None, vals["phone"] = phone partner_id = self.models.execute_kw( - self.db, self.uid, self.password, + *self._auth, "res.partner", "create", [vals] ) @@ -72,7 +79,10 @@ def add_contact_details(self, lead_id: int, contact_name: Optional[str] = None, self.update_lead(lead_id, {"partner_id": partner_id}) return partner_id - def update_contact_address(self, partner_id: int, street: Optional[str] = None, street2: Optional[str] = None, city: Optional[str] = None, state_id: Optional[int] = None, zip_code: Optional[str] = None, country_id: Optional[int] = None): + def update_contact_address(self, partner_id: int, street: Optional[str] = None, + street2: Optional[str] = None, city: Optional[str] = None, + state_id: Optional[int] = None, zip_code: Optional[str] = None, + country_id: Optional[int] = None): vals = {} if street: vals["street"] = street @@ -88,7 +98,7 @@ def update_contact_address(self, partner_id: int, street: Optional[str] = None, vals["country_id"] = country_id return self.models.execute_kw( - self.db, self.uid, self.password, + *self._auth, "res.partner", "write", [[partner_id], vals] - ) \ No newline at end of file + ) diff --git a/service/main.py b/service/main.py index 7bf7216..79242fe 100644 --- a/service/main.py +++ b/service/main.py @@ -143,47 +143,47 @@ async def transcribe_intent(audio: UploadFile = File(...), session_id: str = For except Exception as e: logger.info(traceback.format_exc()) return JSONResponse(content={"message": str(e)}, status_code=500) -async def save_crm_lead_data(lead_id, file_path, translation, extracted_data, summary, user_id=None, transcription_id=None): - - """ - Save CRM lead data to the database through the Next.js API. - """ - try: - # Get base URL from environment or use default - api_base_url = os.environ.get("NEXT_API_BASE_URL", "http://localhost:3000") +# async def save_crm_lead_data(lead_id, file_path, translation, extracted_data, summary, user_id=None, transcription_id=None): + +# """ +# Save CRM lead data to the database through the Next.js API. +# """ +# try: +# # Get base URL from environment or use default +# api_base_url = os.environ.get("NEXT_API_BASE_URL", "http://localhost:3000") - # Extract just the filename from the file path - file_name = os.path.basename(file_path) +# # Extract just the filename from the file path +# file_name = os.path.basename(file_path) - # Prepare the data to send - crm_lead_data = { - "leadId": str(lead_id), - "crmUrl": odoo_url, # Using the odoo_url from config - "fileName": file_name, - "transcriptionId": transcription_id, # This might be None if not provided - "extractedData": extracted_data, - "translation": translation, - "userId": user_id, # This might be None if not provided - "isDefault": False # Adding the default field set to false - } +# # Prepare the data to send +# crm_lead_data = { +# "leadId": str(lead_id), +# "crmUrl": odoo_url, # Using the odoo_url from config +# "fileName": file_name, +# "transcriptionId": transcription_id, # This might be None if not provided +# "extractedData": extracted_data, +# "translation": translation, +# "userId": user_id, # This might be None if not provided +# "isDefault": False # Adding the default field set to false +# } - # Make the API call - response = requests.post( - f"{api_base_url}/api/crm-leads", - json=crm_lead_data, - headers={"Content-Type": "application/json"} - ) +# # Make the API call +# response = requests.post( +# f"{api_base_url}/api/crm-leads", +# json=crm_lead_data, +# headers={"Content-Type": "application/json"} +# ) - if response.status_code == 200: - logger.info(f"CRM lead data saved successfully for lead_id={lead_id}") - return response.json() - else: - logger.error(f"Failed to save CRM lead data: {response.status_code} - {response.text}") - return None +# if response.status_code == 200: +# logger.info(f"CRM lead data saved successfully for lead_id={lead_id}") +# return response.json() +# else: +# logger.error(f"Failed to save CRM lead data: {response.status_code} - {response.text}") +# return None - except Exception as e: - logger.error(f"Exception saving CRM lead data: {str(e)}") - return None +# except Exception as e: +# logger.error(f"Exception saving CRM lead data: {str(e)}") +# return None @@ -197,7 +197,8 @@ async def get_default_crm_leads(): response = requests.get( f"{api_base_url}/api/crm-leads/default", - headers={"Content-Type": "application/json"} + headers={"Content-Type": "application/json"}, + timeout=30 ) if response.status_code == 200: @@ -241,7 +242,8 @@ async def get_crm_lead_direct(lead_id: str): response = requests.get( simple_url, - headers={"Content-Type": "application/json"} + headers={"Content-Type": "application/json"}, + timeout=30 ) if response.status_code == 200: diff --git a/service/summarizer.py b/service/summarizer.py index 91fd78b..fa31fe4 100644 --- a/service/summarizer.py +++ b/service/summarizer.py @@ -220,9 +220,10 @@ def get_str(key): except Exception as e: logger.warning(f"LLM detailed extraction failed: {e}") # Return a basic extraction using regex patterns if LLM fails + phone_match = re.search(r'(\d{3}[-\.\s]?\d{3}[-\.\s]?\d{4})', text) return { "name": extracted_name, - "phone": re.search(r'(\d{3}[-\.\s]?\d{3}[-\.\s]?\d{4})', text).group(1).replace('-', '') if re.search(r'(\d{3}[-\.\s]?\d{3}[-\.\s]?\d{4})', text) else None, + "phone": phone_match.group(1).replace('-', '') if phone_match else None, "address": f"{street_address}, {city}" if street_address and city else None, "street": street_address, "city": city,