VEU.WathBody/3-app/2-cli/Server.WatchBody.Web/Services/PlanSummaryService.cs

98 lines
4.0 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Text.Json;
using Server.WatchBody.Core.Dal.Src.Repository;
using Server.WatchBody.Core.Programs;
namespace Server.WatchBody.Web.Services;
/// <summary>
/// Сводка план/факт по активной версии (spec 05 §2 m08): недели от
/// понедельника недели создания версии; performed — тренировки в
/// плановые дни недели; missed = planned − performed.
/// </summary>
public class PlanSummaryService(
IWorkoutRepository workouts,
IWorkoutMetricsRepository metrics,
ProgramStore programs)
{
private static readonly string[] DayNames =
["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"];
public object Summarize(int athleteId)
{
var active = programs.GetActive(athleteId);
if (active == null || string.IsNullOrWhiteSpace(active.Body))
return new { activeVersion = (int?)null, weeks = Array.Empty<object>() };
var weeks = ParseWeeks(active.Body);
var versionMonday = MondayOf(DateOnly.FromDateTime(active.CreatedAt));
var rows = new List<object>();
for (var i = 0; i < weeks.Count; i++)
{
var from = versionMonday.AddDays(7 * i);
var to = from.AddDays(6);
var plannedDays = weeks[i].ToHashSet(StringComparer.OrdinalIgnoreCase);
var planned = weeks[i].Count;
if (planned == 0) continue;
var inWeek = workouts.Select(new WorkoutFilter
{
AthleteId = athleteId,
DateFrom = from,
DateTo = to,
}).ToList();
// performed — только в плановые дни недели (spec 05)
var performedList = inWeek
.Where(w => plannedDays.Contains(DayName(DateOnly.FromDateTime(w.StartedAt))))
.ToList();
var adherences = performedList
.Select(w => metrics.Select(new WorkoutMetricsFilter
{ AthleteId = athleteId, WorkoutId = w.Id }).FirstOrDefault()?.Adherence)
.Where(a => a != null).ToList();
rows.Add(new
{
week = i + 1,
from = from.ToString("yyyy-MM-dd"),
to = to.ToString("yyyy-MM-dd"),
planned,
performed = performedList.Count,
missed = Math.Max(0, planned - performedList.Count),
avgAdherence = adherences.Count > 0
? Math.Round(adherences.Average(a => a!.Value), 1, MidpointRounding.AwayFromZero)
: (decimal?)null,
});
}
return new { activeVersion = (int?)active.Version, weeks = rows };
}
/// <summary>Дни недели сессий по неделям body (v1 — каждая сессия
/// = одна плановая тренировка; ротация недель — v2).</summary>
private static List<List<string>> ParseWeeks(string body)
{
var result = new List<List<string>>();
try
{
using var doc = JsonDocument.Parse(body);
foreach (var week in doc.RootElement.GetProperty("weeks").EnumerateArray())
{
var days = new List<string>();
if (week.TryGetProperty("sessions", out var sessionNodes))
foreach (var session in sessionNodes.EnumerateArray())
if (session.TryGetProperty("day", out var day) && day.ValueKind == JsonValueKind.String)
days.Add(day.GetString()!);
result.Add(days);
}
}
catch (Exception ex) when (ex is JsonException or KeyNotFoundException)
{
// кривой body — сводка пустая
}
return result;
}
private static DateOnly MondayOf(DateOnly date) =>
date.AddDays(-(((int)date.DayOfWeek + 6) % 7));
private static string DayName(DateOnly date) => DayNames[((int)date.DayOfWeek + 6) % 7];
}