Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.22% covered (success)
93.22%
55 / 59
60.00% covered (warning)
60.00%
3 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
DashboardMetricService
93.22% covered (success)
93.22%
55 / 59
60.00% covered (warning)
60.00%
3 / 5
20.12
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getProgressionStats
91.30% covered (success)
91.30%
21 / 23
0.00% covered (danger)
0.00%
0 / 1
8.04
 getConversationStatsForPeriod
81.82% covered (warning)
81.82%
9 / 11
0.00% covered (danger)
0.00%
0 / 1
4.10
 getHistoricalWeeklyAverageStats
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
4
 calculateEvolution
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3namespace App\Services\RolePlay;
4
5use App\Http\Models\Auth\User;
6use App\Http\Models\RolePlayConversations;
7use App\Http\Models\RolePlayProjects;
8use App\Http\Models\RolePlaySkillProgressions;
9use Carbon\Carbon;
10use MongoDB\BSON\UTCDateTime;
11
12class DashboardMetricService
13{
14    protected RolePlayConversations $conversationModel;
15
16    protected RolePlaySkillProgressions $skillProgressionModel;
17
18    protected RolePlayProjects $projectModel;
19
20    public function __construct(
21        RolePlayConversations $conversationModel,
22        RolePlaySkillProgressions $skillProgressionModel,
23        RolePlayProjects $projectModel
24    ) {
25        $this->conversationModel = $conversationModel;
26        $this->skillProgressionModel = $skillProgressionModel;
27        $this->projectModel = $projectModel;
28    }
29
30    public function getProgressionStats(User $user): array
31    {
32        $skillProgressions = $this->skillProgressionModel->where('status', 'active')->get();
33
34        $criterias = [];
35
36        foreach ($skillProgressions as $progression) {
37            if (empty($progression->scorecard_config) || ! is_array($progression->scorecard_config)) {
38                continue;
39            }
40
41            foreach ($progression->scorecard_config as $config) {
42                $projects = $this->projectModel->where('user_id', $user->id)->where('type', $progression->type)->get();
43
44                foreach ($projects as $project) {
45                    $progressions = $project->progression ?? [];
46
47                    $progress = collect($progressions)->first(function ($prog) use ($config) {
48                        return $prog['name'] === $config['name'];
49                    });
50
51                    $existentCriteria = collect($criterias)->first(function ($crit) use ($config, $progression) {
52                        return $crit['name'] === $config['name'] && $crit['type'] === $progression->type;
53                    });
54
55                    if ($existentCriteria) {
56                        $existentCriteria['score'] = ($existentCriteria['score'] + ($progress['score'] ?? 0)) / 2;
57                    } else {
58                        $criterias[] = [
59                            'type' => $progression->type,
60                            'name' => $config['name'],
61                            'score' => $progress['score'] ?? 0,
62                        ];
63                    }
64                }
65            }
66        }
67
68        return $criterias;
69    }
70
71    public function getConversationStatsForPeriod(User $user, ?Carbon $startDate, ?Carbon $endDate): array
72    {
73        $query = $this->conversationModel->where('user_id', $user->id);
74
75        if ($startDate && $endDate) {
76            $query->where('created_at', '>=', new UTCDateTime(strtotime($startDate->toDateString()) * 1000))
77                ->where('created_at', '<=', new UTCDateTime(strtotime($endDate->toDateString()) * 1000));
78        } elseif ($endDate) {
79            $query->where('created_at', '<', new UTCDateTime(strtotime($endDate->toDateString()) * 1000));
80        }
81
82        return [
83            'conversationsCount' => $query->clone()->count() ?? 0,
84            'practiceTimeSeconds' => $query->clone()->sum('duration') ?? 0,
85            'averageScore' => round($query->clone()->avg('score') ?? 0, 2),
86        ];
87    }
88
89    public function getHistoricalWeeklyAverageStats(User $user): array
90    {
91        $historicalQuery = $this->conversationModel->where('user_id', $user->id)
92            ->where('created_at', '<', now()->startOfWeek()->subWeek());
93
94        $firstRecord = $historicalQuery->clone()->orderBy('created_at', 'asc')->first();
95
96        if (! $firstRecord) {
97            return [
98                'conversationsCount' => 0,
99                'practiceTimeSeconds' => 0,
100                'averageScore' => 0,
101            ];
102        }
103
104        $lastRecordDate = $historicalQuery->clone()->orderBy('created_at', 'desc')->first()->created_at;
105        $totalWeeks = ($firstRecord->created_at->diffInWeeks($lastRecordDate) ?? 0) + 1;
106
107        $totalConversations = $historicalQuery->clone()->count() ?? 0;
108        $totalPracticeTime = $historicalQuery->clone()->sum('duration') ?? 0;
109
110        return [
111            'conversationsCount' => $totalWeeks > 0 ? $totalConversations / $totalWeeks : 0,
112            'practiceTimeSeconds' => $totalWeeks > 0 ? $totalPracticeTime / $totalWeeks : 0,
113            'averageScore' => round($historicalQuery->clone()->avg('score') ?? 0, 2),
114        ];
115    }
116
117    public function calculateEvolution(float $current, float $previous): float
118    {
119        if ($previous == 0) {
120            return $current > 0 ? 100.00 : 0.00;
121        }
122
123        $evolution = (($current - $previous) / $previous) * 100;
124
125        return round($evolution, 2);
126    }
127}