
    `gj                        d Z ddl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
mZmZ ddlmZmZmZmZ 	 	 ddddddde
ee	f   ez  d	ed
edededee   dee   deeef   fdZddee   dedee   fdZ G d d      Zy)ua  
Session Insights Engine for Hermes Agent.

Analyzes historical session data from the SQLite state database to produce
comprehensive usage insights — token consumption, cost estimates, tool usage
patterns, activity trends, model/platform breakdowns, and session metrics.

Inspired by Claude Code's /insights command, adapted for Hermes Agent's
multi-platform architecture with additional cost estimation and platform
breakdown capabilities.

Usage:
    from agent.insights import InsightsEngine
    engine = InsightsEngine(db)
    report = engine.generate(days=30)
    print(engine.format_terminal(report))
    N)Counterdefaultdict)datetime)AnyDictListOptional)CanonicalUsageestimate_usage_costformat_duration_compacthas_known_pricingcache_read_tokenscache_write_tokensproviderbase_urlsession_or_modelinput_tokensoutput_tokensr   r   r   r   returnc                   t        | t              r| }|j                  d      xs d}t        |j                  d      xs d|j                  d      xs d|j                  d      xs d|j                  d      xs d      }	|j                  d	      }|j                  d
      }n| xs d}t        ||||      }	t	        ||	||      }
t        |
j                  xs d      |
j                  fS )z?Estimate the USD cost for a session row or a model/token tuple.model r   r   r   r   r   )r   r   r   r   billing_providerbilling_base_url)r   r           )
isinstancedictgetr
   r   float
amount_usdstatus)r   r   r   r   r   r   r   sessionr   usageresults              A/root/.hermes/venv/lib/python3.12/site-packages/agent/insights.py_estimate_costr'   $   s     "D)"G$* ^49!++o6;!%kk*=>C!&{{+?@EA	
 ;;12;;12 &B%'/1	
 !	F "")c*FMM99    values	max_widthc                     | rt        |       nd}|dk(  r| D cg c]  }d c}S | D cg c]'  }|dkD  rdt        dt        ||z  |z              z  nd) c}S c c}w c c}w )z7Create simple horizontal bar chart strings from values.   r   r   u   █)maxint)r)   r*   peak_vs        r&   
_bar_chartr2   M   se     3v;aDqy"#q##PVW1QEC3q4x)3455BFWW $Ws
   	A,Ac            
          e Zd ZdZd Zd#dededeeef   fdZ	dZ
d	e
 d
Zd	e
 dZd$dededee   fdZd$dededee   fdZd$dededee   fdZd$dededefdZ	 d$dee   dedeee      defdZdZdZd$dededee   fdZ	 d$dee   dededee   fdZdee   dee   fdZdee   dee   fdZdee   deeef   fdZdee   defdZdee   dee   fdZd edefd!Zd edefd"Zy)%InsightsEnginez
    Analyzes session history and produces usage insights.

    Works directly with a SessionDB instance (or raw sqlite3 connection)
    to query session and message data.
    c                 4    || _         |j                  | _        y)z
        Initialize with a SessionDB instance.

        Args:
            db: A SessionDB instance (from hermes_state.py)
        N)db_conn)selfr6   s     r&   __init__zInsightsEngine.__init__]   s     XX
r(   Ndayssourcer   c                 *   t        j                          |dz  z
  }| j                  ||      }| j                  ||      }| j                  ||      }| j	                  ||      }|s||di g g g dddddg di g d
S | j                  |||      }| j                  |||      }	| j                  |      }
| j                  |      }| j                  |      }| j                  |      }| j                  |      }||dt        j                          |	||
||||dS )	z
        Generate a complete insights report.

        Args:
            days: Number of days to look back (default: 30)
            source: Optional filter by source platform

        Returns:
            Dict with all computed insights
        iQ Tr   total_skill_loadstotal_skill_editstotal_skill_actionsdistinct_skills_usedsummary
top_skills)
r:   source_filteremptyoverviewmodels	platformstoolsskillsactivitytop_sessionsF)r:   rE   rF   generated_atrG   rH   rI   rJ   rK   rL   rM   )time_get_sessions_get_tool_usage_get_skill_usage_get_message_stats_compute_model_breakdown_compute_overview_compute_platform_breakdown_compute_tool_breakdown_compute_skill_breakdown_compute_activity_patterns_compute_top_sessions)r8   r:   r;   cutoffsessions
tool_usageskill_usagemessage_statsrH   rG   rI   rJ   rK   rL   rM   s                  r&   generatezInsightsEngine.generateg   sV    u- %%ff5))&&9
++FF;//?!' ./-./001	  #%  "% , ..xH))(M6J44X>	,,Z8..{;228<11(; # IIK " (
 	
r(   a  id, source, model, started_at, ended_at, message_count, tool_call_count, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, billing_provider, billing_base_url, billing_mode, estimated_cost_usd, actual_cost_usd, cost_status, cost_source, api_call_countzSELECT zL FROM sessions WHERE started_at >= ? AND source = ? ORDER BY started_at DESCz= FROM sessions WHERE started_at >= ? ORDER BY started_at DESCr[   c                     |r)| j                   j                  | j                  ||f      }n'| j                   j                  | j                  |f      }|j	                         D cg c]  }t        |       c}S c c}w )z&Fetch sessions within the time window.)r7   execute_GET_SESSIONS_WITH_SOURCE_GET_SESSIONS_ALLfetchallr   r8   r[   r;   cursorrows        r&   rP   zInsightsEngine._get_sessions   sd    ZZ''(F(FQWHXYFZZ''(>(>	JF%+__%67cS	777s   %A:c                    t               }|r| j                  j                  d||f      }n| j                  j                  d|f      }|j                         D ]  }||d   xx   |d   z  cc<    |r| j                  j                  d||f      }n| j                  j                  d|f      }t               }|j                         D ]  }	 |d   }t	        |t
              rt        j                  |      }t	        |t              rL|D ]G  }	t	        |	t              r|	j                  di       ni }
|
j                  d	      }|s;||xx   d
z  cc<   I  |s|r|}n]|r[|rYt        |      t        |      z  }t               }|D ]1  }t        |j                  |d      |j                  |d            ||<   3 |}|j!                         D cg c]
  \  }}||d c}}S # t        j                  t        t        f$ r Y 7w xY wc c}}w )a  Get tool call counts from messages.

        Uses two sources:
        1. tool_name column on 'tool' role messages (set by gateway)
        2. tool_calls JSON on 'assistant' role messages (covers CLI where
           tool_name is not populated on tool responses)
        aR  SELECT m.tool_name, COUNT(*) as count
                   FROM messages m
                   JOIN sessions s ON s.id = m.session_id
                   WHERE s.started_at >= ? AND s.source = ?
                     AND m.role = 'tool' AND m.tool_name IS NOT NULL
                   GROUP BY m.tool_name
                   ORDER BY count DESCaA  SELECT m.tool_name, COUNT(*) as count
                   FROM messages m
                   JOIN sessions s ON s.id = m.session_id
                   WHERE s.started_at >= ?
                     AND m.role = 'tool' AND m.tool_name IS NOT NULL
                   GROUP BY m.tool_name
                   ORDER BY count DESC	tool_namecountzSELECT m.tool_calls
                   FROM messages m
                   JOIN sessions s ON s.id = m.session_id
                   WHERE s.started_at >= ? AND s.source = ?
                     AND m.role = 'assistant' AND m.tool_calls IS NOT NULLzSELECT m.tool_calls
                   FROM messages m
                   JOIN sessions s ON s.id = m.session_id
                   WHERE s.started_at >= ?
                     AND m.role = 'assistant' AND m.tool_calls IS NOT NULL
tool_callsfunctionnamer,   r   )rj   rk   )r   r7   rb   re   r   strjsonloadslistr   r   JSONDecodeError	TypeErrorAttributeErrorsetr-   most_common)r8   r[   r;   tool_countsrg   rh   cursor2tool_calls_countscallscallfuncrn   	all_toolsmergedtoolrk   s                   r&   rQ   zInsightsEngine._get_tool_usage   s8    i ZZ''*  	F ZZ''* 		F ??$ 	:CK()S\9)	:
 jj((N
  G jj((N
 	G $I##% 	CL)eS) JJu-EeT* % 9;EdD;Qtxx
B7WY#xx/-d3q83	9	  0+K. K(3/@+AAIYF! ]";??4#;=N=R=RSWYZ=[\t] K
  +668
e /
 	
% (()^D $
s   A6GGHH Hc           
         i }|r| j                   j                  d||f      }n| j                   j                  d|f      }|j                         D ]N  }	 |d   }t        |t              rt        j                  |      }t        |t              s@	 |d   }|D ]  }t        |t              s|j                  di       }	|	j                  d      }
|
dvr=|	j                  d      }t        |t              r	 t        j                  |      }t        |t              s|j                  d      }t        |t              r|j                         s|j                  ||d	d	d
d      }|
dk(  r|dxx   dz  cc<   n|dxx   dz  cc<   ||d   	||d   kD  s||d<    Q t        |j                               S # t
        j                  t        f$ r Y w xY w# t
        j                  t        f$ r Y _w xY w)z2Extract per-skill usage from assistant tool calls.a  SELECT m.tool_calls, m.timestamp
                   FROM messages m
                   JOIN sessions s ON s.id = m.session_id
                   WHERE s.started_at >= ? AND s.source = ?
                     AND m.role = 'assistant' AND m.tool_calls IS NOT NULLzSELECT m.tool_calls, m.timestamp
                   FROM messages m
                   JOIN sessions s ON s.id = m.session_id
                   WHERE s.started_at >= ?
                     AND m.role = 'assistant' AND m.tool_calls IS NOT NULLrl   	timestamprm   rn   >   
skill_viewskill_manage	argumentsr   N)skill
view_countmanage_countlast_used_atr   r   r,   r   r   )r7   rb   re   r   ro   rp   rq   rr   rs   rt   r   r   strip
setdefaultr)   )r8   r[   r;   skill_countsrg   rh   r{   r   r|   r}   rj   args
skill_nameentrys                 r&   rR   zInsightsEngine._get_skill_usage#  s   24ZZ''N
  F ZZ''N
 	F ??$ 1	6CL)eS) JJu-E!%. /
 K(I &6!$-xx
B/ HHV,	$BBxx,dC(!#zz$/ "$-!XXf-
!*c2*:J:J:L$//!+&'()(,	 ,,'1,'.)Q.)(.)1Y~AV5V,5E.)M&61	6f L'')**Y (()4   !00)< ! !s$   :F<8G<GGG98G9c                     |r| j                   j                  d||f      }n| j                   j                  d|f      }|j                         }|rt        |      S dddddS )z!Get aggregate message statistics.a  SELECT
                     COUNT(*) as total_messages,
                     SUM(CASE WHEN m.role = 'user' THEN 1 ELSE 0 END) as user_messages,
                     SUM(CASE WHEN m.role = 'assistant' THEN 1 ELSE 0 END) as assistant_messages,
                     SUM(CASE WHEN m.role = 'tool' THEN 1 ELSE 0 END) as tool_messages
                   FROM messages m
                   JOIN sessions s ON s.id = m.session_id
                   WHERE s.started_at >= ? AND s.source = ?a  SELECT
                     COUNT(*) as total_messages,
                     SUM(CASE WHEN m.role = 'user' THEN 1 ELSE 0 END) as user_messages,
                     SUM(CASE WHEN m.role = 'assistant' THEN 1 ELSE 0 END) as assistant_messages,
                     SUM(CASE WHEN m.role = 'tool' THEN 1 ELSE 0 END) as tool_messages
                   FROM messages m
                   JOIN sessions s ON s.id = m.session_id
                   WHERE s.started_at >= ?r   )total_messagesuser_messagesassistant_messagestool_messages)r7   rb   fetchoner   rf   s        r&   rS   z!InsightsEngine._get_message_statso  sz    ZZ''?  
F ZZ''. 	
F ootCy 	
!"#a&
 	
r(   r\   r_   rH   c                 h   t        d |D              }t        d |D              }t        d |D              }t        d |D              }||z   |z   |z   }t        d |D              }	t        d |D              }
d}d}t               }t               }d}d}|D ]  }|j                  d	      xs d
}t        |      \  }}||z  }||j                  d      xs dz  }d|v r|j	                  d      d   n|xs d}|dk(  r|dz  }n
|dk(  r|dz  }t        ||j                  d      |j                  d            r|j                  |       |j                  |        |ret        d |D              }t        d |D              }t        d |D              }t        d |D              }t        d |D              }||z   |z   |z   }g }|D ]D  }|j                  d      }|j                  d      }|s(|s+||kD  s1|j                  ||z
         F |rt        |      dz  nd}|rt        |      t        |      z  nd}|D cg c]  }|j                  d      s|d    }}|rt        |      nd}|rt        |      nd}i dt        |      d|
d|	d|d |d!|d"|d#|d$|d%|d&|d'|d(|r|
t        |      z  ndd)|r|t        |      z  ndd*|j                  d*      xs dd+|j                  d+      xs dd,|j                  d,      xs d||t        |      t        |      ||d-S c c}w ).z'Compute high-level overview statistics.c              3   F   K   | ]  }|j                  d       xs d  ywr   r   Nr   .0ss     r&   	<genexpr>z3InsightsEngine._compute_overview.<locals>.<genexpr>  s!     G!%%/414G   !c              3   F   K   | ]  }|j                  d       xs d  ywr   r   Nr   r   s     r&   r   z3InsightsEngine._compute_overview.<locals>.<genexpr>  s!     I115516Q6Ir   c              3   F   K   | ]  }|j                  d       xs d  ywr   r   Nr   r   s     r&   r   z3InsightsEngine._compute_overview.<locals>.<genexpr>  s"     Q1quu%89>Q>Qr   c              3   F   K   | ]  }|j                  d       xs d  ywr   r   Nr   r   s     r&   r   z3InsightsEngine._compute_overview.<locals>.<genexpr>  s"     SQ&: ; @q @Sr   c              3   F   K   | ]  }|j                  d       xs d  yw)tool_call_countr   Nr   r   s     r&   r   z3InsightsEngine._compute_overview.<locals>.<genexpr>  s"     Oquu%67<1<Or   c              3   F   K   | ]  }|j                  d       xs d  yw)message_countr   Nr   r   s     r&   r   z3InsightsEngine._compute_overview.<locals>.<genexpr>  s!     KQQUU?38q8Kr   r   r   r   r   actual_cost_usd/unknownincludedr,   r   r   c              3   X   K   | ]"  }t        |j                  d       xs d       $ yw)costr   N)r    r   r   ms     r&   r   z3InsightsEngine._compute_overview.<locals>.<genexpr>  s"     IQU155=#7C8I   (*c              3   X   K   | ]"  }t        |j                  d       xs d       $ ywr   r.   r   r   s     r&   r   z3InsightsEngine._compute_overview.<locals>.<genexpr>  s#     N!c!%%"7"<1=Nr   c              3   X   K   | ]"  }t        |j                  d       xs d       $ ywr   r   r   s     r&   r   z3InsightsEngine._compute_overview.<locals>.<genexpr>  s#     PAs155#9#>Q?Pr   c              3   X   K   | ]"  }t        |j                  d       xs d       $ ywr   r   r   s     r&   r   z3InsightsEngine._compute_overview.<locals>.<genexpr>  s$     "XA3quu-@'A'FQ#G"Xr   c              3   X   K   | ]"  }t        |j                  d       xs d       $ ywr   r   r   s     r&   r   z3InsightsEngine._compute_overview.<locals>.<genexpr>  s$     #ZaC.B(C(Hq$I#Zr   
started_atended_at  Ntotal_sessionsr   total_tool_callstotal_input_tokenstotal_output_tokenstotal_cache_read_tokenstotal_cache_write_tokenstotal_tokensestimated_costactual_costtotal_hoursavg_session_durationavg_messages_per_sessionavg_tokens_per_sessionr   r   r   )date_range_startdate_range_endmodels_with_pricingmodels_without_pricingunknown_cost_sessionsincluded_cost_sessions)sumrv   r   r'   splitr   addappendlenminr-   sorted)r8   r\   r_   rH   total_inputtotal_outputtotal_cache_readtotal_cache_writer   r   r   
total_costr   r   r   r   r   r   r   	estimatedr"   display	durationsstartendr   avg_durationstarted_timestampsr   r   s                                 r&   rU   z InsightsEngine._compute_overview  s    GhGGIIIQQQS(SS"\14DDGXXOhOOK(KK 
!e!$ !!" 	4AEE'N(bE .q 1Iv)#J155!23:s:K.1Ulekk#&r*AS)G#&!+&9$%*% .@(A155I[C\]#''0&**73	4 I&IIJ NvNNKPPPL""XQW"XX ##ZSY#Z Z&58HHK\\L 	 	.AEE,'E%%
#Cu  u-		. 09c)nt+a:Cs9~I6 8@W!155CVaoWW6H312d4F/0D
c(m
n
  0
 !+	

 "<
 &'7
 '(9
 L
 j
 ;
 ;
 #L
 '(X(FXY
 %hlS]&BTU
 ]..?D1
  !-"3"34H"I"NQ!
" ]..?D1#
$ !1,#)*=#>&,-C&D%:&</
 	
	 Xs   ?L/L/ax  SELECT u.session_id, u.model, u.billing_provider, u.billing_base_url, u.api_call_count, u.input_tokens, u.output_tokens, u.cache_read_tokens, u.cache_write_tokens, u.reasoning_tokens, u.estimated_cost_usd, u.actual_cost_usd, u.cost_status, u.cost_source, u.billing_mode FROM session_model_usage u JOIN sessions s ON s.id = u.session_id WHERE s.started_at >= ? AND s.source = ?ag  SELECT u.session_id, u.model, u.billing_provider, u.billing_base_url, u.api_call_count, u.input_tokens, u.output_tokens, u.cache_read_tokens, u.cache_write_tokens, u.reasoning_tokens, u.estimated_cost_usd, u.actual_cost_usd, u.cost_status, u.cost_source, u.billing_mode FROM session_model_usage u JOIN sessions s ON s.id = u.session_id WHERE s.started_at >= ?c                 6   	 |r)| j                   j                  | j                  ||f      }n'| j                   j                  | j                  |f      }|j	                         D cg c]  }t        |       c}S c c}w # t        j                  $ r g cY S w xY w)zFetch per-model usage rows within the window (issue #51607).

        Returns an empty list when the table is missing (e.g. a DB opened by
        older code that never created it) so the caller can fall back to the
        per-session aggregate.
        )r7   rb   _GET_MODEL_USAGE_WITH_SOURCE_GET_MODEL_USAGE_ALLre   r   sqlite3OperationalErrorrf   s        r&   _get_model_usagezInsightsEngine._get_model_usage  s    		++557G ++D,E,EyQ)/):;#DI;;;'' 	I	s$   A$B  &A;8B  ;B   BBc                 8   t        d       ddddfd
}| j                  ||      }t        d       }|D ]  }||d      }dD ]  }	||	xx   ||	   xs dz  cc<    |d	xx   |d	   xs d
z  cc<   |dxx   |d   xs d
z  cc<    ||d   |d   |j                  d      |d   |d   xs d|d   xs d|d   xs d|d   xs d|d   xs d|j                  d      s|j                  d      r|d	   nd|d   |j                  d            }
|
   dxx   |d   xs dz  cc<    |D ]  }||d      }t        d|j                  d      xs d|d   z
        }t        d|j                  d      xs d|d   z
        }t        d|j                  d      xs d|d   z
        }t        d|j                  d      xs d|d   z
        }t        d
t	        |j                  d	      xs d
      |d	   z
        }t        d
t	        |j                  d      xs d
      |d   z
        }t        d|j                  d      xs d|d   z
        }|s|s|s
|s|s|s|s1 ||j                  d      |j                  d      |j                  d      |d   ||||d|||j                  d            }
|
   }|dxx   |z  cc<    |D ]Y  }|j                  d      xs d}|s|j                  d      xs d}d|v r|j                  d      d   n|}|   dxx   |z  cc<   [ g }j                         D ]Q  \  }}d|i|}t        |d         |d<   |j                  dd        |j                  dd       |j                  |       S |j                  d! d"#       |S )$u  Break down token usage and cost by model.

        Tokens and cost are attributed per model from session_model_usage, so a
        session that switched models mid-flight (via ``/model``) splits across
        every model it used instead of dumping everything on the initial model
        (issue #51607). Sessions without per-model rows — e.g. data written
        before this table existed and not yet backfilled — fall back to their
        single recorded (model, billing_provider) aggregate so nothing is lost.

        Tool calls aren't tied to a specific API invocation, so they stay
        attributed to the session's recorded model.
        c                  .    t               dddddddddddS )Nr   r   )r\   r   r   r   r   reasoning_tokensr   	api_callsrl   r   r   )rv    r(   r&   <lambda>z9InsightsEngine._compute_model_breakdown.<locals>.<lambda>'  s%    q1!"! !11S	*
 r(   N)stored_costr   cost_statusc	          	      6   | xs d} d| v r| j                  d      d   n| }|   }|d   j                  |       |dxx   |z  cc<   |dxx   |z  cc<   |dxx   |z  cc<   |dxx   |z  cc<   |d	xx   |z  cc<   |d
xx   ||z   |z   |z   z  cc<   |	t        | |||||xs d |      \  }}nt        |	xs d      }|xs d}|dxx   |z  cc<   |dxx   t        |
xs d      z  cc<   ||d<   t	        | |xs d |      rd|d<   |S |j                  dd       |S )Nr   r   r   r\   r   r   r   r   r   r   r   r   r   r   r   Thas_pricingF)r   r   r'   r    r   r   )r   r   r   
session_idinpout
cache_readcache_write	reasoningr   r   r   display_modeldestimater"   
model_datas                   r&   _accumulatez<InsightsEngine._compute_model_breakdown.<locals>._accumulate.  sh    &YE475LEKK,R0eM *= 9AjMj)n$o#%!"j0""#{2# !Y.!nsZ!7+!EE"#13&0[%-$ & !!34$1	fI!Imk&8S 99%Am (8D(C#'-  !  ]E2  r(   c            	          dddddddddS )Nr   r   )r   r   r   r   r   api_call_countestimated_cost_usdr   r   r   r(   r&   r   z9InsightsEngine._compute_model_breakdown.<locals>.<lambda>O  s    "#s"	,
 r(   r   )r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   cost_sourcer   r   idr   r   r   r   rl   r\   r   Fc                     | d   | d   fS )Nr   r\   r   xs    r&   r   z9InsightsEngine._compute_model_breakdown.<locals>.<lambda>  s    1^#4a
m"D r(   Tkeyreverse)r   r   r   r-   r    r   itemsr   r   r   sort)r8   r\   r[   r;   r   
usage_rowsusage_totalsrtotalsr  r   r   r   r   r   r   residual_costresidual_actualresidual_callsresidual_bucketrl   r   r   r%   datar   r   s                             @r&   rT   z'InsightsEngine._compute_model_breakdown  s^    ! "
 
 %)d	!@ **66:
" $
   	CA%1!L/%BF + sqv{*	+
 '(A.B,C,JsJ($%+<)=)DD%'
A011559K3L,>!2!7a?9K9Pq%&+!Q/C-D-I$%* uu]+quu]/C *+/0EE-0A qM+&!,<*=*BB&-	C6  #	;A!!D'*Fa!%%/41~8NNOCa!%%05A9PPQCAEE-.3!v>Q7RRJ AEE./41?S8TTK  U155!56=#>-./M "U155!23:s;*+,O !AEE*+0qF;K4LLN sjK="ng&8 9()1T7S*k1)+EE-0A /9mOK(N:(G#	;L  	BA016QJEE'N/iE475LEKK,R0eM}%l3zA3	B %++- 	!KE4e,t,E #D$4 5E* ]E2]I6MM% 	! 	DdSr(   c                    t        d       }|D ]  }|j                  d      xs d}||   }|dxx   dz  cc<   |dxx   |j                  d      xs dz  cc<   |j                  d	      xs d}|j                  d
      xs d}|j                  d      xs d}|j                  d      xs d}	|d	xx   |z  cc<   |d
xx   |z  cc<   |dxx   |z  cc<   |dxx   |	z  cc<   |dxx   ||z   |z   |	z   z  cc<   |dxx   |j                  d      xs dz  cc<    |j                         D 
cg c]  \  }
}d|
i| }}
}|j                  d d       |S c c}}
w )z$Break down usage by platform/source.c            	          dddddddddS )Nr   )r\   messagesr   r   r   r   r   rl   r   r   r(   r&   r   z<InsightsEngine._compute_platform_breakdown.<locals>.<lambda>  s    q!Q"#Qa-
 r(   r;   r   r\   r,   r  r   r   r   r   r   r   r   rl   r   platformc                     | d   S )Nr\   r   r  s    r&   r   z<InsightsEngine._compute_platform_breakdown.<locals>.<lambda>  s
    !J- r(   Tr  )r   r   r  r  )r8   r\   platform_datar   r;   r   r   r   r   r   r  r  r%   s                r&   rV   z*InsightsEngine._compute_platform_breakdown  s   # %
   	=AUU8_1	Ff%AjMQMjMQUU?38q8M%%',1C%%(-AC238qJ%% 45:Kn$o#%!"j0""#{2#nsZ!7+!EElOquu%67<1<O	=$ #0"5"5"7
$ *T*
 
 	/>
s   .Er]   c                     |rt        d |D              nd}g }|D ],  }|r|d   |z  dz  nd}|j                  |d   |d   |d       . |S )z<Process tool usage data into a ranked list with percentages.c              3   &   K   | ]	  }|d      yw)rk   Nr   )r   ts     r&   r   z9InsightsEngine._compute_tool_breakdown.<locals>.<genexpr>  s     9!G*9   r   rk   d   rj   )r   rk   
percentage)r   r   )r8   r]   total_callsr%   r  pcts         r&   rW   z&InsightsEngine._compute_tool_breakdown  sj    =Gc9j99Q 	A6A1W:+c1qCMM+7! 	 r(   r^   c                 R   |rt        d |D              nd}|rt        d |D              nd}||z   }g }|D ]I  }|d   |d   z   }|r||z  dz  nd}|j                  |d   |d   |d   |||j                  d      d	       K |j                  d
 d       |||t	        |      d|dS )z3Process per-skill usage into summary + ranked list.c              3   &   K   | ]	  }|d      yw)r   Nr   r   s     r&   r   z:InsightsEngine._compute_skill_breakdown.<locals>.<genexpr>  s     EA,Er  r   c              3   &   K   | ]	  }|d      yw)r   Nr   r   s     r&   r   z:InsightsEngine._compute_skill_breakdown.<locals>.<genexpr>  s     Ga. 1Gr  r   r   r  r   r   )r   r   r   total_countr  r   c                 6    | d   | d   | d   | d   xs d| d   fS )Nr#  r   r   r   r   r   r   r   s    r&   r   z9InsightsEngine._compute_skill_breakdown.<locals>.<lambda>  s4    - ,.!.!&Q'
 r(   Tr  r=   rB   )r   r   r   r  r   )	r8   r^   r>   r?   r@   rD   r   r#  r  s	            r&   rX   z'InsightsEngine._compute_skill_breakdown  s    ITCEEEZ[KVCG;GG\]/2CC
  
	E-n0EEKFY+(;;cA_`Jw#L1 %n 5*( %		. 9 
	 	  	 		
 &7%6':(+K(8	 %
 	
