| Code Coverage
| Lines | Functions and Methods | Classes and Traits
Total | 99.47% covered (success) | 99.47% | 753 / 757 | 88.00% covered (warning) | 88.00% | 22 / 25 | CRAP | 0.00% covered (danger) | 0.00% | 0 / 1
AcademiaSeeder | 99.47% covered (success) | 99.47% | 753 / 757 | 88.00% covered (warning) | 88.00% | 22 / 25 | 75 | 0.00% covered (danger) | 0.00% | 0 / 1
run | 94.74% covered (success) | 94.74% | 18 / 19 | 0.00% covered (danger) | 0.00% | 0 / 1 | 2.00 |
seedRoles | 94.12% covered (success) | 94.12% | 32 / 34 | 0.00% covered (danger) | 0.00% | 0 / 1 | 4.00 |
seedSettings | 100.00% covered (success) | 100.00% | 56 / 56 | 100.00% covered (success) | 100.00% | 1 / 1 | 1 |
seedEvaluationRules | 100.00% covered (success) | 100.00% | 15 / 15 | 100.00% covered (success) | 100.00% | 1 / 1 | 1 |
seedLevels | 100.00% covered (success) | 100.00% | 23 / 23 | 100.00% covered (success) | 100.00% | 1 / 1 | 2 |
seedEvaluationCatalog | 100.00% covered (success) | 100.00% | 73 / 73 | 100.00% covered (success) | 100.00% | 1 / 1 | 5 |
seedUsers | 100.00% covered (success) | 100.00% | 5 / 5 | 100.00% covered (success) | 100.00% | 1 / 1 | 1 |
seedCoaches | 100.00% covered (success) | 100.00% | 36 / 36 | 100.00% covered (success) | 100.00% | 1 / 1 | 2 |
seedCoachAvailability | 100.00% covered (success) | 100.00% | 17 / 17 | 100.00% covered (success) | 100.00% | 1 / 1 | 3 |
seedGroups | 100.00% covered (success) | 100.00% | 38 / 38 | 100.00% covered (success) | 100.00% | 1 / 1 | 2 |
seedParents | 100.00% covered (success) | 100.00% | 16 / 16 | 100.00% covered (success) | 100.00% | 1 / 1 | 3 |
seedChildren | 100.00% covered (success) | 100.00% | 60 / 60 | 100.00% covered (success) | 100.00% | 1 / 1 | 2 |
seedAttendance | 100.00% covered (success) | 100.00% | 64 / 64 | 100.00% covered (success) | 100.00% | 1 / 1 | 23 |
seedEvaluations | 100.00% covered (success) | 100.00% | 10 / 10 | 100.00% covered (success) | 100.00% | 1 / 1 | 3 |
seedNotifications | 100.00% covered (success) | 100.00% | 48 / 48 | 100.00% covered (success) | 100.00% | 1 / 1 | 1 |
createUser | 94.12% covered (success) | 94.12% | 16 / 17 | 0.00% covered (danger) | 0.00% | 0 / 1 | 2.00 |
createEvaluation | 100.00% covered (success) | 100.00% | 57 / 57 | 100.00% covered (success) | 100.00% | 1 / 1 | 6 |
childDefinitions | 100.00% covered (success) | 100.00% | 17 / 17 | 100.00% covered (success) | 100.00% | 1 / 1 | 1 |
childDefinition | 100.00% covered (success) | 100.00% | 19 / 19 | 100.00% covered (success) | 100.00% | 1 / 1 | 1 |
attendancePatterns | 100.00% covered (success) | 100.00% | 17 / 17 | 100.00% covered (success) | 100.00% | 1 / 1 | 1 |
evaluationPlan | 100.00% covered (success) | 100.00% | 21 / 21 | 100.00% covered (success) | 100.00% | 1 / 1 | 1 |
standardPlan | 100.00% covered (success) | 100.00% | 17 / 17 | 100.00% covered (success) | 100.00% | 1 / 1 | 1 |
scoreTemplates | 100.00% covered (success) | 100.00% | 68 / 68 | 100.00% covered (success) | 100.00% | 1 / 1 | 1 |
groupCoachKey | 100.00% covered (success) | 100.00% | 5 / 5 | 100.00% covered (success) | 100.00% | 1 / 1 | 1 |
childAttendsSession | 100.00% covered (success) | 100.00% | 5 / 5 | 100.00% covered (success) | 100.00% | 1 / 1 | 5 |

