Add multi-sport tracking with configurable recovery bonuses
This commit is contained in:
@@ -6,6 +6,8 @@ final class ScoringService
|
||||
{
|
||||
public function normalize(array $input): array
|
||||
{
|
||||
$sportTypes = normalize_sport_type_selection($input['sport_types'] ?? ($input['sport_type'] ?? []));
|
||||
|
||||
return [
|
||||
'date' => $input['date'] ?? today(),
|
||||
'mood' => max(1, min(10, (int) ($input['mood'] ?? 5))),
|
||||
@@ -14,16 +16,21 @@ final class ScoringService
|
||||
'sleep_hours' => max(0, min(24, (float) ($input['sleep_hours'] ?? 0))),
|
||||
'sleep_feeling' => max(1, min(5, (int) ($input['sleep_feeling'] ?? 3))),
|
||||
'sport_minutes' => max(0, min(1440, (int) ($input['sport_minutes'] ?? 0))),
|
||||
'sport_type' => $sportTypes[0] ?? '',
|
||||
'sport_types' => $sportTypes,
|
||||
'walk_minutes' => max(0, min(1440, (int) ($input['walk_minutes'] ?? 0))),
|
||||
'note' => trim((string) ($input['note'] ?? '')),
|
||||
];
|
||||
}
|
||||
|
||||
public function evaluate(array $entry, array $settings): array
|
||||
public function evaluate(array $entry, array $settings, ?array $previousEntry = null): array
|
||||
{
|
||||
$entry = $this->normalize($entry);
|
||||
$previousEntry = $previousEntry !== null ? $this->normalize($previousEntry) : null;
|
||||
$scoring = $settings['scoring'];
|
||||
$ratings = $this->sortedRatings($settings['ratings'] ?? []);
|
||||
$sportTypes = find_sport_types($settings, $entry['sport_types']);
|
||||
$sportBonus = $this->sportBonusPoints($entry, $previousEntry, $settings, $sportTypes);
|
||||
|
||||
$components = [
|
||||
'mood' => $entry['mood'] * (float) $scoring['mood_multiplier'],
|
||||
@@ -32,6 +39,7 @@ final class ScoringService
|
||||
'sleep_hours' => $this->sleepDurationPoints((float) $entry['sleep_hours'], $scoring['sleep_duration_points']),
|
||||
'sleep_feeling' => $entry['sleep_feeling'] * (float) $scoring['sleep_feeling_multiplier'],
|
||||
'sport_minutes' => $this->bandPoints((int) $entry['sport_minutes'], $scoring['sport_bands']),
|
||||
'sport_bonus' => $sportBonus,
|
||||
'walk_minutes' => $this->bandPoints((int) $entry['walk_minutes'], $scoring['walk_bands']),
|
||||
'note' => $entry['note'] === '' ? 0 : (float) $scoring['journal_points'],
|
||||
];
|
||||
@@ -44,6 +52,7 @@ final class ScoringService
|
||||
max(array_map('floatval', $scoring['sleep_duration_points'])) +
|
||||
(5 * (float) $scoring['sleep_feeling_multiplier']) +
|
||||
$this->maxBandPoints($scoring['sport_bands']) +
|
||||
$this->maxSportBonusPoints($settings) +
|
||||
$this->maxBandPoints($scoring['walk_bands']) +
|
||||
(float) $scoring['journal_points'],
|
||||
1
|
||||
@@ -74,6 +83,8 @@ final class ScoringService
|
||||
'guardrail' => $guardrail,
|
||||
'sentiment' => $this->sentimentForLabel($label, $ratings),
|
||||
'percentage' => $maxTotal > 0 ? round(($total / $maxTotal) * 100, 1) : 0,
|
||||
'sport_type' => $sportTypes[0] ?? null,
|
||||
'sport_types' => $sportTypes,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -135,6 +146,86 @@ final class ScoringService
|
||||
return $max;
|
||||
}
|
||||
|
||||
private function maxSportBonusPoints(array $settings): float
|
||||
{
|
||||
$max = 0.0;
|
||||
$perGroup = [];
|
||||
|
||||
foreach (normalized_sport_types($settings) as $type) {
|
||||
$bonus = (float) ($type['bonus_points'] ?? 0);
|
||||
if ($bonus <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!empty($type['allow_consecutive'])) {
|
||||
$max += $bonus;
|
||||
continue;
|
||||
}
|
||||
|
||||
$group = (string) ($type['recovery_group'] ?? $type['id'] ?? '');
|
||||
if ($group === '') {
|
||||
$group = (string) ($type['id'] ?? '');
|
||||
}
|
||||
|
||||
$perGroup[$group] = max((float) ($perGroup[$group] ?? 0), $bonus);
|
||||
}
|
||||
|
||||
foreach ($perGroup as $bonus) {
|
||||
$max += $bonus;
|
||||
}
|
||||
|
||||
return $max;
|
||||
}
|
||||
|
||||
private function sportBonusPoints(array $entry, ?array $previousEntry, array $settings, array $currentSportTypes): float
|
||||
{
|
||||
if ((int) $entry['sport_minutes'] <= 0 || $currentSportTypes === []) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$previousGroups = [];
|
||||
if ($previousEntry !== null && (int) $previousEntry['sport_minutes'] > 0) {
|
||||
foreach (find_sport_types($settings, $previousEntry['sport_types']) as $type) {
|
||||
$group = (string) ($type['recovery_group'] ?? $type['id'] ?? '');
|
||||
if ($group !== '') {
|
||||
$previousGroups[$group] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$groupBonuses = [];
|
||||
$total = 0.0;
|
||||
|
||||
foreach ($currentSportTypes as $type) {
|
||||
$bonus = (float) ($type['bonus_points'] ?? 0);
|
||||
if ($bonus <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$group = (string) ($type['recovery_group'] ?? $type['id'] ?? '');
|
||||
if ($group === '') {
|
||||
$group = (string) ($type['id'] ?? '');
|
||||
}
|
||||
|
||||
if (!empty($type['allow_consecutive'])) {
|
||||
$total += $bonus;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($previousGroups[$group])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$groupBonuses[$group] = max((float) ($groupBonuses[$group] ?? 0), $bonus);
|
||||
}
|
||||
|
||||
foreach ($groupBonuses as $bonus) {
|
||||
$total += $bonus;
|
||||
}
|
||||
|
||||
return $total;
|
||||
}
|
||||
|
||||
private function sortedRatings(array $ratings): array
|
||||
{
|
||||
usort($ratings, static fn (array $a, array $b): int => ((int) $a['min']) <=> ((int) $b['min']));
|
||||
|
||||
Reference in New Issue
Block a user