
    $iB                       d Z ddlmZ ddlZddlmZmZ ddlmZmZ ddl	m
Z
 ddlmZmZmZ ddlmZ  ed	d
      Z G d de      Z G d de      Zd,dZej+                  d      d-d       Zej+                  de      d ee      f	 	 	 	 	 d.d       Zd,dZej+                  d       ee      f	 	 	 	 	 d/d       Z G d de      Z G d de      Zej9                  dde        ee      f	 	 	 	 	 d0d!       Z G d" d#e      Z G d$ d%e      ZejA                  d&e       ee      f	 	 	 	 	 	 	 d1d'       Z!e"d(k(  rddl#Z# e#jH                  ed)d*+       yy)2u  
AIVA Memory API — FastAPI Bootstrap + Conversations Endpoint
=============================================================
Story 7.01: FastAPI App Bootstrap
Story 7.02: GET /conversations/recent
Story 7.03: GET /context/{session_id}
Story 7.04: POST /conversations
Story 7.05: PATCH /conversations/{conversation_id}/enrich

Provides the core FastAPI application instance for the AIVA Memory API.
Exposes a /health endpoint that validates the app is running and reports
Elestio connection status for postgres, redis, and qdrant.

Also exposes GET /conversations/recent?n=3 which returns the N most recent
Kinan<>AIVA royal conversations from Postgres (royal_conversations table).

POST /conversations creates a new royal_conversations row when a call starts.

PATCH /conversations/{conversation_id}/enrich is triggered by PostCallEnricher
to write enrichment data (transcript, entities, decisions, action items, etc.)
to an existing conversation row and set ended_at to the current UTC time.

VERIFICATION_STAMP
Story: 7.01
Verified By: parallel-builder
Verified At: 2026-02-25
Tests: 6/6
Coverage: 100%

VERIFICATION_STAMP
Story: 7.02
Verified By: parallel-builder
Verified At: 2026-02-25
Tests: 8/8
Coverage: 100%
    )annotationsN)datetimetimezone)AnyOptional)uuid4)DependsFastAPIHTTPException)	BaseModelzAIVA Memory APIz1.0.0)titleversionc                  D    e Zd ZU dZded<   ded<   ded<   ded<   ded<   y	)
ConversationSummaryz6A single royal conversation entry returned to callers.strid
started_atsummary	list[str]action_itemsemotional_signalN__name__
__module____qualname____doc____annotations__     ,/mnt/e/genesis-system/api/aiva_memory_api.pyr   r   D   s     @GOLr   r   c                      e Zd ZU dZded<   y)RecentConversationsResponsez6Top-level response envelope for /conversations/recent.zlist[ConversationSummary]conversationsNr   r   r   r    r"   r"   N   s    @,,r   r"   c                    K   yw)aX  
    Production dependency: returns a live psycopg2 connection to Elestio
    Postgres.  This is overridden in tests via app.dependency_overrides so
    the test suite never touches a real database.

    Returns None in the default stub so the endpoint gracefully degrades to
    an empty conversation list when no connection is available.
    Nr   r   r   r    get_pg_connectionr%   Y   s      s   z/healthc                 |   K   dt        j                  t        j                        j	                         dddddS w)an  
    Returns service status and Elestio connection status.

    In this bootstrap implementation the service statuses are reported as
    'connected' to signal that the API layer is wired up correctly.  Later
    stories will replace these stubs with real Elestio probe calls.

    Returns:
        dict: {status, timestamp, services: {postgres, redis, qdrant}}
    ok	connected)postgresredisqdrant)status	timestampservices)r   nowr   utc	isoformatr   r   r    health_checkr2   j   s<      \\(,,/99;# !
 s   :<z/conversations/recent)response_model   c                  K   t        | d      } |t        g       S |j                         }|j                  d| f       |j	                         }|j                          g }|D ]z  }|\  }}}}	}
