Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
92.31% covered (success)
92.31%
12 / 13
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
RolePlaySkillProgressionService
92.31% covered (success)
92.31%
12 / 13
66.67% covered (warning)
66.67%
2 / 3
6.02
0.00% covered (danger)
0.00%
0 / 1
 resolveScorecard
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 rebuildActiveRow
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
2.01
 resolveSourceLabel
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3namespace App\Http\Services\RolePlay;
4
5use App\Http\Models\RolePlayConfig;
6use App\Http\Models\RolePlaySkillProgressions;
7
8/**
9 * Service responsible for maintaining the `roleplay_skill_progression`
10 * collection — the source of truth for the active scorecard per call type
11 * used by the evaluator.
12 *
13 * Extracted from RebuildRolePlaySkillProgression so the command stays
14 * focused on CLI concerns (progress bar, dry-run, confirmation) while the
15 * data-layer semantics live here (CLAUDE.MD: Controller/Command → Service →
16 * Repository → Model).
17 */
18class RolePlaySkillProgressionService
19{
20    /** Call types managed by this service. */
21    public const MANAGED_TYPES = ['cold-call', 'discovery-call'];
22
23    /**
24     * Resolve the scorecard for a given call type (config overrides win,
25     * else the hardcoded class constants).
26     *
27     * @return array<int, array<string, mixed>>
28     */
29    public function resolveScorecard(string $type): array
30    {
31        return RolePlaySkillProgressions::getScorecard($type);
32    }
33
34    /**
35     * Upsert the active scorecard row for a call type.
36     *
37     * Returns the section count of the persisted scorecard, or null if the
38     * resolved scorecard was empty (nothing was written).
39     */
40    public function rebuildActiveRow(string $type): ?int
41    {
42        $scorecard = $this->resolveScorecard($type);
43
44        if (empty($scorecard)) {
45            return null;
46        }
47
48        RolePlaySkillProgressions::updateOrCreate(
49            ['type' => $type, 'status' => 'active'],
50            ['type' => $type, 'status' => 'active', 'scorecard_config' => $scorecard],
51        );
52
53        return count($scorecard);
54    }
55
56    /**
57     * Human-readable description of where scorecards are being sourced from,
58     * so operators can sanity-check the result.
59     */
60    public function resolveSourceLabel(): string
61    {
62        $config = RolePlayConfig::where('type', 'global')->first();
63        if ($config && ! empty($config->default_scorecards)) {
64            return 'RolePlayConfig.default_scorecards (with hardcoded fallback per type)';
65        }
66
67        return 'hardcoded constants in RolePlaySkillProgressions';
68    }
69}