"""Advanced Vision Care practice facts + the phone agent's tools. Facts sourced from advancedvisioncareflorida.com (8 locations across Broward, Miami-Dade, Palm Beach). NOTE: the website does NOT publish office hours, so we do NOT assert hours — the agent must offer to have staff confirm them instead of inventing them. Fill HOURS in if/when you have them. """ import json import os import re from datetime import datetime, timezone from loguru import logger # ───────────────────────────────────────────────────────────────────────────── # Real facts from advancedvisioncareflorida.com # ───────────────────────────────────────────────────────────────────────────── LOCATIONS = [ # Broward County {"city": "Hollywood / Fort Lauderdale", "address": "2873 Stirling Rd, Fort Lauderdale, FL 33312", "phone": "(954) 983-4969"}, {"city": "Tamarac", "address": "5865 N University Dr, Tamarac, FL 33321", "phone": "(954) 720-2720"}, {"city": "Pembroke Pines", "address": "246 S Flamingo Rd, Pembroke Pines, FL 33027", "phone": "(954) 443-1230"}, {"city": "Lauderdale Lakes", "address": "3682 W Oakland Park Blvd, Lauderdale Lakes, FL 33311", "phone": "(954) 730-8087"}, # Miami-Dade County {"city": "Hialeah", "address": "1770 W 32nd Pl, Hialeah, FL 33012", "phone": "(305) 885-4477"}, {"city": "Kendall", "address": "11605 N Kendall Dr, Miami, FL 33176", "phone": "(305) 982-8927"}, {"city": "Miami Gardens", "address": "4771 NW 183rd St, Miami Gardens, FL 33055", "phone": "(305) 390-2467"}, # Palm Beach County {"city": "Boca Raton", "address": "21673 State Road 7, Boca Raton, FL 33428", "phone": "(561) 470-2310"}, ] PRACTICE_FACTS = { "name": "Advanced Vision Care", "locations": LOCATIONS, "insurance": [ "CarePlus", "Doctors Health", "Florida Blue Medicare", "Optum", "Spectera", "Sunshine Health", "VSP", "WellCare", ], "services": ( "routine and medical eye exams, contact lens exams, pediatric eye exams, " "and LASIK consultations" ), # Website does not publish hours — leave None so the agent won't invent them. "hours": None, } REQUESTS_LOG = os.path.join(os.path.dirname(os.path.abspath(__file__)), "appointment_requests.jsonl") # Expand street abbreviations so the TTS speaks "North Kendall Drive", not "N … D-R". _ABBREV = { "NW": "Northwest", "NE": "Northeast", "SW": "Southwest", "SE": "Southeast", "N": "North", "S": "South", "E": "East", "W": "West", "Dr": "Drive", "Rd": "Road", "Blvd": "Boulevard", "St": "Street", "Ave": "Avenue", "Pl": "Place", "Ln": "Lane", "Ct": "Court", "Hwy": "Highway", "FL": "Florida", } def _spoken_address(addr: str) -> str: """Expand directional + street-type abbreviations for natural speech.""" return re.sub( r"\b(" + "|".join(re.escape(k) for k in _ABBREV) + r")\b", lambda m: _ABBREV[m.group(1)], addr, ) def practice_summary() -> str: """Compact facts block for the system prompt.""" f = PRACTICE_FACTS loc_lines = "\n".join(f" - {l['city']}: {_spoken_address(l['address'])} — {l['phone']}" for l in f["locations"]) hours = f["hours"] or ( "NOT published — do not state specific hours; offer to have the office confirm." ) return ( f"Practice name: {f['name']}\n" f"Locations ({len(f['locations'])} offices across South Florida):\n{loc_lines}\n" f"Insurance accepted (these EXACT plans only): {', '.join(f['insurance'])}.\n" f"Services: {f['services']}\n" f"Hours: {hours}\n" ) def _find_location(name: str): """Loose match a caller's city/location text to a known office.""" if not name: return None n = name.lower() for l in LOCATIONS: if n in l["city"].lower() or l["city"].lower() in n: return l return None # ─── Tools (used when ENABLE_TOOLS=true and the model supports tool-calling) ── def persist_appointment(record: dict) -> str: """Write an appointment request to Odoo (a crm.lead) if configured, else to the JSONL fallback so a request is never lost. Returns where it landed. Used by both the post-call extraction and the (optional) in-call tool.""" record.setdefault("ts", datetime.now(timezone.utc).isoformat()) if os.environ.get("ODOO_USER") and os.environ.get("ODOO_API_KEY"): try: from odoo_client import create_appointment_request model, rec_id = create_appointment_request( patient_name=record.get("patient_name"), callback_number=record.get("callback_number"), reason=f"[{record.get('location') or 'location TBD'}] {record.get('reason') or ''}".strip(), preferred_time=record.get("preferred_time"), insurance=record.get("insurance"), call_sid=record.get("call_sid"), ) logger.info(f"Appointment -> Odoo {model} id={rec_id}: {record.get('patient_name')}") return f"odoo:{model}:{rec_id}" except Exception as e: logger.warning(f"Odoo write failed ({e!r}); falling back to local log") record["odoo_error"] = repr(e) with open(REQUESTS_LOG, "a") as fh: fh.write(json.dumps(record) + "\n") logger.info(f"Appointment -> JSONL: {record.get('patient_name')}") return "jsonl" async def record_appointment_request(params): """In-call tool path (only used when ENABLE_TOOLS=true). Wraps persist_appointment.""" args = params.arguments or {} persist_appointment({ "call_sid": getattr(params, "call_sid", None), "patient_name": args.get("patient_name"), "callback_number": args.get("callback_number"), "location": args.get("location"), "reason": args.get("reason"), "preferred_time": args.get("preferred_time"), "source": "in_call_tool", }) await params.result_callback( {"status": "captured", "message": "Got it — our staff will call you back to confirm the time."} ) async def get_practice_info(params): """Return practice facts (optionally narrowed to one location) for accurate answers.""" args = params.arguments or {} loc = _find_location(args.get("location", "")) result = { "name": PRACTICE_FACTS["name"], "insurance": PRACTICE_FACTS["insurance"], "services": PRACTICE_FACTS["services"], "hours": "not published — offer to have the office confirm", } result["location"] = loc if loc else PRACTICE_FACTS["locations"] await params.result_callback(result)