1 | <?php
2 |
3 | namespace App\Database\Seeds;
4 |
5 | use App\Models\AcademyGroupModel;
6 | use App\Models\AcademyLevelModel;
7 | use App\Models\AttendanceRecordModel;
8 | use App\Models\AttendanceSessionModel;
9 | use App\Models\ChildCoachModel;
10 | use App\Models\ChildGuardianModel;
11 | use App\Models\ChildModel;
12 | use App\Models\CoachAvailabilityModel;
13 | use App\Models\CoachModel;
14 | use App\Models\EvaluationCriteriaModel;
15 | use App\Models\EvaluationModel;
16 | use App\Models\EvaluationRuleModel;
17 | use App\Models\EvaluationScoreModel;
18 | use App\Models\EvaluationStatusHistoryModel;
19 | use App\Models\EvaluationTypeModel;
20 | use App\Models\GuardianModel;
21 | use App\Models\NotificationModel;
22 | use App\Models\SettingModel;
23 | use App\Models\UserRoleModel;
24 | use CodeIgniter\Database\Seeder;
25 | use CodeIgniter\Shield\Entities\User;
26 | use CodeIgniter\Shield\Models\UserModel;
27 | use RuntimeException;
28 |
29 | class AcademiaSeeder extends Seeder
30 | {
31 | private string $defaultPassword = 'Academia123!';
32 |
33 | private UserModel $userModel;
34 |
35 | /** @var array<string, int> */
36 | private array $roleIds = [];
37 |
38 | /** @var array<string, int> */
39 | private array $userIds = [];
40 |
41 | /** @var array<string, int> */
42 | private array $coachIds = [];
43 |
44 | /** @var array<string, int> */
45 | private array $groupIds = [];
46 |
47 | /** @var array<string, int> */
48 | private array $levelIds = [];
49 |
50 | /** @var array<string, int> */
51 | private array $guardianIds = [];
52 |
53 | /** @var array<string, array<string, mixed>> */
54 | private array $children = [];
55 |
56 | /** @var array<string, int> */
57 | private array $evaluationTypeIds = [];
58 |
59 | /** @var array<string, array<string, mixed>> */
60 | private array $criteria = [];
61 |
62 | /** @var array<string, list<string>> */
63 | private array $criteriaByType = [];
64 |
65 | /** @var array<string, float|int|string|bool|null> */
66 | private array $rule = [];
67 |
68 | public function run(): void
69 | {
70 | $this->userModel = model(UserModel::class);
71 |
72 | $this->db->transStart();
73 |
74 | $this->seedRoles();
75 | $this->seedSettings();
76 | $this->seedEvaluationRules();
77 | $this->seedLevels();
78 | $this->seedEvaluationCatalog();
79 | $this->seedUsers();
80 | $this->seedCoaches();
81 | $this->seedCoachAvailability();
82 | $this->seedGroups();
83 | $this->seedParents();
84 | $this->seedChildren();
85 | $this->seedAttendance();
86 | $this->seedEvaluations();
87 | $this->seedNotifications();
88 |
89 | $this->db->transComplete();
90 |
91 | if (! $this->db->transStatus()) {
92 | throw new RuntimeException('Academia seed failed: ' . json_encode($this->db->error()));
93 | }
94 | }
95 |
96 | private function seedRoles(): void
97 | {
98 | $now = date('Y-m-d H:i:s');
99 |
100 | $inserted = $this->db->table('roles')->insertBatch([
101 | [
102 | 'slug' => 'admin',
103 | 'name' => 'Admin',
104 | 'description' => 'Acces complet in platforma.',
105 | 'priority' => 1,
106 | 'created_at' => $now,
107 | 'updated_at' => $now,
108 | ],
109 | [
110 | 'slug' => 'coach',
111 | 'name' => 'Trainer',
112 | 'description' => 'Acces la copii, evaluari si prezenta pentru grupele proprii de inot.',
113 | 'priority' => 2,
114 | 'created_at' => $now,
115 | 'updated_at' => $now,
116 | ],
117 | [
118 | 'slug' => 'parent',
119 | 'name' => 'Parent',
120 | 'description' => 'Rol limitat, pregatit pentru fazele viitoare.',
121 | 'priority' => 3,
122 | 'created_at' => $now,
123 | 'updated_at' => $now,
124 | ],
125 | ]);
126 |
127 | if ($inserted === false) {
128 | throw new RuntimeException('Unable to seed roles: ' . json_encode($this->db->error()));
129 | }
130 |
131 | $query = $this->db->table('roles')->get();
132 | if ($query === false) {
133 | throw new RuntimeException('Unable to read roles after seed: ' . json_encode($this->db->error()));
134 | }
135 |
136 | foreach ($query->getResultArray() as $row) {
137 | $this->roleIds[$row['slug']] = (int) $row['id'];
138 | }
139 | }
140 |
141 | private function seedSettings(): void
142 | {
143 | $model = model(SettingModel::class);
144 | $now = date('Y-m-d H:i:s');
145 |
146 | $model->insertBatch([
147 | [
148 | 'class' => 'Academia',
149 | 'key' => 'academy_name',
150 | 'value' => 'Splash Academy',
151 | 'type' => 'string',
152 | 'context' => 'general',
153 | 'label' => 'Academy Name',
154 | 'group_name' => 'general',
155 | 'description' => 'Numele academiei afisat in layout.',
156 | 'is_public' => 1,
157 | 'created_at' => $now,
158 | 'updated_at' => $now,
159 | ],
160 | [
161 | 'class' => 'Academia',
162 | 'key' => 'default_dashboard_period',
163 | 'value' => '30',
164 | 'type' => 'integer',
165 | 'context' => 'dashboard',
166 | 'label' => 'Default Dashboard Period',
167 | 'group_name' => 'dashboard',
168 | 'description' => 'Perioada implicita pentru filtrele dashboardului.',
169 | 'is_public' => 0,
170 | 'created_at' => $now,
171 | 'updated_at' => $now,
172 | ],
173 | [
174 | 'class' => 'Academia',
175 | 'key' => 'no_evaluation_days',
176 | 'value' => '45',
177 | 'type' => 'integer',
178 | 'context' => 'dashboard',
179 | 'label' => 'No Evaluation Days Threshold',
180 | 'group_name' => 'dashboard',
181 | 'description' => 'Numarul de zile dupa care copilul intra in alerta fara evaluare.',
182 | 'is_public' => 0,
183 | 'created_at' => $now,
184 | 'updated_at' => $now,
185 | ],
186 | [
187 | 'class' => 'Academia',
188 | 'key' => 'absence_alert_threshold',
189 | 'value' => '25',
190 | 'type' => 'integer',
191 | 'context' => 'dashboard',
192 | 'label' => 'Absence Alert Threshold',
193 | 'group_name' => 'dashboard',
194 | 'description' => 'Procent de absenta peste care este generata alerta.',
195 | 'is_public' => 0,
196 | 'created_at' => $now,
197 | 'updated_at' => $now,
198 | ],
199 | ]);
200 | }
201 |
202 | private function seedEvaluationRules(): void
203 | {
204 | $model = model(EvaluationRuleModel::class);
205 | $now = date('Y-m-d H:i:s');
206 |
207 | $this->rule = [
208 | 'name' => 'Global Default Rule',
209 | 'scope_type' => 'global',
210 | 'scope_id' => null,
211 | 'almost_ready_threshold' => 3.5,
212 | 'ready_threshold' => 4.0,
213 | 'minimum_critical_threshold' => 3.0,
214 | 'is_active' => 1,
215 | 'notes' => 'Pragurile implicite pentru scorare si semnal de promovare.',
216 | 'created_at' => $now,
217 | 'updated_at' => $now,
218 | ];
219 |
220 | $model->insert($this->rule);
221 | }
222 |
223 | private function seedLevels(): void
224 | {
225 | $model = model(AcademyLevelModel::class);
226 | $now = date('Y-m-d H:i:s');
227 |
228 | $levels = [
229 | ['key' => 'delfinas', 'name' => 'Delfinaș (Inițiere)', 'slug' => 'delfinas-initiere', 'sort_order' => 1, 'color_hex' => '#0ea5e9'],
230 | ['key' => 'rechin', 'name' => 'Rechin (Avansat)', 'slug' => 'rechin-avansat', 'sort_order' => 2, 'color_hex' => '#0284c7'],
231 | ['key' => 'pre_performanta', 'name' => 'Pre-Performanță', 'slug' => 'pre-performanta', 'sort_order' => 3, 'color_hex' => '#14b8a6'],
232 | ['key' => 'sportiv_performanta', 'name' => 'Sportiv de Performanță', 'slug' => 'sportiv-de-performanta', 'sort_order' => 4, 'color_hex' => '#f59e0b'],
233 | ];
234 |
235 | foreach ($levels as $level) {
236 | $model->insert([
237 | 'name' => $level['name'],
238 | 'slug' => $level['slug'],
239 | 'sort_order' => $level['sort_order'],
240 | 'color_hex' => $level['color_hex'],
241 | 'internal_description' => 'Nivel intern pentru cursurile de inot: ' . $level['name'] . '.',
242 | 'parent_description' => 'Nivelul ' . $level['name'] . ' marcheaza progresul copilului in programul de inot.',
243 | 'benchmark_minimum' => 3.0,
244 | 'benchmark_target' => 4.0,
245 | 'promotion_rules' => 'Necesita evaluare full cu status READY si criterii critice peste prag.',
246 | 'created_at' => $now,
247 | 'updated_at' => $now,
248 | ]);
249 |
250 | $this->levelIds[$level['key']] = (int) $model->getInsertID();
251 | }
252 | }
253 |
254 | private function seedEvaluationCatalog(): void
255 | {
256 | $typeModel = model(EvaluationTypeModel::class);
257 | $criteriaModel = model(EvaluationCriteriaModel::class);
258 | $now = date('Y-m-d H:i:s');
259 |
260 | $types = [
261 | [
262 | 'key' => 'quick-check',
263 | 'name' => 'Quick Check',
264 | 'description' => 'Monitorizare rapida pentru progresul saptamanal la inot.',
265 | 'is_quick_check' => 1,
266 | 'affects_promotion' => 0,
267 | ],
268 | [
269 | 'key' => 'full-evaluation',
270 | 'name' => 'Full Evaluation',
271 | 'description' => 'Evaluare completa folosita pentru decizii de promovare in programul de inot.',
272 | 'is_quick_check' => 0,
273 | 'affects_promotion' => 1,
274 | ],
275 | ];
276 |
277 | foreach ($types as $type) {
278 | $typeModel->insert([
279 | 'name' => $type['name'],
280 | 'slug' => $type['key'],
281 | 'description' => $type['description'],
282 | 'is_quick_check' => $type['is_quick_check'],
283 | 'affects_promotion' => $type['affects_promotion'],
284 | 'status' => 'active',
285 | 'created_at' => $now,
286 | 'updated_at' => $now,
287 | ]);
288 |
289 | $this->evaluationTypeIds[$type['key']] = (int) $typeModel->getInsertID();
290 | }
291 |
292 | $criteria = [
293 | ['slug' => 'water-confidence', 'name' => 'Water Confidence', 'weight' => 1.2, 'critical' => 1, 'sort_order' => 1],
294 | ['slug' => 'breathing-control', 'name' => 'Breathing Control', 'weight' => 1.2, 'critical' => 1, 'sort_order' => 2],
295 | ['slug' => 'stroke-technique', 'name' => 'Stroke Technique', 'weight' => 1.4, 'critical' => 1, 'sort_order' => 3],
296 | ['slug' => 'start-turn-basics', 'name' => 'Start & Turn Basics', 'weight' => 0.9, 'critical' => 0, 'sort_order' => 4],
297 | ['slug' => 'race-awareness', 'name' => 'Race Awareness', 'weight' => 0.7, 'critical' => 0, 'sort_order' => 5],
298 | ['slug' => 'focus-effort', 'name' => 'Focus & Effort', 'weight' => 0.8, 'critical' => 0, 'sort_order' => 6],
299 | ];
300 |
301 | foreach ($criteria as $criterion) {
302 | $criteriaModel->insert([
303 | 'name' => $criterion['name'],
304 | 'slug' => $criterion['slug'],
305 | 'description' => $criterion['name'] . ' in contextul evaluarii academiei.',
306 | 'is_active' => 1,
307 | 'default_weight' => $criterion['weight'],
308 | 'is_critical' => $criterion['critical'],
309 | 'sort_order' => $criterion['sort_order'],
310 | 'min_score' => 1,
311 | 'max_score' => 5,
312 | 'created_at' => $now,
313 | 'updated_at' => $now,
314 | ]);
315 |
316 | $this->criteria[$criterion['slug']] = [
317 | 'id' => (int) $criteriaModel->getInsertID(),
318 | 'weight' => (float) $criterion['weight'],
319 | 'is_critical' => (bool) $criterion['critical'],
320 | ];
321 | }
322 |
323 | $this->criteriaByType = [
324 | 'quick-check' => ['water-confidence', 'breathing-control', 'stroke-technique', 'focus-effort'],
325 | 'full-evaluation' => ['water-confidence', 'breathing-control', 'stroke-technique', 'start-turn-basics', 'race-awareness', 'focus-effort'],
326 | ];
327 |
328 | $pivotRows = [];
329 | foreach ($this->criteriaByType as $typeSlug => $criteriaSlugs) {
330 | foreach ($criteriaSlugs as $index => $criterionSlug) {
331 | $pivotRows[] = [
332 | 'evaluation_type_id' => $this->evaluationTypeIds[$typeSlug],
333 | 'evaluation_criteria_id' => $this->criteria[$criterionSlug]['id'],
334 | 'weight' => $this->criteria[$criterionSlug]['weight'],
335 | 'is_required' => 1,
336 | 'sort_order' => $index + 1,
337 | ];
338 | }
339 | }
340 |
341 | $this->db->table('evaluation_type_criteria')->insertBatch($pivotRows);
342 | }
343 |
344 | private function seedUsers(): void
345 | {
346 | $this->userIds['admin'] = $this->createUser('admin', 'admin@academia.local', 'admin', 'admin');
347 | $this->userIds['coach_andrei'] = $this->createUser('andrei.popescu', 'andrei@academia.local', 'coach', 'coach');
348 | $this->userIds['coach_ioana'] = $this->createUser('ioana.dumitrescu', 'ioana@academia.local', 'coach', 'coach');
349 | $this->userIds['coach_mihai'] = $this->createUser('mihai.ionescu', 'mihai@academia.local', 'coach', 'coach');
350 | $this->userIds['parent_cristina'] = $this->createUser('cristina.marin', 'cristina@academia.local', 'parent', 'parent');
351 | }
352 |
353 | private function seedCoaches(): void
354 | {
355 | $model = model(CoachModel::class);
356 | $now = date('Y-m-d H:i:s');
357 |
358 | $coaches = [
359 | 'coach_andrei' => [
360 | 'full_name' => 'Andrei Popescu',
361 | 'display_role' => 'Head Trainer',
362 | 'phone' => '0711 111 111',
363 | 'email' => 'andrei@academia.local',
364 | ],
365 | 'coach_ioana' => [
366 | 'full_name' => 'Ioana Dumitrescu',
367 | 'display_role' => 'Trainer',
368 | 'phone' => '0722 222 222',
369 | 'email' => 'ioana@academia.local',
370 | ],
371 | 'coach_mihai' => [
372 | 'full_name' => 'Mihai Ionescu',
373 | 'display_role' => 'Assistant Trainer',
374 | 'phone' => '0733 333 333',
375 | 'email' => 'mihai@academia.local',
376 | ],
377 | ];
378 |
379 | foreach ($coaches as $key => $coach) {
380 | $model->insert([
381 | 'user_id' => $this->userIds[$key],
382 | 'full_name' => $coach['full_name'],
383 | 'display_role' => $coach['display_role'],
384 | 'phone' => $coach['phone'],
385 | 'email' => $coach['email'],
386 | 'avatar_path' => null,
387 | 'is_active' => 1,
388 | 'notes' => 'Profil initial seed-uit pentru Faza 1.',
389 | 'created_at' => $now,
390 | 'updated_at' => $now,
391 | ]);
392 |
393 | $this->coachIds[$key] = (int) $model->getInsertID();
394 | }
395 | }
396 |
397 | private function seedCoachAvailability(): void
398 | {
399 | $model = model(CoachAvailabilityModel::class);
400 | $now = date('Y-m-d H:i:s');
401 |
402 | $slots = [
403 | 'coach_andrei' => [[1, '16:00:00', '19:00:00'], [3, '16:00:00', '19:00:00'], [5, '16:00:00', '18:30:00']],
404 | 'coach_ioana' => [[2, '16:00:00', '19:00:00'], [4, '16:00:00', '19:00:00'], [6, '10:00:00', '13:00:00']],
405 | 'coach_mihai' => [[1, '17:00:00', '20:00:00'], [4, '17:00:00', '20:00:00'], [6, '09:00:00', '12:00:00']],
406 | ];
407 |
408 | foreach ($slots as $coachKey => $entries) {
409 | foreach ($entries as [$dayOfWeek, $start, $end]) {
410 | $model->insert([
411 | 'coach_id' => $this->coachIds[$coachKey],
412 | 'day_of_week' => $dayOfWeek,
413 | 'start_time' => $start,
414 | 'end_time' => $end,
415 | 'is_available' => 1,
416 | 'created_at' => $now,
417 | ]);
418 | }
419 | }
420 | }
421 |
422 | private function seedGroups(): void
423 | {
424 | $model = model(AcademyGroupModel::class);
425 | $now = date('Y-m-d H:i:s');
426 |
427 | $groups = [
428 | 'red_sparks' => [
429 | 'name' => 'Delfinas Initiere',
430 | 'code' => 'DFN',
431 | 'academy_level_id' => $this->levelIds['delfinas'],
432 | 'primary_coach_id' => $this->coachIds['coach_andrei'],
433 | 'schedule_summary' => 'Mon & Wed, 17:00',
434 | ],
435 | 'orange_aces' => [
436 | 'name' => 'Rechin Avansat',
437 | 'code' => 'RCH',
438 | 'academy_level_id' => $this->levelIds['rechin'],
439 | 'primary_coach_id' => $this->coachIds['coach_ioana'],
440 | 'schedule_summary' => 'Tue & Thu, 17:00',
441 | ],
442 | 'green_smash' => [
443 | 'name' => 'Performanta Bazin',
444 | 'code' => 'PRF',
445 | 'academy_level_id' => $this->levelIds['sportiv_performanta'],
446 | 'primary_coach_id' => $this->coachIds['coach_mihai'],
447 | 'schedule_summary' => 'Mon & Thu, 18:00',
448 | ],
449 | ];
450 |
451 | foreach ($groups as $key => $group) {
452 | $model->insert([
453 | 'name' => $group['name'],
454 | 'code' => $group['code'],
455 | 'academy_level_id' => $group['academy_level_id'],
456 | 'primary_coach_id' => $group['primary_coach_id'],
457 | 'schedule_summary' => $group['schedule_summary'],
458 | 'status' => 'active',
459 | 'capacity' => 12,
460 | 'created_at' => $now,
461 | 'updated_at' => $now,
462 | ]);
463 |
464 | $this->groupIds[$key] = (int) $model->getInsertID();
465 | }
466 | }
467 |
468 | private function seedParents(): void
469 | {
470 | $model = model(GuardianModel::class);
471 | $now = date('Y-m-d H:i:s');
472 |
473 | foreach ($this->childDefinitions() as $childKey => $child) {
474 | $guardian = $child['guardian'];
475 | $model->insert([
476 | 'user_id' => $guardian['user_key'] ? $this->userIds[$guardian['user_key']] : null,
477 | 'full_name' => $guardian['full_name'],
478 | 'phone' => $guardian['phone'],
479 | 'email' => $guardian['email'],
480 | 'relationship_type' => 'parent',
481 | 'status' => 'active',
482 | 'notes' => 'Contact seed-uit pentru profilul copilului.',
483 | 'created_at' => $now,
484 | 'updated_at' => $now,
485 | ]);
486 |
487 | $this->guardianIds[$childKey] = (int) $model->getInsertID();
488 | }
489 | }
490 |
491 | private function seedChildren(): void
492 | {
493 | $childModel = model(ChildModel::class);
494 | $childGuardianModel = model(ChildGuardianModel::class);
495 | $childCoachModel = model(ChildCoachModel::class);
496 | $journeyService = service('journey');
497 | $now = date('Y-m-d H:i:s');
498 |
499 | foreach ($this->childDefinitions() as $childKey => $definition) {
500 | $fullName = $definition['first_name'] . ' ' . $definition['last_name'];
501 | $childModel->insert([
502 | 'first_name' => $definition['first_name'],
503 | 'last_name' => $definition['last_name'],
504 | 'full_name' => $fullName,
505 | 'birth_date' => $definition['birth_date'],
506 | 'gender' => $definition['gender'],
507 | 'academy_level_id' => $this->levelIds[$definition['level_key']],
508 | 'academy_group_id' => $this->groupIds[$definition['group_key']],
509 | 'primary_coach_id' => $this->coachIds[$definition['coach_key']],
510 | 'primary_guardian_id' => $this->guardianIds[$childKey],
511 | 'first_class_at' => $definition['first_class_at'],
512 | 'status' => $definition['status'],
513 | 'has_siblings' => $definition['has_siblings'],
514 | 'notes' => $definition['notes'],
515 | 'created_at' => $now,
516 | 'updated_at' => $now,
517 | ]);
518 |
519 | $childId = (int) $childModel->getInsertID();
520 |
521 | $childGuardianModel->insert([
522 | 'child_id' => $childId,
523 | 'parent_id' => $this->guardianIds[$childKey],
524 | 'relationship_type' => 'parent',
525 | 'is_primary' => 1,
526 | 'created_at' => $now,
527 | ]);
528 |
529 | $childCoachModel->insert([
530 | 'child_id' => $childId,
531 | 'coach_id' => $this->coachIds[$definition['coach_key']],
532 | 'is_primary' => 1,
533 | 'assigned_at' => $definition['first_class_at'] . ' 09:00:00',
534 | 'unassigned_at' => null,
535 | ]);
536 |
537 | $this->children[$childKey] = [
538 | 'id' => $childId,
539 | 'full_name' => $fullName,
540 | 'status' => $definition['status'],
541 | 'group_key' => $definition['group_key'],
542 | 'coach_key' => $definition['coach_key'],
543 | 'level_key' => $definition['level_key'],
544 | ];
545 |
546 | $journeyService->recordEvent([
547 | 'child_id' => $childId,
548 | 'event_type' => 'child_created',
549 | 'title' => 'Copil adaugat in academie',
550 | 'description' => 'Profilul copilului a fost creat si asignat la grupa si trainerul principal.',
551 | 'metadata' => json_encode([
552 | 'group' => $definition['group_key'],
553 | 'coach' => $definition['coach_key'],
554 | 'level' => $definition['level_key'],
555 | ], JSON_UNESCAPED_UNICODE),
556 | 'created_by' => $this->userIds['admin'],
557 | 'created_at' => $now,
558 | ]);
559 | }
560 | }
561 |
562 | private function seedAttendance(): void
563 | {
564 | $sessionModel = model(AttendanceSessionModel::class);
565 | $recordModel = model(AttendanceRecordModel::class);
566 | $journey = service('journey');
567 | $today = new \DateTimeImmutable('today');
568 | $offsets = [-27, -24, -20, -17, -13, -10, -6, -3];
569 | $patterns = $this->attendancePatterns();
570 |
571 | foreach ($this->groupIds as $groupKey => $groupId) {
572 | $cancelledSessionId = null;
573 |
574 | foreach ($offsets as $index => $offset) {
575 | $sessionDate = $today->modify($offset . ' days')->format('Y-m-d');
576 | $status = 'completed';
577 | $isCancelled = 0;
578 | $recoveryFor = null;
579 |
580 | if ($groupKey === 'orange_aces' && $index === 2) {
581 | $status = 'cancelled';
582 | $isCancelled = 1;
583 | }
584 |
585 | if ($groupKey === 'orange_aces' && $index === 3) {
586 | $status = 'recovery';
587 | $recoveryFor = $cancelledSessionId;
588 | }
589 |
590 | $sessionModel->insert([
591 | 'academy_group_id' => $groupId,
592 | 'coach_id' => $this->coachIds[$this->groupCoachKey($groupKey)],
593 | 'session_date' => $sessionDate,
594 | 'start_time' => $groupKey === 'green_smash' ? '18:00:00' : '17:00:00',
595 | 'end_time' => $groupKey === 'green_smash' ? '19:30:00' : '18:30:00',
596 | 'status' => $status,
597 | 'is_cancelled' => $isCancelled,
598 | 'cancelled_reason' => $isCancelled ? 'Trainer indisponibil - sesiune mutata pe recovery.' : null,
599 | 'recovery_for_session_id' => $recoveryFor,
600 | 'notes' => $status === 'recovery' ? 'Sesiune de recuperare programata automat.' : null,
601 | 'created_at' => $sessionDate . ' 08:00:00',
602 | 'updated_at' => $sessionDate . ' 08:00:00',
603 | ]);
604 |
605 | $sessionId = (int) $sessionModel->getInsertID();
606 |
607 | if ($isCancelled) {
608 | $cancelledSessionId = $sessionId;
609 | continue;
610 | }
611 |
612 | foreach ($this->children as $childKey => $child) {
613 | if ($child['group_key'] !== $groupKey || ! $this->childAttendsSession($child['status'], $offset)) {
614 | continue;
615 | }
616 |
617 | $pattern = $patterns[$childKey] ?? array_fill(0, count($offsets), 'present');
618 | $status = $pattern[$index] ?? 'present';
619 |
620 | if ($status === 'recovery' && $groupKey !== 'orange_aces') {
621 | $status = 'present';
622 | }
623 |
624 | if ($groupKey === 'orange_aces' && $index === 3 && $status === 'present') {
625 | $status = 'recovery';
626 | }
627 |
628 | $recordModel->insert([
629 | 'attendance_session_id' => $sessionId,
630 | 'child_id' => $child['id'],
631 | 'status' => $status,
632 | 'checked_in_at' => in_array($status, ['present', 'recovery'], true) ? $sessionDate . ' 16:55:00' : null,
633 | 'notes' => $status === 'absent_unexcused' ? 'Absenta nemotivata.' : null,
634 | 'created_at' => $sessionDate . ' 20:00:00',
635 | 'updated_at' => $sessionDate . ' 20:00:00',
636 | ]);
637 | }
638 | }
639 | }
640 |
641 | foreach (['maria_enache', 'teodor_stan'] as $childKey) {
642 | $journey->recordEvent([
643 | 'child_id' => $this->children[$childKey]['id'],
644 | 'event_type' => 'attendance_issue',
645 | 'title' => 'Avertizare de prezenta',
646 | 'description' => 'Copilul a depasit pragul de absente pentru perioada recenta.',
647 | 'metadata' => json_encode(['source' => 'seed'], JSON_UNESCAPED_UNICODE),
648 | 'created_by' => $this->userIds['admin'],
649 | ]);
650 | }
651 | }
652 |
653 | private function seedEvaluations(): void
654 | {
655 | $plans = $this->evaluationPlan();
656 |
657 | foreach ($plans as $childKey => $entries) {
658 | foreach ($entries as $entry) {
659 | $this->createEvaluation(
660 | $childKey,
661 | $entry['type'],
662 | $entry['days_ago'],
663 | $entry['template'],
664 | $entry['note'],
665 | );
666 | }
667 | }
668 | }
669 |
670 | private function seedNotifications(): void
671 | {
672 | $model = model(NotificationModel::class);
673 | $now = date('Y-m-d H:i:s');
674 |
675 | $model->insertBatch([
676 | [
677 | 'user_id' => $this->userIds['admin'],
678 | 'child_id' => $this->children['luca_marin']['id'],
679 | 'type' => 'promotion_ready',
680 | 'title' => 'Copil pregatit pentru avansare',
681 | 'message' => 'Luca Marin are ultima evaluare Full cu status READY.',
682 | 'status' => 'unread',
683 | 'action_url' => '/dashboard',
684 | 'created_at' => $now,
685 | 'read_at' => null,
686 | ],
687 | [
688 | 'user_id' => $this->userIds['coach_andrei'],
689 | 'child_id' => $this->children['luca_marin']['id'],
690 | 'type' => 'promotion_ready',
691 | 'title' => 'Ready in grupa ta',
692 | 'message' => 'Luca Marin poate intra in discutia de promovare.',
693 | 'status' => 'unread',
694 | 'action_url' => '/dashboard',
695 | 'created_at' => $now,
696 | 'read_at' => null,
697 | ],
698 | [
699 | 'user_id' => $this->userIds['admin'],
700 | 'child_id' => $this->children['maria_enache']['id'],
701 | 'type' => 'attendance_alert',
702 | 'title' => 'Prezenta scazuta',
703 | 'message' => 'Maria Enache are absente ridicate in ultimele sedinte.',
704 | 'status' => 'unread',
705 | 'action_url' => '/dashboard',
706 | 'created_at' => $now,
707 | 'read_at' => null,
708 | ],
709 | [
710 | 'user_id' => $this->userIds['admin'],
711 | 'child_id' => $this->children['daria_neagu']['id'],
712 | 'type' => 'evaluation_overdue',
713 | 'title' => 'Fara evaluare recenta',
714 | 'message' => 'Daria Neagu nu are evaluare in ultimele 45 de zile.',
715 | 'status' => 'unread',
716 | 'action_url' => '/dashboard',
717 | 'created_at' => $now,
718 | 'read_at' => null,
719 | ],
720 | ]);
721 | }
722 |
723 | private function createUser(string $username, string $email, string $shieldGroup, string $appRole): int
724 | {
725 | $user = new User([
726 | 'username' => $username,
727 | 'email' => $email,
728 | 'password' => $this->defaultPassword,
729 | 'active' => 1,
730 | ]);
731 |
732 | $this->userModel->save($user);
733 |
734 | $created = $this->userModel->findByCredentials(['email' => $email]);
735 | if (! $created) {
736 | throw new RuntimeException('Unable to create user ' . $email);
737 | }
738 |
739 | $created->addGroup($shieldGroup);
740 |
741 | model(UserRoleModel::class)->insert([
742 | 'user_id' => (int) $created->id,
743 | 'role_id' => $this->roleIds[$appRole],
744 | 'created_at' => date('Y-m-d H:i:s'),
745 | ]);
746 |
747 | return (int) $created->id;
748 | }
749 |
750 | private function createEvaluation(string $childKey, string $typeKey, int $daysAgo, string $templateKey, string $note): void
751 | {
752 | $child = $this->children[$childKey];
753 | $typeId = $this->evaluationTypeIds[$typeKey];
754 | $date = (new \DateTimeImmutable('today'))->modify('-' . $daysAgo . ' days')->format('Y-m-d');
755 | $coachKey = $child['coach_key'];
756 | $template = $this->scoreTemplates()[$templateKey];
757 | $criteriaRows = [];
758 |
759 | foreach ($this->criteriaByType[$typeKey] as $criterionSlug) {
760 | $criteriaRows[] = [
761 | 'evaluation_criteria_id' => $this->criteria[$criterionSlug]['id'],
762 | 'weight' => $this->criteria[$criterionSlug]['weight'],
763 | 'score' => $template[$criterionSlug],
764 | 'is_critical' => $this->criteria[$criterionSlug]['is_critical'],
765 | 'notes' => null,
766 | ];
767 | }
768 |
769 | $result = service('evaluationEngine')->evaluate($criteriaRows, $this->rule);
770 | $promotionSignal = $typeKey === 'full-evaluation' ? (int) $result['promotion_signal'] : 0;
771 |
772 | $evaluationModel = model(EvaluationModel::class);
773 | $evaluationModel->insert([
774 | 'child_id' => $child['id'],
775 | 'evaluation_type_id' => $typeId,
776 | 'evaluation_date' => $date,
777 | 'evaluator_user_id' => $this->userIds[$coachKey],
778 | 'evaluator_coach_id' => $this->coachIds[$coachKey],
779 | 'level_at_time_id' => $this->levelIds[$child['level_key']],
780 | 'final_score' => $result['final_score'],
781 | 'final_status' => $result['final_status'],
782 | 'promotion_signal' => $promotionSignal,
783 | 'notes' => $note,
784 | 'created_at' => $date . ' 12:00:00',
785 | 'updated_at' => $date . ' 12:00:00',
786 | ]);
787 |
788 | $evaluationId = (int) $evaluationModel->getInsertID();
789 |
790 | foreach ($criteriaRows as $criteriaRow) {
791 | model(EvaluationScoreModel::class)->insert(array_merge($criteriaRow, [
792 | 'evaluation_id' => $evaluationId,
793 | ]));
794 | }
795 |
796 | model(EvaluationStatusHistoryModel::class)->insert([
797 | 'evaluation_id' => $evaluationId,
798 | 'previous_status' => null,
799 | 'new_status' => $result['final_status'],
800 | 'reason' => 'Seeded initial evaluation status.',
801 | 'changed_by' => $this->userIds[$coachKey],
802 | 'created_at' => $date . ' 12:05:00',
803 | ]);
804 |
805 | service('journey')->recordEvent([
806 | 'child_id' => $child['id'],
807 | 'event_type' => $typeKey === 'quick-check' ? 'quick_check_added' : 'evaluation_added',
808 | 'title' => $typeKey === 'quick-check' ? 'Quick Check adaugat' : 'Full Evaluation adaugata',
809 | 'description' => $note,
810 | 'metadata' => json_encode([
811 | 'type' => $typeKey,
812 | 'score' => $result['final_score'],
813 | 'status' => $result['final_status'],
814 | ], JSON_UNESCAPED_UNICODE),
815 | 'created_by' => $this->userIds[$coachKey],
816 | 'created_at' => $date . ' 12:10:00',
817 | ]);
818 | }
819 |
820 | /**
821 | * @return array<string, array<string, mixed>>
822 | */
823 | private function childDefinitions(): array
824 | {
825 | return [
826 | 'luca_marin' => $this->childDefinition('Luca', 'Marin', '2016-04-12', 'male', 'rechin', 'red_sparks', 'coach_andrei', 'active', '2025-09-02', 1, 'Adaptare buna in apa si ritm foarte bun.', 'Cristina Marin', '0740 100 100', 'cristina@academia.local', 'parent_cristina'),
827 | 'sofia_radu' => $this->childDefinition('Sofia', 'Radu', '2017-01-20', 'female', 'delfinas', 'red_sparks', 'coach_andrei', 'active', '2025-10-10', 0, 'Foarte receptiva la feedback.', 'Elena Radu', '0740 100 101', 'elena.radu@academia.local'),
828 | 'matei_dinu' => $this->childDefinition('Matei', 'Dinu', '2016-08-05', 'male', 'rechin', 'red_sparks', 'coach_andrei', 'active', '2025-09-18', 0, 'Constanta in crestere, are nevoie de mai multa incredere.', 'Roxana Dinu', '0740 100 102', 'roxana.dinu@academia.local'),
829 | 'emma_pavel' => $this->childDefinition('Emma', 'Pavel', '2015-07-11', 'female', 'pre_performanta', 'orange_aces', 'coach_ioana', 'active', '2025-08-15', 0, 'Se misca bine in bazin, coordonare in progres.', 'Adrian Pavel', '0740 100 103', 'adrian.pavel@academia.local'),
830 | 'david_ilie' => $this->childDefinition('David', 'Ilie', '2015-11-09', 'male', 'pre_performanta', 'orange_aces', 'coach_ioana', 'active', '2025-08-22', 1, 'Competitiv, raspunde bine la obiective clare.', 'Corina Ilie', '0740 100 104', 'corina.ilie@academia.local'),
831 | 'ana_toma' => $this->childDefinition('Ana', 'Toma', '2014-09-03', 'female', 'sportiv_performanta', 'orange_aces', 'coach_ioana', 'active', '2025-07-30', 0, 'Stabila in sesiuni, aproape de pragul urmator.', 'Andreea Toma', '0740 100 105', 'andreea.toma@academia.local'),
832 | 'teodor_stan' => $this->childDefinition('Teodor', 'Stan', '2014-12-14', 'male', 'sportiv_performanta', 'orange_aces', 'coach_ioana', 'active', '2025-07-18', 0, 'Prezenta inconstanta in ultima luna.', 'Mihnea Stan', '0740 100 106', 'mihnea.stan@academia.local'),
833 | 'ilinca_pop' => $this->childDefinition('Ilinca', 'Pop', '2013-06-01', 'female', 'sportiv_performanta', 'green_smash', 'coach_mihai', 'active', '2025-06-20', 0, 'Control bun pe serii lungi.', 'Camelia Pop', '0740 100 107', 'camelia.pop@academia.local'),
834 | 'victor_oprea' => $this->childDefinition('Victor', 'Oprea', '2013-10-27', 'male', 'sportiv_performanta', 'green_smash', 'coach_mihai', 'active', '2025-06-28', 0, 'Nivel bun, inca lucreaza la strategie de cursa.', 'Laura Oprea', '0740 100 108', 'laura.oprea@academia.local'),
835 | 'maria_enache' => $this->childDefinition('Maria', 'Enache', '2012-03-16', 'female', 'sportiv_performanta', 'green_smash', 'coach_mihai', 'active', '2025-05-12', 0, 'Potential bun, dar absentele incetinesc progresul.', 'George Enache', '0740 100 109', 'george.enache@academia.local'),
836 | 'alex_tudor' => $this->childDefinition('Alex', 'Tudor', '2017-09-21', 'male', 'delfinas', 'red_sparks', 'coach_andrei', 'active', '2025-10-18', 0, 'Energetic, are nevoie de mai multa consistenta.', 'Bianca Tudor', '0740 100 110', 'bianca.tudor@academia.local'),
837 | 'eva_nistor' => $this->childDefinition('Eva', 'Nistor', '2015-02-07', 'female', 'pre_performanta', 'orange_aces', 'coach_ioana', 'pause', '2025-08-05', 0, 'Pauza temporara, revenire planificata luna viitoare.', 'Sorin Nistor', '0740 100 111', 'sorin.nistor@academia.local'),
838 | 'rares_mocanu' => $this->childDefinition('Rares', 'Mocanu', '2013-01-30', 'male', 'sportiv_performanta', 'green_smash', 'coach_mihai', 'active', '2025-05-28', 0, 'Stagnare usoara pe partea de cursa.', 'Daniela Mocanu', '0740 100 112', 'daniela.mocanu@academia.local'),
839 | 'daria_neagu' => $this->childDefinition('Daria', 'Neagu', '2012-11-11', 'female', 'sportiv_performanta', 'green_smash', 'coach_mihai', 'active', '2025-05-02', 0, 'Necesita reevaluare completa dupa revenirea in ritm.', 'Silviu Neagu', '0740 100 113', 'silviu.neagu@academia.local'),
840 | 'tudor_barbu' => $this->childDefinition('Tudor', 'Barbu', '2016-06-18', 'male', 'rechin', 'red_sparks', 'coach_andrei', 'left', '2025-09-10', 0, 'A parasit temporar programul.', 'Anca Barbu', '0740 100 114', 'anca.barbu@academia.local'),
841 | ];
842 | }
843 |
844 | /**
845 | * @return array<string, mixed>
846 | */
847 | private function childDefinition(
848 | string $firstName,
849 | string $lastName,
850 | string $birthDate,
851 | string $gender,
852 | string $levelKey,
853 | string $groupKey,
854 | string $coachKey,
855 | string $status,
856 | string $firstClassAt,
857 | int $hasSiblings,
858 | string $notes,
859 | string $guardianName,
860 | string $guardianPhone,
861 | string $guardianEmail,
862 | ?string $guardianUserKey = null,
863 | ): array {
864 | return [
865 | 'first_name' => $firstName,
866 | 'last_name' => $lastName,
867 | 'birth_date' => $birthDate,
868 | 'gender' => $gender,
869 | 'level_key' => $levelKey,
870 | 'group_key' => $groupKey,
871 | 'coach_key' => $coachKey,
872 | 'status' => $status,
873 | 'first_class_at' => $firstClassAt,
874 | 'has_siblings' => $hasSiblings,
875 | 'notes' => $notes,
876 | 'guardian' => [
877 | 'full_name' => $guardianName,
878 | 'phone' => $guardianPhone,
879 | 'email' => $guardianEmail,
880 | 'user_key' => $guardianUserKey,
881 | ],
882 | ];
883 | }
884 |
885 | /**
886 | * @return array<string, list<string>>
887 | */
888 | private function attendancePatterns(): array
889 | {
890 | return [
891 | 'luca_marin' => ['present', 'present', 'present', 'present', 'recovery', 'present', 'present', 'present'],
892 | 'sofia_radu' => ['present', 'present', 'absent_excused', 'present', 'present', 'present', 'present', 'present'],
893 | 'matei_dinu' => ['present', 'absent_unexcused', 'present', 'present', 'absent_excused', 'present', 'present', 'present'],
894 | 'emma_pavel' => ['present', 'present', 'present', 'present', 'present', 'present', 'present', 'present'],
895 | 'david_ilie' => ['present', 'present', 'present', 'present', 'recovery', 'present', 'present', 'present'],
896 | 'ana_toma' => ['present', 'present', 'absent_excused', 'recovery', 'present', 'present', 'present', 'present'],
897 | 'teodor_stan' => ['present', 'absent_unexcused', 'absent_unexcused', 'present', 'present', 'absent_excused', 'present', 'present'],
898 | 'ilinca_pop' => ['present', 'present', 'present', 'present', 'present', 'present', 'present', 'present'],
899 | 'victor_oprea' => ['present', 'present', 'present', 'present', 'present', 'absent_excused', 'present', 'present'],
900 | 'maria_enache' => ['present', 'absent_unexcused', 'absent_unexcused', 'present', 'absent_excused', 'absent_unexcused', 'recovery', 'absent_unexcused'],
901 | 'alex_tudor' => ['present', 'present', 'present', 'present', 'present', 'present', 'absent_excused', 'present'],
902 | 'eva_nistor' => ['present', 'present', 'absent_excused', 'present', 'present', 'present', 'absent_excused', 'absent_excused'],
903 | 'rares_mocanu' => ['present', 'present', 'present', 'present', 'absent_unexcused', 'present', 'present', 'present'],
904 | 'daria_neagu' => ['present', 'present', 'present', 'present', 'present', 'present', 'present', 'present'],
905 | 'tudor_barbu' => ['present', 'present', 'absent_unexcused', 'present', 'present', 'present', 'present', 'present'],
906 | ];
907 | }
908 |
909 | /**
910 | * @return array<string, list<array<string, int|string>>>
911 | */
912 | private function evaluationPlan(): array
913 | {
914 | return [
915 | 'luca_marin' => $this->standardPlan('ready', 6),
916 | 'sofia_radu' => $this->standardPlan('ready', 9),
917 | 'matei_dinu' => $this->standardPlan('almost', 5),
918 | 'emma_pavel' => $this->standardPlan('almost', 4),
919 | 'david_ilie' => $this->standardPlan('ready', 8),
920 | 'ana_toma' => $this->standardPlan('almost', 11),
921 | 'teodor_stan' => $this->standardPlan('hold', 7),
922 | 'ilinca_pop' => $this->standardPlan('ready', 10),
923 | 'victor_oprea' => $this->standardPlan('almost', 12),
924 | 'maria_enache' => $this->standardPlan('hold', 3),
925 | 'alex_tudor' => $this->standardPlan('hold', 13),
926 | 'eva_nistor' => $this->standardPlan('almost', 20),
927 | 'rares_mocanu' => $this->standardPlan('hold', 14),
928 | 'daria_neagu' => [
929 | ['type' => 'full-evaluation', 'days_ago' => 58, 'template' => 'full_hold_old', 'note' => 'Ultima evaluare completa este depasita si necesita refresh.'],
930 | ],
931 | 'tudor_barbu' => [
932 | ['type' => 'full-evaluation', 'days_ago' => 75, 'template' => 'full_hold_old', 'note' => 'Evaluare istorica pastrata pentru traseul copilului.'],
933 | ],
934 | ];
935 | }
936 |
937 | /**
938 | * @return list<array<string, int|string>>
939 | */
940 | private function standardPlan(string $status, int $latestFullDaysAgo): array
941 | {
942 | return match ($status) {
943 | 'ready' => [
944 | ['type' => 'full-evaluation', 'days_ago' => 52, 'template' => 'full_ready_old', 'note' => 'Evaluare completa de referinta, aproape de READY.'],
945 | ['type' => 'quick-check', 'days_ago' => 18, 'template' => 'quick_ready', 'note' => 'Quick Check pozitiv, progres confirmat intre evaluari.'],
946 | ['type' => 'full-evaluation', 'days_ago' => $latestFullDaysAgo, 'template' => 'full_ready', 'note' => 'Evaluare completa recenta, copil pregatit pentru discutie de promovare.'],
947 | ],
948 | 'almost' => [
949 | ['type' => 'full-evaluation', 'days_ago' => 49, 'template' => 'full_almost_old', 'note' => 'Evaluare anterioara cu progres constant, dar sub pragul final.'],
950 | ['type' => 'quick-check', 'days_ago' => 16, 'template' => 'quick_almost', 'note' => 'Quick Check de monitorizare, copil aproape de READY.'],
951 | ['type' => 'full-evaluation', 'days_ago' => $latestFullDaysAgo, 'template' => 'full_almost', 'note' => 'Evaluare completa recenta, copil aproape gata pentru nivelul urmator.'],
952 | ],
953 | default => [
954 | ['type' => 'full-evaluation', 'days_ago' => 47, 'template' => 'full_hold_old', 'note' => 'Evaluare anterioara cu blocaj pe criteriile critice.'],
955 | ['type' => 'quick-check', 'days_ago' => 15, 'template' => 'quick_hold', 'note' => 'Quick Check folosit pentru monitorizare interna si alerta.'],
956 | ['type' => 'full-evaluation', 'days_ago' => $latestFullDaysAgo, 'template' => 'full_hold', 'note' => 'Evaluare completa recenta, copilul ramane in HOLD.'],
957 | ],
958 | };
959 | }
960 |
961 | /**
962 | * @return array<string, array<string, float>>
963 | */
964 | private function scoreTemplates(): array
965 | {
966 | return [
967 | 'full_ready_old' => [
968 | 'water-confidence' => 3.8,
969 | 'breathing-control' => 3.9,
970 | 'stroke-technique' => 4.0,
971 | 'start-turn-basics' => 3.6,
972 | 'race-awareness' => 3.7,
973 | 'focus-effort' => 3.9,
974 | ],
975 | 'full_ready' => [
976 | 'water-confidence' => 4.4,
977 | 'breathing-control' => 4.2,
978 | 'stroke-technique' => 4.5,
979 | 'start-turn-basics' => 4.0,
980 | 'race-awareness' => 4.1,
981 | 'focus-effort' => 4.4,
982 | ],
983 | 'full_almost_old' => [
984 | 'water-confidence' => 3.2,
985 | 'breathing-control' => 3.3,
986 | 'stroke-technique' => 3.4,
987 | 'start-turn-basics' => 3.0,
988 | 'race-awareness' => 3.1,
989 | 'focus-effort' => 3.3,
990 | ],
991 | 'full_almost' => [
992 | 'water-confidence' => 3.6,
993 | 'breathing-control' => 3.7,
994 | 'stroke-technique' => 3.8,
995 | 'start-turn-basics' => 3.4,
996 | 'race-awareness' => 3.5,
997 | 'focus-effort' => 3.7,
998 | ],
999 | 'full_hold_old' => [
1000 | 'water-confidence' => 2.9,
1001 | 'breathing-control' => 3.0,
1002 | 'stroke-technique' => 2.8,
1003 | 'start-turn-basics' => 3.1,
1004 | 'race-awareness' => 3.0,
1005 | 'focus-effort' => 3.2,
1006 | ],
1007 | 'full_hold' => [
1008 | 'water-confidence' => 3.0,
1009 | 'breathing-control' => 3.1,
1010 | 'stroke-technique' => 2.9,
1011 | 'start-turn-basics' => 3.2,
1012 | 'race-awareness' => 3.0,
1013 | 'focus-effort' => 3.3,
1014 | ],
1015 | 'quick_ready' => [
1016 | 'water-confidence' => 4.3,
1017 | 'breathing-control' => 4.1,
1018 | 'stroke-technique' => 4.3,
1019 | 'focus-effort' => 4.4,
1020 | ],
1021 | 'quick_almost' => [
1022 | 'water-confidence' => 3.7,
1023 | 'breathing-control' => 3.6,
1024 | 'stroke-technique' => 3.7,
1025 | 'focus-effort' => 3.8,
1026 | ],
1027 | 'quick_hold' => [
1028 | 'water-confidence' => 3.0,
1029 | 'breathing-control' => 3.0,
1030 | 'stroke-technique' => 2.8,
1031 | 'focus-effort' => 3.2,
1032 | ],
1033 | ];
1034 | }
1035 |
1036 | private function groupCoachKey(string $groupKey): string
1037 | {
1038 | return match ($groupKey) {
1039 | 'red_sparks' => 'coach_andrei',
1040 | 'orange_aces' => 'coach_ioana',
1041 | default => 'coach_mihai',
1042 | };
1043 | }
1044 |
1045 | private function childAttendsSession(string $status, int $offset): bool
1046 | {
1047 | if ($status === 'left' && $offset > -18) {
1048 | return false;
1049 | }
1050 |
1051 | if ($status === 'pause' && $offset > -8) {
1052 | return false;
1053 | }
1054 |
1055 | return true;
1056 | }
1057 | }