r(   c                    t               }t               }t               }|D ]y  }|j                  d      }|st        j                  |      }||j	                         xx   dz  cc<   ||j
                  xx   dz  cc<   ||j                  d      xx   dz  cc<   { g d}t        d      D 	cg c]  }	||	   |j                  |	d      d }
}	t        d      D 	cg c]  }	|	|j                  |	d      d	 }}	|
rt        |
d
       nd}|rt        |d       nd}t        |      }|rt        |j                               }d}d}t        dt        |            D ]]  }	t        j                  ||	dz
     d      }t        j                  ||	   d      }||z
  j                  dk(  r|dz  }t        ||      }\d}_ nd}|
|||||dS c c}	w c c}	w )z2Analyze activity patterns by day of week and hour.r   r,   z%Y-%m-%d)MonTueWedThuFriSatSun   r   )dayrk      )hourrk   c                     | d   S Nrk   r   r  s    r&   r   z;InsightsEngine._compute_activity_patterns.<locals>.<lambda>  s
    qz r(   r  Nc                     | d   S r3  r   r  s    r&   r   z;InsightsEngine._compute_activity_patterns.<locals>.<lambda>  s
    7 r(   )by_dayby_hourbusiest_daybusiest_houractive_days
max_streak)r   r   r   fromtimestampweekdayr1  strftimeranger-   r   r   keysstrptimer:   )r8   r\   
day_countshour_countsdaily_countsr   tsdt	day_namesiday_breakdownhour_breakdownr8  r9  r:  	all_datescurrent_streakr;  d1d2s                       r&   rY   z)InsightsEngine._compute_activity_patterns   s   Y
iy 	7A|$B''+Brzz|$)$ A% Z01Q61	7 F	 1X
 aL:>>!Q+?@
 
 2Y
 A!67
 
 GTc--ABY]HVs>/CD\` ,' |0023INJ1c)n- '&&yQ'7D&&y|Z@G>>Q&"a'N!$Z!@J%&N' J $%&(&$
 	
C


s   .G	Gc           	      l   g }|D cg c]&  }|j                  d      r|j                  d      r|( }}|rct        |d       }|d   |d   z
  }|j                  d|d   dd t        |      t	        j
                  |d         j                  d	      d
       t        |d       }|j                  d      xs ddkD  rZ|j                  d|d   dd |d    d|j                  d      r't	        j
                  |d         j                  d	      ndd
       t        |d       }|j                  d      xs d|j                  d      xs dz   }	|	dkD  rX|j                  d|d   dd |	dd|j                  d      r't	        j
                  |d         j                  d	      ndd
       t        |d       }
|
j                  d      xs ddkD  rZ|j                  d|
d   dd |
d    d|
j                  d      r't	        j
                  |
d         j                  d	      ndd
       |S c c}w )z<Find notable sessions (longest, most messages, most tokens).r   r   c                     | d   | d   z
  S )Nr   r   r   r%  s    r&   r   z6InsightsEngine._compute_top_sessions.<locals>.<lambda>F  s    q}q> r(   r4  zLongest sessionr   N   %b %d)labelr   valuedatec                 ,    | j                  d      xs dS )Nr   r   r   r%  s    r&   r   z6InsightsEngine._compute_top_sessions.<locals>.<lambda>Q  s    o0F0K! r(   r   r   zMost messages msgs?c                 X    | j                  d      xs d| j                  d      xs dz   S )Nr   r   r   r   r%  s    r&   r   z6InsightsEngine._compute_top_sessions.<locals>.<lambda>]  s(    15505A!%%:P:UTUV r(   r   r   zMost tokens, tokensc                 ,    | j                  d      xs dS )Nr   r   r   r%  s    r&   r   z6InsightsEngine._compute_top_sessions.<locals>.<lambda>i  s    7H1I1NQ r(   r   zMost tool callsz calls)r   r-   r   r   r   r<  r>  )r8   r\   topr   sessions_with_durationlongestdur	most_msgsmost_tokenstoken_total
most_toolss              r&   rZ   z$InsightsEngine._compute_top_sessions:  s}     "
uu\"quuZ'8 "
 "
 "&?G *%(==CJJ*%dmCR005 ..w|/DENNwW	  &KL	MM/*/a14JJ('ocr2%o67u=]f]j]jkw]x..y/FGPPQXY  B	  V
 #~6;!P_@`@edef?JJ&)$/4'?'2_j_n_no{_|..{</HIRRSZ[  CF	  'NO
NN,-2a7JJ*(.s3&'89:&A^h^l^lmy^z..z,/GHQQRYZ  AD	  
g"
s   +H1reportc                 >   |j                  d      r7|j                  dd      }|j                  d      r	d|d    dnd}d| d	| d
S g }|d   }|d   }|j                  d      }|j                  d       |j                  d       |j                  d       d| d	}|r	|d| dz  }dt        |      z
  dz
  }|dz  }	||	z
  }
