diff --git a/internal/app/statistics.go b/internal/app/statistics.go index 7e00386..42abda5 100644 --- a/internal/app/statistics.go +++ b/internal/app/statistics.go @@ -3,6 +3,9 @@ package app import ( "context" "errors" + "log/slog" + "sync" + "time" "github.com/LazyBachelor/LazyPM/internal/models" "github.com/LazyBachelor/LazyPM/internal/storage" @@ -10,14 +13,17 @@ import ( type StatisticsService struct { storage *storage.Storage[models.Statistics] + logger *slog.Logger + mu sync.Mutex } -func NewStatisticsService(storage *storage.Storage[models.Statistics]) (*StatisticsService, error) { +func NewStatisticsService(storage *storage.Storage[models.Statistics], logger *slog.Logger) (*StatisticsService, error) { if err := storage.Init(); err != nil { return nil, err } return &StatisticsService{ storage: storage, + logger: logger, }, nil } @@ -35,3 +41,83 @@ func (s *StatisticsService) GetStatistics() (models.Statistics, error) { } return *s.storage.Data, nil } + +func (s *StatisticsService) RecordTaskRun(ctx context.Context, run models.TaskRunMetrics) error { + _ = ctx + + s.mu.Lock() + defer s.mu.Unlock() + + if s.storage.Data == nil { + return errors.New("statistics data not initialized") + } + + stats := s.storage.Data + now := time.Now() + + if stats.StartTime.IsZero() { + stats.StartTime = run.StartedAt + } + if run.StartedAt.Before(stats.StartTime) { + stats.StartTime = run.StartedAt + } + + stats.EndTime = now + stats.Duration = stats.EndTime.Sub(stats.StartTime) + stats.InterfaceType = run.InterfaceType + + stats.TaskRuns++ + stats.LastTaskName = run.TaskName + stats.LastRunID = run.RunID + + if run.Completed { + stats.TasksCompleted++ + } else { + stats.TasksFailed++ + } + + stats.TotalDurationMs += run.DurationMs + if stats.TaskRuns > 0 { + stats.AverageDurationMs = stats.TotalDurationMs / int64(stats.TaskRuns) + } + + stats.ValidationAttempts += run.ValidationAttempts + stats.ValidationSuccesses += run.ValidationSuccesses + stats.ValidationFailures += run.ValidationFailures + stats.ValidationChecksPassed += run.ValidationChecksPassed + stats.ValidationChecksFailed += run.ValidationChecksFailed + + userActions := 0 + for _, log := range run.Logs { + if log.Level == "user_action" { + userActions++ + } + } + stats.TotalUserActions += userActions + stats.ButtonClicks.Clicks += userActions + if run.QuestionnaireCompleted { + stats.QuestionnairesCompleted++ + } + if run.QuestionnaireUserQuit { + stats.QuestionnairesAbandoned++ + } + + if err := s.storage.Save(); err != nil { + if s.logger != nil { + s.logger.Error("failed to save global statistics", "error", err) + } + return err + } + + if s.logger != nil { + s.logger.Info("global statistics updated", + "task", run.TaskName, + "run_id", run.RunID, + "task_runs", stats.TaskRuns, + "completed", stats.TasksCompleted, + "failed", stats.TasksFailed, + ) + } + + return nil +}