t        |t              r|j                         }|	g }	|j                  t        t        |      t        |      t        |      t        |	      t        |
                   | t        |      S w)u  
    Returns the last *n* royal conversations where kinan=true.

    Query parameters:
        n: Number of conversations to return (default 3, hard-capped at 20).

    The endpoint always returns HTTP 200.  When no conversations exist — or
    when no database connection is available — the response contains an empty
    ``conversations`` list rather than a 404.

    Database query (executed when pg_conn is not None):

        SELECT id, started_at, summary, action_items, emotional_signal
        FROM royal_conversations
        WHERE kinan = true
        ORDER BY started_at DESC
        LIMIT :n

    Returns:
        RecentConversationsResponse: envelope containing a (possibly empty)
        list of ConversationSummary objects.
       )r#   z
        SELECT id, started_at, summary, action_items, emotional_signal
        FROM royal_conversations
        WHERE kinan = true
        ORDER BY started_at DESC
        LIMIT %s
        )r   r   r   r   r   )minr"   cursorexecutefetchallclose
isinstancer   r1   appendr   r   list)npg_connr8   rowsr#   rowconv_idr   r   r   r   s              r    get_recent_conversationsrD      s     8 	Ar
A*<< ^^F
NN	 
	 ??D
LLN/1M 
GJDWl4Dj(+#--/J Lw<z?G!,/!$%5!6	

& ']CCs   C+C-c                      y)a7  
    Production dependency: returns a live Redis client connected to Elestio.
    Overridden in tests via app.dependency_overrides so the test suite never
    touches a real Redis instance.

    Returns None in the default stub so the endpoint gracefully degrades when
    no Redis connection is available.
    Nr   r   r   r    get_redis_clientrF      s     r   z/context/{session_id}c                   K   || dddS |j                  d|        }|| dddS t        |t              r|j                  d      }| |ddS w)u  
    Returns the pre-assembled ROYAL_CHAMBER_CONTEXT XML envelope from Redis
    for an active session.

    Redis key pattern: ``aiva:context:{session_id}``

    Always returns HTTP 200. When the key is absent — or when no Redis client
    is available — the response carries ``hydrated=false`` and an empty
    ``context_xml`` string rather than a 404.

    Args:
        session_id: Unique identifier of the AIVA session.
        redis: Injected Redis client (None in stub / tests override).

    Returns:
        dict: {session_id, context_xml, hydrated}
            - ``context_xml``   — raw XML string from Redis (empty if missing)
            - ``hydrated``      — True when the key was found in Redis
     F)
session_idcontext_xmlhydratedzaiva:context:zutf-8T)getr<   bytesdecode)rI   r*   rJ   s      r    get_session_contextrO      ss     0 }(OO))mJ<89K(OO +u%!((1 !" s   AAc                  4    e Zd ZU dZded<   ded<   i Zded<   y)ConversationCreateRequestu5   Payload for POST /conversations — call-start event.r   rI   call_iddictparticipantsN)r   r   r   r   r   rT   r   r   r    rQ   rQ     s    ?OLL$r   rQ   c                  &    e Zd ZU dZded<   ded<   y)ConversationCreateResponsez2Response envelope returned by POST /conversations.r   conversation_idr,   Nr   r   r   r    rV   rV     s    <Kr   rV   z/conversations   )status_coder3   c           	       K   t        t                     }|t        |d      S 	 |j                         }|j	                  d|| j
                  | j                  t        j                  | j                        f       |j                          |j                          t        |d      S # t        $ r4}t        |      j                         }d|v sd|v rt        dd	       d}~ww xY ww)
uO  
    Creates a new row in royal_conversations when a call starts.

    Inserts the following columns:
        id          — freshly generated UUID4
        session_id  — from request payload
        call_id     — from request payload (must be UNIQUE)
        participants — JSON-serialised dict from payload
        started_at  — NOW() in Postgres (UTC)
        status      — 'active'

    Returns:
        201 ConversationCreateResponse with conversation_id (UUID) and
        status="active".

    Errors:
        409 Conflict — when call_id already exists in royal_conversations
                       (detected via UNIQUE constraint violation).
        422 Unprocessable Entity — FastAPI validates the request body
                                   automatically; missing required fields
                                   raise this before reaching handler code.

    Graceful degradation:
        When pg_conn is None (no database available) the endpoint still
        returns 201 with a freshly generated UUID so callers can continue
        operating in offline / dev mode.
    Nactive)rW   r,   zINSERT INTO royal_conversations
               (id, session_id, call_id, participants, started_at, status)
               VALUES (%s, %s, %s, %s, NOW(), 'active')unique	duplicatei  zDuplicate call_idrY   detail)r   r   rV   r8   r9   rI   rR   jsondumpsrT   commitr;   	Exceptionlowerr   )payloadr@   rW   r8   exc	err_lowers         r    create_conversationrh   %  s     @ %'lO)+
 	

