Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
RolePlayReevaluationService
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
2 / 2
7
100.00% covered (success)
100.00%
1 / 1
 fetchConversationsToReevaluate
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
5
 resetProgressionsForUsers
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace App\Http\Services\RolePlay;
4
5use App\Http\Models\RolePlayConversations;
6use App\Http\Models\UserRolePlayProgression;
7use Illuminate\Support\Collection;
8
9/**
10 * Service encapsulating the data-layer concerns of the roleplay re-evaluation
11 * flow so the ReevaluateRolePlayConversations console command stays focused
12 * on CLI presentation.
13 *
14 * The command previously built conversation queries and deleted progression
15 * documents directly against the models, which violated the Controller/Command
16 * → Service → Repository → Model rule from CLAUDE.MD.
17 */
18class RolePlayReevaluationService
19{
20    /**
21     * Fetch the conversations that should be re-evaluated given the supplied
22     * filters. Filters are applied defensively — any unset keys are ignored.
23     *
24     * @param  array{
25     *     conversation?: string|null,
26     *     user?: string|null,
27     *     call_type?: string|null,
28     *     status?: string|null,
29     *     limit?: int|null,
30     * }  $filters
31     * @return Collection<int, RolePlayConversations>
32     */
33    public function fetchConversationsToReevaluate(array $filters): Collection
34    {
35        $query = RolePlayConversations::query();
36
37        if (! empty($filters['conversation'])) {
38            $query->where('_id', $filters['conversation']);
39        } else {
40            $query->where('status', $filters['status'] ?? 'done');
41        }
42
43        if (! empty($filters['user'])) {
44            $query->where('user_id', $filters['user']);
45        }
46
47        // We cannot re-evaluate records without transcript content.
48        $query->whereNotNull('transcript')->where('transcript', '!=', '');
49
50        $conversations = $query->orderBy('created_at', 'asc')->get();
51
52        if (! empty($filters['call_type'])) {
53            $callTypeFilter = $filters['call_type'];
54            $conversations = $conversations->filter(function ($c) use ($callTypeFilter) {
55                $c->loadMissing('project');
56
57                return ($c->project->type ?? null) === $callTypeFilter;
58            })->values();
59        }
60
61        if (! empty($filters['limit'])) {
62            $conversations = $conversations->take((int) $filters['limit']);
63        }
64
65        return $conversations;
66    }
67
68    /**
69     * Wipe progression documents for the given user ids so aggregates
70     * rebuild from scratch as each session is re-evaluated.
71     *
72     * @param  array<int, string>  $userIds
73     * @return int Number of documents deleted.
74     */
75    public function resetProgressionsForUsers(array $userIds): int
76    {
77        if (empty($userIds)) {
78            return 0;
79        }
80
81        return (int) UserRolePlayProgression::whereIn('user_id', $userIds)->delete();
82    }
83}