|j                  dd|	z   d| dd|
z   d       |j                  d       |j                  d       |j                  d      r|j                  d      rvt        j                  |d         j                  d      }t        j                  |d         j                  d      }|j                  d| d|        |j                  d       |j                  d       |j                  d       |j                  d|d   dd |d!   d"       |j                  d#|d$   d%d&|d'   d"       |j                  d(|d)   d%d*|d+   d"       |j                  d,|d-   d"       |d.   d/kD  r3|j                  d0t        |d.   d1z        d2d3t        |d4                 |j                  d5|d6   d7       |j                  d       |d8   r|j                  d9       |j                  d       |j                  d:d;d<dd=d>dd?d@       |d8   D ]-  }|dA   dBdC }|j                  d:|d<d|dD   d>d|d-   dE       / |j                  d       t        |dF         dGkD  s|dF   r|dF   d/   dH   dIk7  r|j                  dJ       |j                  d       |j                  d:dKdLdd=d>ddMdNdd?dO       |dF   D ]/  }|j                  d:|dH   dLd|dD   d>d|dP   dQd|d-   dR       1 |j                  d       |dS   r|j                  dT       |j                  d       |j                  d:dUdVddWd>ddXd>       |dS   dBdY D ])  }|j                  d:|dZ   dVd|d[   d\d|d]   d^dX       + t        |dS         dYkD  r$|j                  d_t        |dS         dYz
   d`       |j                  d       |j                  dai       }|j                  dbg       }|r#|j                  dc       |j                  d       |j                  d:dddVddedfddgdfddhdi       |dBdj D ]i  }dk}|j                  dl      r't        j                  |dl         j                  dm      }|j                  d:|dn   dBdC dVd|do   dpd|dq   dpd|di       k |j                  dri       }|j                  ds|j                  dtd/       du|j                  dvd/      d"dw|j                  dxd/      d"       |j                  d       |j                  dyi       }|j                  dz      r|j                  d{       |j                  d       |dz   D cg c]  }|d[   	 }}t        |dY|      }t        |dz         D ]+  \  }}||   }|j                  d:|d}    d:|d~d|d[           - |j                  d       t        |d   d d      }|D cg c]  }|d[   d/kD  s| c}dBd }|r_g }|D ]5  }|d   }|dk  rdnd} |dz  xs d}!|j                  |! |  d|d[    d       7 |j                  ddj                  |              |j                  d      r|j                  d|d           |j                  d      r |d   dGkD  r|j                  d|d    d       |j                  d       |j                  d      ri|j                  d       |j                  d       |d   D ].  }"|j                  d:|"d   dd|"d   dd|"d    d|"d    d	       0 |j                  d       dj                  |      S c c}w c c}w )z6Format the insights report for terminal display (CLI).rF   r:      rE   z
 (source: )r   z   No sessions found in the last z days.rG   u     ╔══════════════════════════════════════════════════════════╗uD     ║                    📊 Hermes Insights                    ║zLast  (:      u     ║ u   ║u     ╚══════════════════════════════════════════════════════════╝r   r   z	%b %d, %Yz
  Period:     — u     📋 Overviewu     ────────────────────────────────────────────────────────z  Sessions:          r   z<12z  Messages:        r   rZ  z  Tool calls:        r   z<12,z  User messages:   r   z  Input tokens:      r   z  Output tokens:   r   z  Total tokens:      r   r   r   z  Active time:       ~r   z<11z  Avg session:     ~r   z  Avg msgs/session:  r   .1frH   u     🤖 Models Used  Modelz<30Sessionsz>8Tokensz>12r   N   r\   z>12,rI   r,   r  cliu     📱 PlatformsPlatformz<14Messagesz>10z>14r  z>10,z>14,rJ   u     🔧 Top ToolsToolz<28Calls%   r   rk   z>8,r  z>7.1fz
  ... and z more toolsrK   rD   u     🧠 Top SkillsSkillLoadsz>7Editsz	Last usedz>11
   u   —r   rR  r   r   z>7,r   rC   z  Distinct skills: rA   z	  Loads: r>   z	  Edits: r?   rL   r6  u     📅 Activity Patterns)r*   r/  z<15r7  c                     | d   S r3  r   r  s    r&   r   z0InsightsEngine.format_terminal.<locals>.<lambda>  s
    aj r(   Tr     r1     AMPMz  Peak hours: z, r:  z  Active days: r;  z  Best streak:  consecutive daysrM   u     🏆 Notable SessionsrS  z<20rT  z<18rU  r   
)r   r   r   r   r<  r>  r   r2   	enumerater   join)#r8   re  r:   srclineso
src_filterperiod_labelpaddingleft_pad	right_pad	start_strend_strr   
model_namepr  rK   rD   r   	last_usedrC   actr   
day_valuesbarsrH  bar
busy_hoursh	hour_strshrampm
display_hrrE  s#                                      r&   format_terminalzInsightsEngine.format_terminalx  s	   ::g::fb)D=CZZ=XJvo67q9^`C5dV5QGG:f~ZZ0
 	R  N  	O[\tfE*bA..Ls<((1,a<h&	uS8^,Al^1S9_<MSQR  N  	OR 55#$/?)@ ..q1C/DENN{[I,,Q/?-@AJJ;WGLL:i[gY?@LL 	&'&',Q/?-@,EEXYZ[kYlmnXopq,Q/A-B4,HH[\]^m\nop[qrs,Q/C-DT,JJ]^_`u^vwx]yz{,Q~->q,ABC]aLL12I!MJZ]aJa2bcf1gg{  }T  UV  Wm  Un  }o  |p  q  r,Q/I-J3,OPQR (LL-.LL*+LL2gc]!Jr?!HS>JKH% `wZ_
r*S!11Z=2DAaFWX\E]^_` LL vk"#a'F;,?F;DWXYDZ[eDfjoDoLL+,LL*+LL2j-Qz"oQz#>NaPXY\~^_K( xr!J-!4Aa
mB5Gq:W[H\\]^_`n^opt]uvwxLL '?LL+,LL*+LL2fS\72,aBx@AG_Sb) ]r!F)C!G*S1A1\?SXBYYZ[\]6'?#b(z#fWo*>*C)DKPQLL Hb)ZZb1
LL,-LL*+LL2gc]!GB<qAkRUEVWX#CR !	99^, ( 6 6u^7L M V VW^ _Iw,S153Fs2K1USaMbcfLgghirsvhwx	 jjB/GLL%gkk2H!&L%M N!++&91=a@ A!++&91=a@B
 LL jjR(778LL34LL*+ /2(m<!G*<J<jB7D!#h-0 F11gr!E(2c#Ya'
|DEF LL  I4HRVWJ%/B1W:>!B2AFJ	# KA6B#%74D!#bBJ$$
|D6AgJ<q%IJ	K
 ~dii	.B-CDEww}%s=/A.BCDww|$\):Q)>s</@.AARSTLL ::n%LL23LL*+^, jr"W+c!2!BwK3DBr&zlRTUWXdUeTffghijLLyyE = Cs   )`'`5`c                 .   |j                  d      r|j                  dd      }d| dS g }|d   }|d   }|j                  d| d       |j                  d	|d
    d|d   dd|d   d       |j                  d|d   dd|d   dd|d   dd       |d   dkD  r2|j                  dt        |d   dz         dt        |d                 |j                  d       |d   rW|j                  d       |d   d d! D ]*  }|j                  d"|d#   d d$  d%|d&    d'|d   dd(       , |j                  d       t        |d)         d*kD  rQ|j                  d+       |d)   D ]'  }|j                  d"|d,    d%|d&    d'|d-   dd.       ) |j                  d       |d/   rU|j                  d0       |d/   d d1 D ](  }|j                  d"|d2    d%|d3   dd4|d5   d6d7       * |j                  d       |j                  d8i       }|j                  d9      r|j                  d:       |d9   d d! D ]g  }	d}
