
    iK                         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 G d	 d
      Zy)ut  
core/workers/escalation_worker.py

Story 5.11: EscalationWorker — Human Handoff Signal
AIVA RLM Nexus PRD v2 — Track A

Writes a Redis escalation signal so Telnyx knows to transfer the active
call to a human operator.  The signal is stored under the key
``aiva:escalation:{session_id}`` with a 300-second TTL.

Design notes:
- Redis SETEX is used (not SET with EX kwarg) to keep mock pattern simple.
- Observability event is appended to events.jsonl (path injected via ctor).
- All external I/O (Redis, file) is injected at construction time.
- No SQLite.  No hardwired side-effects.  Fully testable without real services.
    N)datetimetimezone)Path)AnyOptionali,  ESCALATION_TTLz5/mnt/e/genesis-system/data/observability/events.jsonlc                   j    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dfd
Zd	e
deddfdZy)EscalationWorkera  
    Writes a human-handoff escalation signal to Redis.

    Called by SwarmRouter for every ESCALATE_HUMAN IntentSignal.

    Responsibilities:
      1. Set Redis key ``aiva:escalation:{session_id}`` to ``"requested"``
         with a 300-second TTL via SETEX.
      2. Append an observability event to events.jsonl.
      3. Return ``{"status": "escalation_requested", "session_id": session_id}``.

    All external dependencies are injected via the constructor so the worker
    is fully testable without a real Redis or filesystem.

    Args:
        redis_client: Object with a ``setex(name, time, value)`` method.
                      Compatible with redis.asyncio / redis.Redis.
                      If None, Redis write is skipped with a warning logged.
        events_path:  Path to the observability events.jsonl file.
                      Defaults to EVENTS_PATH constant.
                      Inject ``tmp_path / "events.jsonl"`` in tests.
    Nredis_clientevents_pathreturnc                 0    || _         |xs t        | _        y )N)_redisEVENTS_PATH_events_path)selfr   r   s      7/mnt/e/genesis-system/core/workers/escalation_worker.py__init__zEscalationWorker.__init__?   s    
 #'6;    intentc                 t   K   t        |dd      }| j                  |       | j                  ||       d|dS w)u  
        Write escalation signal to Redis and log the observability event.

        Steps:
          1. Extract session_id from intent (falls back to "unknown").
          2. Call SETEX aiva:escalation:{session_id} 300 "requested".
          3. Append escalation event to events.jsonl.
          4. Return {"status": "escalation_requested", "session_id": session_id}.

        Args:
            intent: An IntentSignal (duck-typed).  Must carry:
                    - intent.session_id (str)
                    - intent.intent_type (IntentType or str) — for observability
                    - intent.utterance (str) — for observability

        Returns:
            dict with keys ``status`` and ``session_id``.  Never None.
        
session_idunknownescalation_requested)statusr   )getattr_write_redis
_log_event)r   r   r   s      r   executezEscalationWorker.executeK   sD     & "&,	B
*%
F+ -$
 	
s   68r   c                 (   | j                   t        j                  d|       yd| }	 | j                   j                  |t        d       t        j                  d|t               y# t        $ r!}t        j                  d||       Y d}~yd}~ww xY w)ao  
        Write the escalation signal to Redis.

        Uses SETEX (not SET with EX kwarg) to keep the mock pattern simple.

        Key:   ``aiva:escalation:{session_id}``
        Value: ``"requested"`` (plain string, not a JSON dict)
        TTL:   ESCALATION_TTL seconds (300)

        If no redis_client was injected, logs a warning and returns silently.
        NuQ   EscalationWorker: no redis_client — escalation signal not stored for session %szaiva:escalation:	requestedz0EscalationWorker: set %s = 'requested' (TTL=%ds)z7EscalationWorker: Redis setex failed for session %s: %s)r   loggerwarningsetexr   info	Exceptionerror)r   r   keyexcs       r   r   zEscalationWorker._write_redisl   s     ;;NNc  -	KKc>;?KKB
  	LLI 	s   <A' '	B0BBc                    t        |dd      }t        |d      r|j                  n
t        |      }d||t        |dd      t	        j
                  t        j                        j                         d}	 | j                  j                  j                  dd	       | j                  j                  d
d      5 }|j                  t        j                  |      dz          ddd       t         j#                  d| j                  |       y# 1 sw Y   +xY w# t$        $ r!}t         j'                  d||       Y d}~yd}~ww xY w)uB  
        Append an escalation observability event to events.jsonl.

        Event schema:
          - event_type  : "escalation_requested"
          - session_id  : str
          - intent_type : str (enum value or raw string)
          - utterance   : str
          - timestamp   : ISO 8601 UTC string

        If the file cannot be written, logs an error and returns silently —
        observability must never block the call flow.

        Args:
            session_id: The AIVA/Telnyx call session identifier.
            intent:     The IntentSignal (duck-typed).
        intent_typer   valuer   	utterance )
event_typer   r+   r-   	timestampT)parentsexist_okazutf-8)encoding
Nz5EscalationWorker: event appended to %s for session %sz:EscalationWorker: failed to write event for session %s: %s)r   hasattrr,   strr   nowr   utc	isoformatr   parentmkdiropenwritejsondumpsr"   debugr&   r'   )r   r   r   raw_intent_typeintent_type_streventfhr)   s           r   r   zEscalationWorker._log_event   s!   & "&-C 0 !!_% 	 1$* b9!hll3==?
	$$**4$*G""''g'> 3"E*T123LLG!!3 3  	LLL 	s1   4AD 8(D
 )D 
DD 	E D;;E )NN)__name__
__module____qualname____doc__r   r   r   r   dictr   r7   r   r    r   r   r
   r
   '   sz    2 '+&*7sm7 d^7 
	7
C 
D 
B s  t  D0S 0# 0$ 0r   r
   )rI   r?   loggingr   r   pathlibr   typingr   r   	getLoggerrF   r"   r   int__annotations__r   r
   rK   r   r   <module>rR      sP   "   '   			8	$  JKW Wr   