Files
kandev/apps/backend/internal/analytics/dto/dto.go
T
João Salavisa 672f2b1f26 feat(stats): add avg turn duration and messages per turn (#1328)
* feat(stats): add avg turn duration and messages per turn to averages card

Adds two new metrics to the workspace stats Averages card: mean turn
duration and mean message count per turn. Both exclude outliers — turns
shorter than 1s (no-op/aborted), longer than 1h (zombie turns where
completed_at was backfilled across an agent restart), and turns with no
messages — so the displayed means reflect typical work rather than tail
skew. On a ~4k-turn sample the filter drops avg duration from ~1062s to
~289s.

* fix(stats): exclude exact 1h turns and use Tooltip for averages help text

Address PR feedback: the BETWEEN bound was inclusive so turns exactly at
the 1h ceiling were retained, contradicting the documented half-open
range. Also swap the title attribute on the new Averages rows for the
shared Tooltip component so the help text is reachable via keyboard and
screen readers.

* fix(stats): align turn-row zero state with em-dash convention

Address PR feedback: avg_messages_per_turn rendered "0.0" while
avg_turn_duration_ms rendered "—" under the same empty-data condition.
Also corrects the GetGlobalStats docstring to acknowledge that
clean_turn_agg adds one correlated per-turn lookup on top of the four
single-scan CTEs.

* test(stats): cover exact 1h turn boundary in clean-turn exclusion test

Add a turn of exactly 3,600,000ms (cleanTurnMaxDurationMs) to
TestGetGlobalStats_CleanTurnExcludesOutliers so the exclusive upper
bound is exercised directly, not just via the 2h zombie turn that
sits well above the boundary.

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
2026-06-11 13:45:16 +01:00

102 lines
4.3 KiB
Go

package dto
// TaskStatsDTO represents aggregated statistics for a single task
type TaskStatsDTO struct {
TaskID string `json:"task_id"`
TaskTitle string `json:"task_title"`
WorkspaceID string `json:"workspace_id"`
WorkflowID string `json:"workflow_id"`
State string `json:"state"`
SessionCount int `json:"session_count"`
TurnCount int `json:"turn_count"`
MessageCount int `json:"message_count"`
UserMessageCount int `json:"user_message_count"`
ToolCallCount int `json:"tool_call_count"`
TotalDurationMs int64 `json:"total_duration_ms"`
ActiveDurationMs int64 `json:"active_duration_ms"`
ElapsedSpanMs int64 `json:"elapsed_span_ms"`
CreatedAt string `json:"created_at"`
CompletedAt *string `json:"completed_at,omitempty"`
}
// GlobalStatsDTO represents workspace-wide aggregated statistics
type GlobalStatsDTO struct {
TotalTasks int `json:"total_tasks"`
CompletedTasks int `json:"completed_tasks"`
InProgressTasks int `json:"in_progress_tasks"`
TotalSessions int `json:"total_sessions"`
TotalTurns int `json:"total_turns"`
TotalMessages int `json:"total_messages"`
TotalUserMessages int `json:"total_user_messages"`
TotalToolCalls int `json:"total_tool_calls"`
TotalDurationMs int64 `json:"total_duration_ms"`
AvgTurnsPerTask float64 `json:"avg_turns_per_task"`
AvgMessagesPerTask float64 `json:"avg_messages_per_task"`
AvgDurationMsPerTask int64 `json:"avg_duration_ms_per_task"`
AvgTurnDurationMs int64 `json:"avg_turn_duration_ms"`
AvgMessagesPerTurn float64 `json:"avg_messages_per_turn"`
}
// DailyActivityDTO represents activity statistics for a single day
type DailyActivityDTO struct {
Date string `json:"date"`
TurnCount int `json:"turn_count"`
MessageCount int `json:"message_count"`
TaskCount int `json:"task_count"`
}
// CompletedTaskActivityDTO represents completed task counts for a day
type CompletedTaskActivityDTO struct {
Date string `json:"date"`
CompletedTasks int `json:"completed_tasks"`
}
// AgentUsageDTO represents usage statistics for a single agent profile
type AgentUsageDTO struct {
AgentProfileID string `json:"agent_profile_id"`
AgentProfileName string `json:"agent_profile_name"`
AgentModel string `json:"agent_model"`
SessionCount int `json:"session_count"`
TurnCount int `json:"turn_count"`
TotalDurationMs int64 `json:"total_duration_ms"`
}
// RepositoryStatsDTO represents usage statistics for a repository
type RepositoryStatsDTO struct {
RepositoryID string `json:"repository_id"`
RepositoryName string `json:"repository_name"`
TotalTasks int `json:"total_tasks"`
CompletedTasks int `json:"completed_tasks"`
InProgressTasks int `json:"in_progress_tasks"`
SessionCount int `json:"session_count"`
TurnCount int `json:"turn_count"`
MessageCount int `json:"message_count"`
UserMessageCount int `json:"user_message_count"`
ToolCallCount int `json:"tool_call_count"`
TotalDurationMs int64 `json:"total_duration_ms"`
TotalCommits int `json:"total_commits"`
TotalFilesChanged int `json:"total_files_changed"`
TotalInsertions int `json:"total_insertions"`
TotalDeletions int `json:"total_deletions"`
}
// GitStatsDTO represents aggregated git statistics
type GitStatsDTO struct {
TotalCommits int `json:"total_commits"`
TotalFilesChanged int `json:"total_files_changed"`
TotalInsertions int `json:"total_insertions"`
TotalDeletions int `json:"total_deletions"`
}
// StatsResponse represents the full stats API response
type StatsResponse struct {
Global GlobalStatsDTO `json:"global"`
TaskStats []TaskStatsDTO `json:"task_stats"`
TaskStatsHasMore bool `json:"task_stats_has_more"`
DailyActivity []DailyActivityDTO `json:"daily_activity"`
CompletedActivity []CompletedTaskActivityDTO `json:"completed_activity"`
AgentUsage []AgentUsageDTO `json:"agent_usage"`
RepositoryStats []RepositoryStatsDTO `json:"repository_stats"`
GitStats GitStatsDTO `json:"git_stats"`
}