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

71 lines
3.3 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;
namespace Server.WatchBody.Web.Services;
/// <summary>
/// Таймлайн тренировки из facts_json (spec 03 §2 m08): по Lap —
/// длительность/avg/peak пульс/отдых; пульсограмма records с
/// прореживанием до 600 точек.
/// </summary>
public static class FactsTimeline
{
public static (List<Dictionary<string, object?>> Timeline, List<Dictionary<string, int>> Curve) Parse(
string? factsJson, int sessionDurationS)
{
var timeline = new List<Dictionary<string, object?>>();
var curve = new List<Dictionary<string, int>>();
if (string.IsNullOrWhiteSpace(factsJson)) return (timeline, curve);
try
{
using var doc = JsonDocument.Parse(factsJson);
var root = doc.RootElement;
var records = new List<(int t, int hr)>();
if (root.TryGetProperty("records", out var recordNodes))
foreach (var r in recordNodes.EnumerateArray())
if (r.TryGetProperty("t_offset_s", out var t) && r.TryGetProperty("hr", out var hr))
records.Add((t.GetInt32(), hr.GetInt32()));
// пульсограмма: не более 600 точек
if (records.Count > 0)
{
var step = Math.Max(1, (int)Math.Ceiling(records.Count / 600.0));
for (var i = 0; i < records.Count; i += step)
curve.Add(new Dictionary<string, int> { ["t"] = records[i].t, ["hr"] = records[i].hr });
}
if (!root.TryGetProperty("laps", out var lapNodes)) return (timeline, curve);
var laps = new List<(DateTime start, int durationS)>();
foreach (var l in lapNodes.EnumerateArray())
if (l.TryGetProperty("start", out var s) && l.TryGetProperty("duration_s", out var d) &&
DateTime.TryParse(s.GetString(), null, System.Globalization.DateTimeStyles.AssumeUniversal |
System.Globalization.DateTimeStyles.AdjustToUniversal, out var start))
laps.Add((start, d.GetInt32()));
var sessionStart = laps.Count > 0 ? laps.Min(l => l.start)
: records.Count > 0 ? DateTime.MinValue : DateTime.MinValue;
for (var i = 0; i < laps.Count; i++)
{
var offset = (int)(laps[i].start - sessionStart).TotalSeconds;
var end = offset + laps[i].durationS;
var nextStart = i + 1 < laps.Count ? (int)(laps[i + 1].start - sessionStart).TotalSeconds : end;
var inLap = records.Where(r => r.t >= offset && r.t <= end).Select(r => r.hr).ToList();
timeline.Add(new Dictionary<string, object?>
{
["index"] = i,
["startOffsetS"] = offset,
["durationS"] = laps[i].durationS,
["avgHr"] = inLap.Count > 0 ? (int)Math.Round(inLap.Average(), MidpointRounding.AwayFromZero) : null,
["peakHr"] = inLap.Count > 0 ? inLap.Max() : null,
["restAfterS"] = Math.Max(0, nextStart - end),
});
}
}
catch (JsonException)
{
// битые факты — пустой таймлайн
}
return (timeline, curve);
}
}