!;  ""

7//0		
	
 	 &'   HNN$	y K9$<C8KLLs)   #C*A7B* C**	C'3/C""C''C*c                      e Zd ZU dZdZded<   g Zded<   g Zded<   g Zded<   dZ	ded	<   g Z
ded
<   g Zded<   dZded<   y)EnrichmentPayloadz>Enrichment data written by PostCallEnricher after a call ends.rH   r   transcript_rawr   enriched_entitiesdecisions_mader   r   	key_factskinan_directivesmemory_vector_idN)r   r   r   r   rk   r   rl   rm   r   r   rn   ro   rp   r   r   r    rj   rj   w  sY    HNC#%y% "NI" L) cIy"$i$cr   rj   c                  &    e Zd ZU dZded<   ded<   y)EnrichmentResponsezDResponse envelope for PATCH /conversations/{conversation_id}/enrich.r   r,   rW   Nr   r   r   r    rr   rr     s    NKr   rr   z'/conversations/{conversation_id}/enrichc                  K   |t        d|       S 	 |j                         }|j                  d|j                  t	        j
                  |j                        t	        j
                  |j                        t	        j
                  |j                        |j                  t	        j
                  |j                        t	        j
                  |j                        |j                  | f	       |j                         }|j                          |j                          |t!        dd      	 t        d|       S # t         $ r  t"        $ r t!        dd      w xY ww)	u'  
    Updates an existing royal_conversations row with enrichment data.

    Triggered by PostCallEnricher once a call has completed and the transcript
    has been processed.  All eight enrichment fields are written in a single
    UPDATE statement.  ended_at is set to NOW() (current UTC time in Postgres).

    Args:
        conversation_id: UUID of the conversation row to update.
        payload: EnrichmentPayload containing all enrichment fields.
        pg_conn: Injected Postgres connection (None → graceful degradation).

    Returns:
        200 EnrichmentResponse with status="enriched" and conversation_id echoed.

    Errors:
        404 Not Found — when conversation_id does not match any row
                        (detected because RETURNING id yields no row).
        500 Internal Server Error — on unexpected database errors.

    Graceful degradation:
        When pg_conn is None (no database available) the endpoint returns 200
        with status="enriched" so callers can continue in offline / dev mode.

    SQL executed (single UPDATE):
        UPDATE royal_conversations
        SET transcript_raw      = %s,
            enriched_entities   = %s,   -- JSON array
            decisions_made      = %s,   -- JSON array
            action_items        = %s,   -- JSON array
            emotional_signal    = %s,
            key_facts           = %s,   -- JSON array
            kinan_directives    = %s,   -- JSON array
            memory_vector_id    = %s,
            ended_at            = NOW()
        WHERE id = %s
        RETURNING id
    enriched)r,   rW   am  UPDATE royal_conversations
               SET transcript_raw = %s, enriched_entities = %s,
                   decisions_made = %s, action_items = %s,
                   emotional_signal = %s, key_facts = %s,
                   kinan_directives = %s, memory_vector_id = %s,
                   ended_at = NOW()
               WHERE id = %s
               RETURNING idi  zConversation not foundr^   i  zDatabase error)rr   r8   r9   rk   r`   ra   rl   rm   r   r   rn   ro   rp   fetchonerb   r;   r   rc   )rW   re   r@   r8   results        r    enrich_conversationrw     s0    X !_UU F! &&

7445

7112

7//0((

7,,-

7334((
	
* ">C8PQQ  ZQQ   F4DEEFs   EDD: ,E: EE__main__z0.0.0.0i="  )hostport)returnr   )r{   rS   )r?   intr@   r   r{   r"   )rI   r   r*   r   r{   rS   )re   rQ   r@   r   r{   rV   )rW   r   re   rj   r@   r   r{   rr   )%r   
__future__r   r`   r   r   typingr   r   uuidr   fastapir	   r
   r   pydanticr   appr   r"   r%   rL   r2   rD   rF   rO   rQ   rV   postrh   rj   rr   patchrw   r   uvicornrunr   r   r    <module>r      s  #J #  '    3 3  %w7) -) -	"  , 	 1LM,-DD
DDDD !DD NDDX	" 	 ! )*&&& 
& "&l	   
<VW ,-A&AA  A XAb
	 
  4EWX ,-QRQRQR QR 	QR YQR@ zGKK)$/ r   