Add matched training signals, daily recovery and performance analysis

This commit is contained in:
Peter Stockings
2026-10-03 13:22:32 +10:00
parent e6e2f22479
commit 418e03583a
37 changed files with 1380 additions and 47 deletions
+31 -3
View File
@@ -37,6 +37,9 @@ def extract(text):
"source": "runs",
"cohorts": [],
"comparison": "period",
"reduce": None,
"long_run": 10.0,
"route": None,
}
seen, lines = set(), text.splitlines()
@@ -47,11 +50,26 @@ def extract(text):
for index, source in enumerate(lines):
line, number = source.strip(), index + 1
if line in {"runs", "laps", "segments"}:
if line in {"runs", "laps", "segments", "intervals", "efforts", "days", "health"}:
once("source", number)
options["source"] = line
lines[index] = "runs"
continue
elif match := re.fullmatch(r"reduce by run min (\d+(?:\.\d+)?) min", line):
once("reduce", number)
minutes = float(match[1])
if not 1 <= minutes <= 180:
raise QueryError("Use 1–180 matched minutes per run.", number)
options["reduce"] = minutes
elif match := re.fullmatch(r"long run distance >= (\d+(?:\.\d+)?) km", line):
once("long_run", number)
distance = float(match[1])
if not 1 <= distance <= 100:
raise QueryError("Use a long-run threshold of 1–100 km.", number)
options["long_run"] = distance
elif match := re.fullmatch(r"route similar to (\d+)", line):
once("route", number)
options["route"] = int(match[1])
elif line == "compare previous year":
options["comparison"] = "year"
lines[index] = "compare previous period"
@@ -138,6 +156,16 @@ def extract(text):
def validate(q, catalog):
if q["flag"] and q["source"] in {"laps", "segments", "intervals"} and not q["reduce"]:
raise QueryError("Reduce recordings to independent runs before using a deviation flag.")
if q["reduce"] and q["source"] not in {"intervals", "segments"}:
raise QueryError("reduce by run requires intervals or segments.")
if q["reduce"] and (q["derived"] or q["cohorts"]):
raise QueryError("Reduce matched recordings before adding calculations or cohorts.")
if q["route"] and q["source"] in {"days", "health"}:
raise QueryError("Route matching requires a recording or runs source.")
if q["source"] in {"days", "health"} and q["group"] in {"run", "hour"}:
raise QueryError("Daily observations require calendar, all, or numeric grouping.")
if q["plot"] and q["chart"] == "none":
raise QueryError("A scatter plot conflicts with chart none.")
if q["cohorts"] and (
@@ -167,7 +195,7 @@ def validate(q, catalog):
)
if q["compare"]:
raise QueryError("Stacked cohorts show the current period; use compare none.")
if q["source"] != "runs":
if q["source"] not in {"runs", "days", "health"}:
temporal = {
"gap_since_previous_run",
"distance_change",
@@ -231,7 +259,7 @@ def validate(q, catalog):
*q["extra_metrics"],
]
supported = lambda key: (
q["aggregates"][key]["function"] not in {"sum", "count", "stddev"}
q["aggregates"][key]["function"] not in {"sum", "count", "stddev", "latest"}
if key in q["aggregates"]
else key not in additive
)