
    i                         U d Z ddlZddlZddlmZmZ ddlmZ ddlmZm	Z	  ej                  e      ZdZeed<    ed      Zd	Zeed
<    G d d      Zy)ui  
core/workers/booking_worker.py

BookingWorker — creates a GHL lead from extracted intent entities and
posts to the Telnyx lead webhook at api.sunaivadigital.com.

Created by Story 5.06 (AIVA RLM Nexus PRD v2).

Responsibilities:
  1. Extract name, phone, location, service from intent.extracted_entities
  2. POST to GHL_WEBHOOK_URL with a GHL-compatible lead payload
  3. Write success/failure result to Redis aiva:results:{session_id}
  4. Return a status dict — never None

Dependencies are injected at construction so all I/O is fully mockable in tests.
No SQLite. No direct network calls in the class body.
    N)datetimetimezone)Path)AnyOptionalz2https://api.sunaivadigital.com/webhook/telnyx-leadGHL_WEBHOOK_URLz5/mnt/e/genesis-system/data/observability/events.jsonlUnknown_UNKNOWN_FALLBACKc                   ~    e Zd ZdZ	 	 ddee   dee   ddfdZdedefdZdedefd	Z	d
ede
defdZde
deddfdZy)BookingWorkera  
    Converts an AIVA BOOK_JOB IntentSignal into a GHL lead via webhook POST.

    All external I/O (HTTP, Redis) is performed through injected client objects
    so the worker is fully testable without any real network or database.

    Args:
        http_client: Object with a coroutine method ``post(url, json=payload)``.
                     Response must expose a ``.status`` attribute (int).
                     Compatible with aiohttp.ClientSession and httpx.AsyncClient.
        redis_client: Object with a method ``set(key, value)`` for storing
                      worker results. Compatible with aioredis / redis.asyncio.
    Nhttp_clientredis_clientreturnc                      || _         || _        y )N)_http_redis)selfr   r   s      4/mnt/e/genesis-system/core/workers/booking_worker.py__init__zBookingWorker.__init__:   s    
 !
"    intentc                    K   t        |dd      }| j                  |      }| j                  ||       d{   }| j                  ||       d{    |S 7  7 w)u  
        Main entry point.  Called by SwarmRouter for every BOOK_JOB intent.

        Steps:
          1. Build a GHL-compatible lead payload from intent.extracted_entities.
          2. POST the payload to GHL_WEBHOOK_URL via the injected http_client.
          3. Store the result in Redis under ``aiva:results:{session_id}``.
          4. Return a status dict — {"status": "ok", "lead_id": ...} on success
             or {"status": "error", "reason": ...} on failure.

        Args:
            intent: An IntentSignal instance (duck-typed — no direct import to
                    keep the worker decoupled from the classifier layer).

        Returns:
            dict with at minimum a ``status`` key.  Never None.
        
session_idunknownN)getattr_build_lead_payload
_post_lead_store_result)r   r   r   payloadresults        r   executezBookingWorker.executeF   s]     $ "&,	B
**62w
;;  V444 <4s!   4AAAAAAc           	      X   t        |di       xs i }t        |dt              }|j                  dt              |j                  dt              |j                  dt              |j                  dt              d|t        j                  t
        j                        j                         dS )	u!  
        Builds a GHL-compatible lead payload from the intent's extracted entities.

        Entity extraction rules:
          - Uses ``intent.extracted_entities`` dict if present.
          - Falls back to ``_UNKNOWN_FALLBACK`` ("Unknown") for any missing field.
          - ``source`` is always "AIVA_CALL" — hardcoded, non-negotiable.

        Args:
            intent: An IntentSignal (or any object with extracted_entities dict).

        Returns:
            dict suitable for JSON serialisation and POSTing to the GHL webhook.
        extracted_entitiesr   namephonelocationservice	AIVA_CALL)	firstNamer%   r&   r'   source	sessionId	createdAt)r   r
   getr   nowr   utc	isoformat)r   r   entitiesr   s       r   r   z!BookingWorker._build_lead_payloadc   s     !)=rBHb!&,8IJ
 #,,v/@A",,w0AB",,z3DE",,y2CD%$",,x||4>>@
 	
r   r   r   c                   K   | j                   t        j                  d|       dddS 	 | j                   j                  t        |       d{   }t        |dd      }|*d|cxk  rd	k  rn nt        j                  d
||       d|dS d| }t        j                  d||       d|dS 7 ^# t        $ r/}t        j                  d||       dt        |      dcY d}~S d}~ww xY ww)a  
        POST the lead payload to GHL_WEBHOOK_URL.

        If no http_client was injected the method returns an error dict without
        attempting any network I/O.

        Args:
            payload:    GHL-compatible lead dict (from _build_lead_payload).
            session_id: Used for logging context only.

        Returns:
            {"status": "ok", "lead_id": <session_id>} on HTTP 2xx, or
            {"status": "error", "reason": <message>} on any failure.
        Nu;   No http_client injected — cannot POST lead for session %serrorno_http_client)statusreason)jsonr5      i,  z1Lead posted successfully for session %s (HTTP %s)ok)r5   lead_idhttp_z2Lead POST returned non-2xx for session %s: HTTP %sz#Lead POST failed for session %s: %s)
r   loggerr3   postr   r   infowarning	Exceptionstr)r   r   r   responsestatus_coder6   excs          r   r   zBookingWorker._post_lead   s      ::LLVXbc%1ABB	;!ZZ___7_KKH&x4@K &3++C+COQ[]hi"&:>> .H
 #*V<< L   	;LL>
CP%S::	;sR   (C+$B0 B.<B0 C+ B0 -C+.B0 0	C(9$C#C(C+#C((C+r    c                 B  K   | j                   t        j                  d|       yd| }	 | j                   j                  |t	        j
                  |             t        j                  d|       y# t        $ r!}t        j                  d||       Y d}~yd}~ww xY ww)u  
        Write the worker result to Redis under the key ``aiva:results:{session_id}``.

        If no redis_client was injected the method logs a warning and returns
        silently — Redis storage is best-effort and must not block the call.

        Args:
            session_id: Identifies the AIVA conversation session.
            result:     The dict returned from _post_lead (will be JSON-serialised).
        NuC   No redis_client injected — skipping result storage for session %szaiva:results:zResult stored in Redis at %sz2Failed to store result in Redis for session %s: %s)	r   r<   r?   setr7   dumpsdebugr@   r3   )r   r   r    keyrD   s        r   r   zBookingWorker._store_result   s      ;;NN`blmj\*	`KKOOCF!34LL7= 	`LLMz[^__	`s/   )BAA2 1B2	B;BBBB)NN)__name__
__module____qualname____doc__r   r   r   dictr!   r   rA   r   r    r   r   r   r   +   s      &*&*#c]# sm# 
	#C D :
# 
$ 
8&; &;# &;$ &;P`c `4 `D `r   r   )rM   r7   loggingr   r   pathlibr   typingr   r   	getLoggerrJ   r<   r   rA   __annotations__EVENTS_PATHr
   r   rO   r   r   <module>rV      s`   $   '   			8	$ L KJK # 3 "P` P`r   