
    oi]                     X    d Z ddlZddlmZ  G d d      ZdefdZdefdZd	edefd
Z	y)u#  
AIVA RLM Nexus PRD v2 — Story 4.07
Track A: ConversationReplayEngine — Last N Conversations Summary

Queries royal_conversations, formats N most recent Kinan<>AIVA conversations
into a concise human-readable string for system prompt injection.

File: core/memory/conversation_replay.py
    N)Optionalc                   :    e Zd ZdZddZd	dedefdZdedefdZ	y)
ConversationReplayEngineu  
    Queries royal_conversations, formats N most recent conversations
    into a concise summary for system prompt injection.

    Uses the getconn/putconn pattern against a psycopg2 connection pool.
    All SQL uses parameterised %s placeholders — never f-strings in SQL.
    Nc                     || _         y)z
        Args:
            postgres_pool: A psycopg2 connection pool (SimpleConnectionPool or
                           ThreadedConnectionPool). If None, get_recent_summary()
                           returns an empty string gracefully.
        N)_pool)selfpostgres_pools     8/mnt/e/genesis-system/core/memory/conversation_replay.py__init__z!ConversationReplayEngine.__init__   s     #
    nreturnc                 N  K   | j                   y|dk  ryd}	 | j                   j                         }| j                  ||      }	 |	 | j                   j	                  |       |syg }|D ]!  }t        |      }|s|j                  |       # |syddj                  |      z   S # t        $ r1 Y |,	 | j                   j	                  |       y# t        $ r Y yw xY wyw xY w# t        $ r Y w xY w# |,	 | j                   j	                  |       w # t        $ r Y w w xY ww xY ww)a!  
        Fetches last N royal_conversations where kinan participated.

        Queries royal_conversations with:
            participants->>'kinan' = 'true'
        ORDER BY started_at DESC LIMIT %s

        Returns a human-readable string such as:
            RECENT CONTEXT:
            [2026-02-24] Call (18min): George's Cairns number checked. Action: Follow up.
            [2026-02-23] Call (6min): Voice widget confirmed live. Kinan happy.

        Returns empty string (never None) when:
        - No conversations exist
        - postgres_pool is None
        - Any database error occurs

        Args:
            n: Number of recent conversations to fetch (default 3, must be >= 1).

        Returns:
            Formatted string with RECENT CONTEXT header, or "" if nothing found.
        N    zRECENT CONTEXT:

)r   getconn_fetch_rows	Exceptionputconn_format_rowappendjoin)r   r   connrowslinesrowlines          r
   get_recent_summaryz+ConversationReplayEngine.get_recent_summary(   sN    0 ::q5
	::%%'D##D!,D JJ&&t,  	#Cs#DT"	#
 "TYYu%555+  	JJ&&t,    	 !  JJ&&t,    s   D%,B& D%	C# $D%<*D%&	C /C2 0D%3C D%	CD%CD%C  C2 #	C/,D%.C//D%2D"6DD"	DD"DD""D%c           
      &   d}|j                         5 }|j                  ||f       |j                  D cg c]  }|d   	 }}|j                         D cg c]  }t	        t        ||             c}cddd       S c c}w c c}w # 1 sw Y   yxY w)a  
        Execute parameterised SELECT against royal_conversations.

        Uses LIMIT %s (not Python slice) so the database does the bounding.
        Returns a list of dict-like rows, each containing:
            started_at, ended_at, summary, action_items
        zSELECT started_at, ended_at, summary, action_items FROM royal_conversations WHERE participants->>'kinan' = 'true' ORDER BY started_at DESC LIMIT %sr   N)cursorexecutedescriptionfetchalldictzip)r   r   r   _SQLcurdesccolumnsr   s           r
   r   z$ConversationReplayEngine._fetch_rowse   s     	 [[] 	GcKKqd#+.??;4tAw;G;7:||~FDWc*+F	G 	G;F	G 	Gs(   "BA=BB1B=
BB)N)   )
__name__
__module____qualname____doc__r   intstrr   listr    r   r
   r   r      s6    #76# 76c 76zG3 G4 Gr   r   r   c                 x    |y	 || z
  }t        |j                               }|dz  }| dS # t        $ r Y yw xY w)u   
    Return a human-readable duration string.

    If ended_at is None → "ongoing"
    Otherwise → "Xmin" where X is the integer number of minutes (rounded down).
    ongoing<   min)r0   total_secondsr   )
started_atended_atdeltar8   minutess        r
   _format_durationr=   ~   sW     :%E//122%# s   '- 	99c                 N   | y| }t        | t              r-| j                         }|r|dv ry	 t        j                  |      }t        |t              rW|D cg c]5  }t        |      j                         st        |      j                         7 }}|syddj                  |      z   S t        |t              r"t        |      j                         }|rd| S dS t        |      j                         }|rd| S dS # t        j
                  t        f$ r d| cY S w xY wc c}w )u  
    Format action_items (JSONB array or None) into an inline string.

    Rules:
    - None / empty list / empty string → return "" (omit Action part)
    - JSON string that parses to a list → join with "; "
    - JSON string that parses to a single string → use as-is
    - Already a list (psycopg2 native) → join with "; "
    - Any other type → str() it

    Returns:
        " Action: <text>" if there are items, else "".
    r   )z[]nullz{}z	 Action: z; )

isinstancer1   stripjsonloadsJSONDecodeError
ValueErrorr2   r   r%   )action_itemsitemsstrippedi	non_emptytexts         r
   _format_action_itemsrL      s"     E ,$%%'8';;	*JJx(E %-2Ec!fllnSV\\^E	ETYYy111%5z!%)4&!1r1u:D!%Ytf-2-% $$j1 	*xj))	* Fs   C> D"6D">DDr   c                 ^   | j                  d      }|y	 |j                  d      }| j                  d      }t	        ||      }| j                  d      xs dj                         }|sd}t        | j                  d	            }d
| d| d| | S # t        $ r t        |      dd }Y w xY w)z
    Format a single royal_conversations row into one summary line.

    Output format:
        [YYYY-MM-DD] Call (Xmin): <summary><action_part>

    Where action_part is either "" or " Action: <items>".
    Returns "" if started_at is missing.
    r9   Nr   z%Y-%m-%d
   r:   summaryz(no summary)rF   [z] Call (z): )getstrftimeAttributeErrorr1   r=   rA   rL   )r   r9   date_strr:   durationrO   action_parts          r
   r   r      s     &J(&&z2
 wwz"H
H5Hwwy!'R..0G &sww~'>?Kxj
#gY{mDD  (z?3B'(s   B B,+B,)
r/   rB   typingr   r   r1   r=   rL   r%   r   r3   r   r
   <module>rX      sP     eG eGXc &-.# -.`ET Ec Er   