trainlog/tools/generate_session_generation_policy.py

58 lines
3.7 KiB
Python

#!/usr/bin/env python3
"""Generate immutable C data from the canonical session-generation policy."""
from __future__ import annotations
import sys
from pathlib import Path
from validate_session_generation_policy import validate
def c_string(value: str) -> str:
escaped = value.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n")
return f'"{escaped}"'
def main() -> None:
policy_path, catalog_dir, output_path = map(Path, sys.argv[1:4])
root = validate(policy_path, catalog_dir)
lines = [
"/* Generated from session-generation-policy-v1.json; do not edit. */",
'#include "trainlog/session_generation_policy_internal.h"',
"static const TrainlogSessionGenerationGoalPolicy goal_policies[] = {",
]
for goal in ("general", "strength", "hypertrophy", "endurance"):
row = root["goals"][goal]
lines.append(" {%s,%d,%d,%d,%d,%d,%d,%d,%d,%d}," % (
c_string(goal), row["sets"], row["repetitions"], row["rest_seconds"],
*row["sets_range"], *row["repetitions_range"], *row["rest_seconds_range"]))
lines += ["};", "static const int duration_presets_minutes[] = {" +
",".join(str(value) for value in root["duration"]["presets_minutes"]) + "};",
"static const TrainlogSessionGenerationZoneExpansion zone_expansions[] = {"]
for zone_id, expanded in root["zone_expansion"].items():
lines.append(" {%s,%s}," % (c_string(zone_id), c_string("\n".join(expanded))))
lines += ["};", "const TrainlogSessionGenerationPolicy trainlog_session_generation_policy_v1 = {",
f" {root['load']['lookback_seconds']},",
f" {root['exposure']['short_window_seconds']}, {root['exposure']['long_window_seconds']},",
f" {root['selection']['recency']['same_exercise_window_seconds']}, {root['selection']['recency']['same_pattern_window_seconds']},",
f" {root['selection']['max_exercises']}, {root['duration']['custom_min_minutes']}, {root['duration']['custom_max_minutes']},",
" duration_presets_minutes, sizeof(duration_presets_minutes)/sizeof(duration_presets_minutes[0]),",
f" {root['duration']['preparation_seconds']}, {root['duration']['setup_and_transition_seconds_per_exercise']}, {root['duration']['estimated_seconds_per_repetition']},",
f" {root['exposure']['recent_primary_sets_24h_threshold']}, {root['exposure']['recent_secondary_sets_24h_threshold']},",
f" {root['exposure']['repeated_primary_sets_72h_threshold']}, {root['exposure']['repeated_secondary_sets_72h_threshold']},",
" {" + ",".join(str(root["selection"]["score"][key]) for key in (
"requested_primary_zone", "requested_secondary_zone_only", "new_primary_zone", "new_pattern",
"qualifying_working_load_history", "preferred_exercise", "recent_same_exercise", "recent_same_pattern",
"recent_primary_threshold_on_candidate_primary", "recent_secondary_threshold_on_candidate_primary",
"repeated_primary_threshold_on_candidate_primary", "repeated_secondary_threshold_on_candidate_primary",
"any_exposure_flag_on_candidate_secondary_zones")) + "},",
" " + ",".join(c_string("\n".join(root["selection"][key])) for key in (
"upper_push_patterns", "upper_pull_patterns", "lower_extension_patterns", "lower_flexion_patterns")) + ",",
" zone_expansions, sizeof(zone_expansions)/sizeof(zone_expansions[0]),",
" goal_policies, sizeof(goal_policies)/sizeof(goal_policies[0])", "};"]
output_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
if __name__ == "__main__":
main()