|	j                  d;      r*d<t	        j
                  |	d;         j                  d=       }
|j                  d"|	d>    d%|	d?   dd@|	dA   ddB|
        i |j                  d       |j                  dCi       }|j                  dD      r|j                  dE      r|dE   dF   }|dGk  rdHndI}|dGz  xs dG}|j                  dJ|dD   dK    dL|dD   d3    dM| | dN|dE   d3    dO
       |j                  dP      r|j                  dQ|dP           |j                  dRd      d*kD  r|j                  dS|dR    dT       dUj                  |      S )Vz;Format the insights report for gateway/messaging (shorter).rF   r:   rg  zNo sessions found in the last z days.rG   u"   📊 **Hermes Insights** — Last z days
z**Sessions:** r   z | **Messages:** r   rZ  z | **Tool calls:** r   z**Tokens:** r   z (in: r   z / out: r   rh  r   r   z**Active time:** ~r   z | **Avg session:** ~r   r   rH   u   **🤖 Models:**Nr  rp  r      rn  r\   z sessions, r[  rI   r,   u   **📱 Platforms:**r  r  rW  rJ   u   **🔧 Top Tools:**   r   rk   z calls (r  ro  z%)rK   rD   u   **🧠 Top Skills:**r   z, last used rR  r   r   z loads, r   z editsrL   r8  r9  r1  r  r  r  u   **📅 Busiest:** r/  zs (z sessions), rj  z
 sessions)r:  z**Active days:** r;  z**Best streak:** r  r  )r   r   r   r   r   r<  r>  r  )r8   re  r:   r  r  r   r  r  rK   r   suffixr  r  r  r  s                  r&   format_gatewayzInsightsEngine.format_gateway  s   ::g::fb)D3D6@@:f~9$wGH 	~a(8&9%::KAN^L_`aKbbuvw  yK  wL  MN  vO  P  	Q|An$5a#8qAU?VWX>YYabcdybz{|a}}~  	A]aLL-.EaFVY]F].^-__t  vM  NO  Pf  Ng  vh  ui  j  kR (LL+,H%bq) pr!G*Sb/!2%*kRSTbRcdeQffmnopLL vk"#a'LL./K( hr!J-a
m_KPQR\P]^_O``efghLL '?LL./G_Ra( ar!F)E!G*Qx,X[G\\^_`aLLHb)::l#LL/0-bq1 99^,+H,B,B5CX,Y,b,bcj,k+lmFw(eL.A!-DHUSaMbcdLeeklrkst	 LL jjR(77=!cggn&=^$V,B74DbBJLL-c-.@.G-HCP]L^_fLgKhhtu  uA  BF  AG  GI  JM  N\  J]  ^e  Jf  Ig  gq  r  sww}%0]1C0DEHww|Q'!+0\1B0CCTUVyyr(   )rg  N)N) __name__
__module____qualname____doc__r9   r.   ro   r   r   r`   _SESSION_COLSrc   rd   r    r   rP   rQ   rR   rS   r	   rU   r   r   r   rT   rV   rW   rX   rY   rZ   r  r  r   r(   r&   r4   r4   U   sL   >
S >
s >
d38n >
JQM - !$ 	$  - !$ 	$ 8E 83 8$t* 8Z
e Z
S Z
DJ Z
xJ+u J+c J+T$Z J+X
 
 
t 
P (,	\
t*\
 \
 d$	\

 
\
~	4 !	# u c T$Z & BFRT
R,1R;>R	dRhDJ 4: >$t* d &
DJ &
4S> &
P8
4: 8
$ 8
t8d4j 8T$Z 8|J d J s J X@ T @ c @ r(   r4   )r   r   )   )r  rp   r   rO   collectionsr   r   r   typingr   r   r   r	   agent.usage_pricingr
   r   r   r   ro   r.   tupler    r'   r2   r4   r   r(   r&   <module>r     s   $    ,  , ,  $:
 ""$:38ns*$:$: $:
 $: $: sm$: sm$: 5#:$:RXtCy XS X$s) Xo  o r(   