84 lines
4.5 KiB
Python
84 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Validate MACHINE_EXERCISE_MODEL_V1 catalogs without inferring science."""
|
|
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
|
|
from exercise_names import normalize_catalog_name
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
EXERCISE_ID = re.compile(r"^ex_[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$")
|
|
PROFILE_ID = re.compile(r"^sp_[a-z0-9_]+_v1$")
|
|
LOADS = {"none", "external", "assistance", "bodyweight", "cardio"}
|
|
|
|
|
|
def load(name):
|
|
return json.loads((ROOT / "catalog" / name).read_text(encoding="utf-8"))
|
|
|
|
|
|
def main():
|
|
machine = load("machine-exercises-v1.json")
|
|
profiles = load("scientific-profiles-v1.json")
|
|
equipment = load("equipment-v1.json")
|
|
knowledge = load("exercise-knowledge-v1.json")
|
|
equipment_knowledge = load("equipment-knowledge-v1.json")
|
|
if set(machine) != {"format", "version", "exercises"} or machine["format"] != "trainlog-machine-exercises" or machine["version"] != 1:
|
|
raise ValueError("invalid machine exercise envelope")
|
|
if set(profiles) != {"format", "version", "profiles"} or profiles["format"] != "trainlog-scientific-profiles" or profiles["version"] != 1:
|
|
raise ValueError("invalid scientific profile envelope")
|
|
equipment_ids = {row["id"] for row in equipment["equipment"]}
|
|
exercise_knowledge = {row["exercise_id"] for row in knowledge["exercises"]}
|
|
capabilities = {(row["equipment_id"], cap["display_name"])
|
|
for row in equipment_knowledge["equipment"] for cap in row["capabilities"]}
|
|
profile_ids = set()
|
|
previous = ""
|
|
for row in profiles["profiles"]:
|
|
if set(row) != {"scientific_profile_id", "knowledge_source"}:
|
|
raise ValueError("invalid scientific profile record")
|
|
profile_id = row["scientific_profile_id"]
|
|
if not PROFILE_ID.fullmatch(profile_id) or profile_id <= previous or profile_id in profile_ids:
|
|
raise ValueError("scientific profiles must be unique and sorted")
|
|
previous = profile_id
|
|
profile_ids.add(profile_id)
|
|
source = row["knowledge_source"]
|
|
if source.get("kind") == "exercise":
|
|
if set(source) != {"kind", "exercise_id"} or source["exercise_id"] not in exercise_knowledge:
|
|
raise ValueError("unknown exercise knowledge source")
|
|
elif source.get("kind") == "equipment_capability":
|
|
if set(source) != {"kind", "equipment_id", "capability"} or (source["equipment_id"], source["capability"]) not in capabilities:
|
|
raise ValueError("unknown equipment capability source")
|
|
else:
|
|
raise ValueError("invalid profile source kind")
|
|
seen_ids = set()
|
|
seen_names = set()
|
|
previous = ""
|
|
for row in machine["exercises"]:
|
|
required = {"exercise_id", "display_name", "load_semantics", "machine_variant", "legacy_equipment_id", "scientific_profile_id", "science_state"}
|
|
if set(row) != required or not EXERCISE_ID.fullmatch(row["exercise_id"]):
|
|
raise ValueError("invalid machine exercise record")
|
|
if row["exercise_id"] <= previous or row["exercise_id"] in seen_ids:
|
|
raise ValueError("machine exercises must be unique and sorted")
|
|
previous = row["exercise_id"]
|
|
seen_ids.add(row["exercise_id"])
|
|
name = normalize_catalog_name(row["display_name"])
|
|
if name in seen_names:
|
|
raise ValueError("duplicate machine exercise name")
|
|
seen_names.add(name)
|
|
if row["load_semantics"] is not None and row["load_semantics"] not in LOADS:
|
|
raise ValueError("invalid load semantics")
|
|
if row["legacy_equipment_id"] is not None and row["legacy_equipment_id"] not in equipment_ids:
|
|
raise ValueError("unknown legacy equipment")
|
|
if row["scientific_profile_id"] is not None and row["scientific_profile_id"] not in profile_ids:
|
|
raise ValueError("unknown scientific profile")
|
|
resolved = row["science_state"] == "resolved"
|
|
if row["science_state"] not in {"resolved", "unresolved"} or resolved != (row["scientific_profile_id"] is not None):
|
|
raise ValueError("science state/profile mismatch")
|
|
seated_leg = next(row for row in machine["exercises"] if row["exercise_id"] == "ex_617007f9-7420-4408-91b9-8ffb77900f13")
|
|
if any(seated_leg[key] is not None for key in ("load_semantics", "legacy_equipment_id", "scientific_profile_id")):
|
|
raise ValueError("Seated Leg must remain unresolved")
|
|
print("MACHINE_EXERCISE_MODEL_V1 catalogs: OK")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|