Insights

Overview

Insights tools provide structured, opinionated analytics about an entity type. They follow the naming pattern of a Get prefix and an Insights suffix with a singular entity name in between, such as GetConnectionRequestInsights. Unlike a Summary tool, the shape of the result is decided by you, not by the language model. Inputs are usually just a few optional filters. In addition to aggregate counts, an insights result often includes curated extras, such as the top ten connectors or the oldest unconnected requests.

Filter and Aggregate

Start by building the query and applying any optional filters, then perform the aggregation in SQL. As with a Summary tool, group by every dimension you will need and select the counts into a small POCO. This keeps the heavy lifting in SQL and lets you compose the final result quickly in memory.

[Description( "Returns the insights of connection requests." )]
[AgentPurpose( "Retrieves a set of insights into connection requests." )]
[AgentToolGuid( "51e14e2d-09a4-440e-9e7d-df1bf22bd918" )]
public AgentToolResult GetConnectionRequestInsights(
    string connectionOpportunityIdKey = null,
    string campusIdKey = null )
{
    var helper = new AgentToolHelper( AgentRequestContext, _logger );

    var query = new ConnectionRequestService( AgentRequestContext.RockContext )
        .Queryable()
        .Where( cr => !cr.ConnectedDateTime.HasValue );

    query = helper.WhereOptionalIdKey( query, cr => cr.ConnectionOpportunityId, connectionOpportunityIdKey );
    query = helper.WhereOptionalIdKey( query, cr => cr.CampusId, campusIdKey );

    if ( helper.HasErrors )
    {
        return helper.ErrorResult;
    }

    // Aggregate in SQL into a set of counts per combination of values.
    var groupCounts = query
        .GroupBy( cr => new
        {
            cr.ConnectorPersonAliasId,
            cr.ConnectionStatusId,
        } )
        .Select( cr => new InsightsGroupCount
        {
            ConnectorPersonAliasId = cr.Key.ConnectorPersonAliasId,
            ConnectionStatusId = cr.Key.ConnectionStatusId,
            Count = cr.Count(),
        } )
        .ToList();

    var insights = GetInsightsResult( groupCounts );

    return Success( insights ).WithoutHistoryContent();
}

Build and Return the Result

A helper method turns the raw counts into the opinionated result object. Here we compute totals, a breakdown by status, and the top connectors. Because the payload is decided entirely by you, there is no dimension argument. Use a small cached state object (a dictionary of Id to Name) rather than repeatedly calling the cache while building the result.

private ConnectionRequestInsightsResult GetInsightsResult( List<InsightsGroupCount> groupCounts )
{
    var state = GetInsightsState( groupCounts );

    var insights = new ConnectionRequestInsightsResult
    {
        ActiveCount = groupCounts.Sum( gc => gc.Count ),
        UnassignedCount = groupCounts
            .Where( gc => !gc.ConnectorPersonAliasId.HasValue )
            .Sum( gc => gc.Count ),
        CountByStatus = groupCounts
            .GroupBy( gc => gc.ConnectionStatusId )
            .Select( g => new SummaryGroupResult
            {
                Id = g.Key,
                Name = state.ConnectionStatuses[g.Key],
                Total = g.Sum( gc => gc.Count ),
            } )
            .ToList(),
    };

    // Include a curated extra: the ten busiest connectors.
    // (Query the top connector person aliases and project to PersonResult.)

    return insights;
}

Note

Insights differ from Summary tools. A Summary tool lets the language model choose the primary grouping dimension, while an Insights tool always returns the same structured shape. Insights results are usually large, so return them with WithoutHistoryContent() to keep them out of chat history.