{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://gymsync.app/schemas/gymsync-block-v2.schema.json",
  "title": "GymSync Training Block v2",
  "description": "A portable 4-16 week training block: an immutable plan (sessions, goals, nutrition) plus an append-only results log. One file travels between the athlete's app and whoever writes the plan (Claude, ChatGPT, a personal trainer).",
  "type": "object",
  "required": [
    "gymsyncBlock",
    "meta",
    "exercises",
    "sessions"
  ],
  "properties": {
    "gymsyncBlock": {
      "const": 2,
      "description": "Format version marker."
    },
    "meta": {
      "$ref": "#/$defs/meta"
    },
    "goals": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/goal"
      }
    },
    "nutrition": {
      "$ref": "#/$defs/nutrition"
    },
    "exercises": {
      "type": "array",
      "minItems": 0,
      "description": "Exercise catalog. Definitions live here ONCE; plan sessions and log entries reference exercises by id. A cardio-only block (e.g. pure running base) carries an empty array: the key itself stays required.",
      "items": {
        "$ref": "#/$defs/exercise"
      }
    },
    "phases": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/phase"
      }
    },
    "sessions": {
      "type": "array",
      "minItems": 1,
      "description": "The PLAN, in chronological order. Immutable once the block starts; in-app edits are recorded as log.sessions[].amendments.",
      "items": {
        "$ref": "#/$defs/plannedSession"
      }
    },
    "log": {
      "$ref": "#/$defs/log"
    },
    "recipes": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/recipe"
      },
      "description": "MIRROR of the user's recipe collection (app injects on every save) + delivery vehicle for trainer proposals; the collection (recipes.json) stays the source of truth: apps MERGE by id on import, never delete."
    },
    "nutritionLog": {
      "type": "object",
      "description": "Food LOG (fact, append-only): day yyyy-MM-dd → slot entries. Same plan≠log barrier as sessions: coach revisions must never touch it.",
      "propertyNames": {
        "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
      },
      "additionalProperties": {
        "type": "array",
        "items": {
          "$ref": "#/$defs/mealLogEntry"
        }
      }
    }
  },
  "patternProperties": {
    "^x-": {}
  },
  "additionalProperties": false,
  "$defs": {
    "slug": {
      "type": "string",
      "pattern": "^[a-z0-9][a-z0-9-]*$",
      "maxLength": 64
    },
    "isoDate": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
    },
    "isoDateTime": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}(:\\d{2})?(Z|[+-]\\d{2}:?\\d{2})?)?$",
      "description": "ISO date-time ('2026-08-10T17:30:00Z') or bare date ('2026-08-10', coach-friendly; apps anchor it at noon). Pattern-enforced: `format` alone is not validated by most tools."
    },
    "meta": {
      "type": "object",
      "required": [
        "id",
        "title",
        "label",
        "start",
        "weeks",
        "units"
      ],
      "properties": {
        "id": {
          "$ref": "#/$defs/slug",
          "description": "Unique block id, e.g. 'block-4-shoulders-2026'. Also the recommended file name: <id>.gymsync.json"
        },
        "title": {
          "type": "string",
          "description": "Human title, e.g. 'Block 4: Shoulder Hypertrophy'."
        },
        "label": {
          "type": "string",
          "maxLength": 24,
          "description": "Short label used in exports and session headers, e.g. 'Block 4'."
        },
        "athlete": {
          "type": "string"
        },
        "createdBy": {
          "enum": [
            "claude",
            "chatgpt",
            "gemini",
            "trainer",
            "app",
            "manual",
            "other"
          ]
        },
        "created": {
          "$ref": "#/$defs/isoDate"
        },
        "start": {
          "$ref": "#/$defs/isoDate"
        },
        "weeks": {
          "type": "integer",
          "minimum": 1,
          "maximum": 16
        },
        "units": {
          "enum": [
            "kg",
            "lb"
          ]
        },
        "locale": {
          "type": "string",
          "description": "BCP-47, e.g. 'pl' or 'en-US'. Language of names/notes."
        },
        "timezone": {
          "type": "string"
        },
        "notes": {
          "type": "string"
        }
      },
      "patternProperties": {
        "^x-": {}
      },
      "additionalProperties": false
    },
    "goal": {
      "type": "object",
      "required": [
        "id",
        "kind",
        "target"
      ],
      "properties": {
        "id": {
          "$ref": "#/$defs/slug"
        },
        "kind": {
          "enum": [
            "lift1RM",
            "liftRepMax",
            "measurement",
            "cardioTime",
            "cardioDistance",
            "habit"
          ],
          "description": "lift1RM/liftRepMax need exerciseId; measurement needs metric; cardio* need cardioKind context in notes or testSessionId."
        },
        "exerciseId": {
          "$ref": "#/$defs/slug"
        },
        "metric": {
          "enum": [
            "weightKg",
            "bodyFatPct",
            "bodyFatTapePct",
            "leanBodyMassKg",
            "waistCm",
            "chestCm",
            "armsCm",
            "shouldersCm",
            "neckCm",
            "hipsCm",
            "thighCm",
            "vo2max"
          ]
        },
        "hr": {
          "type": "integer",
          "minimum": 60,
          "maximum": 220,
          "description": "Reference heart rate for cardio efficiency goals, e.g. pace at HR 135."
        },
        "target": {
          "$ref": "#/$defs/goalValue"
        },
        "baseline": {
          "$ref": "#/$defs/goalValue"
        },
        "direction": {
          "enum": [
            "increase",
            "decrease"
          ]
        },
        "deadline": {
          "$ref": "#/$defs/isoDate"
        },
        "testSessionId": {
          "type": "string",
          "description": "Plan session where this goal is tested."
        },
        "notes": {
          "type": "string"
        }
      },
      "patternProperties": {
        "^x-": {}
      },
      "additionalProperties": false
    },
    "goalValue": {
      "oneOf": [
        {
          "type": "number"
        },
        {
          "type": "object",
          "properties": {
            "kg": {
              "type": "number"
            },
            "reps": {
              "type": "integer",
              "minimum": 1
            },
            "minutes": {
              "type": "number"
            },
            "km": {
              "type": "number"
            }
          },
          "minProperties": 1,
          "additionalProperties": false,
          "patternProperties": {
            "^x-": {}
          }
        }
      ]
    },
    "nutrition": {
      "type": "object",
      "required": [
        "kcal"
      ],
      "properties": {
        "kcal": {
          "type": "integer",
          "minimum": 800
        },
        "proteinG": {
          "type": "integer",
          "minimum": 0
        },
        "carbsG": {
          "type": "integer",
          "minimum": 0
        },
        "fatG": {
          "type": "integer",
          "minimum": 0
        },
        "trainingDayCarbsDelta": {
          "type": "integer",
          "description": "DEPRECATED (pre-M5 flat model): superseded by dayTypes[].weight; tools ignore it when dayTypes present."
        },
        "fastingWindow": {
          "type": "string",
          "description": "e.g. '14:10, last meal 19:00'"
        },
        "notes": {
          "type": "string"
        },
        "dayTypes": {
          "type": "object",
          "description": "Meal-plan templates per session day type (keys: strength/long/interval/easy/swim/rest/…). App resolves the day's type from the plan's sessions; fallback families: tempo→interval, zone2/recovery/technique/run→easy, bike→easy-template (but long rides carry kind:'long'), deload→rest, test→strength.",
          "additionalProperties": {
            "$ref": "#/$defs/mealDayType"
          }
        },
        "dayOverrides": {
          "type": "object",
          "description": "Per-date exceptions (test day, wedding, travel): key yyyy-MM-dd.",
          "propertyNames": {
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "additionalProperties": {
            "$ref": "#/$defs/mealDayOverride"
          }
        }
      },
      "patternProperties": {
        "^x-": {}
      },
      "additionalProperties": false
    },
    "exercise": {
      "type": "object",
      "required": [
        "id",
        "name",
        "muscles"
      ],
      "properties": {
        "id": {
          "$ref": "#/$defs/slug"
        },
        "name": {
          "type": "string"
        },
        "aliases": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "muscles": {
          "type": "array",
          "minItems": 1,
          "items": {
            "enum": [
              "chest",
              "back",
              "shoulders",
              "biceps",
              "triceps",
              "legs",
              "core",
              "fullbody",
              "quads",
              "hamstrings",
              "glutes",
              "calves",
              "grip",
              "traps",
              "rearDelts",
              "forearms",
              "frontDelts",
              "sideDelts",
              "lats",
              "upperBack"
            ]
          },
          "description": "LEGACY płaska lista: pierwsza partia liczona jak primary, reszta jak secondary. Nowe bloki: używaj primary/secondary."
        },
        "equipment": {
          "enum": [
            "barbell",
            "trapbar",
            "dumbbell",
            "cable",
            "machine",
            "bodyweight",
            "kettlebell",
            "band",
            "other",
            "sled",
            "erg",
            "smith"
          ]
        },
        "perArm": {
          "type": "boolean",
          "description": "One-arm-at-a-time exercise; logged sets are per arm (volume ×2, export adds a per-arm marker)."
        },
        "dualDB": {
          "type": "boolean",
          "description": "A dumbbell in EACH hand simultaneously (volume ×2, no export marker)."
        },
        "bodyweight": {
          "type": "boolean",
          "description": "Tonnage counts body weight (+ any added load in 'kg')."
        },
        "notes": {
          "type": "string"
        },
        "primary": {
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "back",
              "biceps",
              "calves",
              "chest",
              "core",
              "forearms",
              "frontDelts",
              "fullbody",
              "glutes",
              "grip",
              "hamstrings",
              "lats",
              "legs",
              "quads",
              "rearDelts",
              "shoulders",
              "sideDelts",
              "traps",
              "triceps",
              "upperBack"
            ]
          },
          "description": "Partie GŁÓWNE (seria bezpośrednia ×1,0 do objętości). Zwykle 1, przy złożonych max 2 (martwy: plecy+nogi)."
        },
        "secondary": {
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "back",
              "biceps",
              "calves",
              "chest",
              "core",
              "forearms",
              "frontDelts",
              "fullbody",
              "glutes",
              "grip",
              "hamstrings",
              "lats",
              "legs",
              "quads",
              "rearDelts",
              "shoulders",
              "sideDelts",
              "traps",
              "triceps",
              "upperBack"
            ]
          },
          "description": "Partie POŚREDNIE (×0,5). 1–3. Ławka: primary=[chest], secondary=[triceps,frontDelts]."
        },
        "movementPattern": {
          "type": "string",
          "enum": [
            "push",
            "pull",
            "hinge",
            "squat",
            "lunge",
            "carry"
          ],
          "description": "Wzorzec ruchu: balans pchanie↔ciągnięcie w bloku."
        }
      },
      "patternProperties": {
        "^x-": {}
      },
      "additionalProperties": false
    },
    "phase": {
      "type": "object",
      "required": [
        "weeks",
        "phase"
      ],
      "properties": {
        "weeks": {
          "type": "array",
          "minItems": 2,
          "maxItems": 2,
          "items": {
            "type": "integer",
            "minimum": 1
          },
          "description": "[firstWeek, lastWeek] inclusive."
        },
        "phase": {
          "enum": [
            "accumulation",
            "intensification",
            "deload",
            "taper",
            "test",
            "transition",
            "maintenance",
            "primer"
          ]
        },
        "note": {
          "type": "string"
        }
      },
      "additionalProperties": false,
      "patternProperties": {
        "^x-": {}
      }
    },
    "plannedSession": {
      "type": "object",
      "required": [
        "id",
        "week",
        "type"
      ],
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1,
          "description": "Unique within the block, e.g. 'W4D1', 'W1R2'."
        },
        "week": {
          "type": "integer",
          "minimum": 1
        },
        "day": {
          "type": "integer",
          "minimum": 1,
          "description": "Plan-day number for strength templates (Day 1/2/3), not weekday."
        },
        "type": {
          "enum": [
            "strength",
            "run",
            "bike",
            "swim",
            "hiit",
            "mobility",
            "other"
          ]
        },
        "name": {
          "type": "string"
        },
        "scheduled": {
          "$ref": "#/$defs/isoDate"
        },
        "warmup": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "work": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/prescription"
          },
          "description": "Strength prescriptions; required when type == 'strength'."
        },
        "cardio": {
          "$ref": "#/$defs/cardioTarget"
        },
        "notes": {
          "type": "string"
        }
      },
      "allOf": [
        {
          "if": {
            "properties": {
              "type": {
                "const": "strength"
              }
            }
          },
          "then": {
            "required": [
              "work"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "enum": [
                  "run",
                  "bike",
                  "swim",
                  "hiit"
                ]
              }
            }
          },
          "then": {
            "required": [
              "cardio"
            ]
          }
        }
      ],
      "patternProperties": {
        "^x-": {}
      },
      "additionalProperties": false
    },
    "prescription": {
      "type": "object",
      "required": [
        "exerciseId",
        "sets",
        "reps"
      ],
      "properties": {
        "exerciseId": {
          "$ref": "#/$defs/slug"
        },
        "variant": {
          "type": "string",
          "description": "e.g. 'Heavy', 'Volume', 'drop set': display only; PRs match on exerciseId."
        },
        "sets": {
          "type": "integer",
          "minimum": 1,
          "maximum": 20
        },
        "reps": {
          "type": "string",
          "pattern": "^(\\d+([-–]\\d+)?|max|amrap)$",
          "description": "'2', '8-12', 'max'."
        },
        "load": {
          "$ref": "#/$defs/load"
        },
        "restSec": {
          "type": "integer",
          "minimum": 0,
          "maximum": 900
        },
        "main": {
          "type": "boolean",
          "description": "Main lift: bold in exports, RPE prompt at finish."
        },
        "notes": {
          "type": "string"
        }
      },
      "patternProperties": {
        "^x-": {}
      },
      "additionalProperties": false
    },
    "load": {
      "description": "Exactly one load style. For bodyweight exercises 'kg' means ADDED load.",
      "oneOf": [
        {
          "type": "object",
          "required": [
            "kg"
          ],
          "properties": {
            "kg": {
              "type": "number",
              "exclusiveMinimum": 0
            }
          },
          "additionalProperties": false,
          "patternProperties": {
            "^x-": {}
          }
        },
        {
          "type": "object",
          "required": [
            "stages",
            "style"
          ],
          "properties": {
            "stages": {
              "type": "array",
              "minItems": 2,
              "items": {
                "type": "number",
                "exclusiveMinimum": 0
              }
            },
            "style": {
              "enum": [
                "perSet",
                "dropRounds"
              ],
              "description": "perSet: one stage per set (stages length MUST equal sets). dropRounds: every set runs through ALL stages back-to-back."
            }
          },
          "additionalProperties": false,
          "patternProperties": {
            "^x-": {}
          }
        },
        {
          "type": "object",
          "required": [
            "percent1RM"
          ],
          "properties": {
            "percent1RM": {
              "type": "number",
              "exclusiveMinimum": 0,
              "maximum": 120
            },
            "of": {
              "$ref": "#/$defs/slug",
              "description": "goalId or exerciseId whose 1RM the percent refers to; defaults to the prescription's exerciseId."
            }
          },
          "additionalProperties": false,
          "patternProperties": {
            "^x-": {}
          }
        },
        {
          "type": "object",
          "required": [
            "rpeTarget"
          ],
          "properties": {
            "rpeTarget": {
              "type": "number",
              "minimum": 5,
              "maximum": 10
            }
          },
          "additionalProperties": false,
          "patternProperties": {
            "^x-": {}
          }
        }
      ]
    },
    "cardioTarget": {
      "type": "object",
      "required": [
        "kind"
      ],
      "properties": {
        "kind": {
          "enum": [
            "zone2",
            "easy",
            "interval",
            "tempo",
            "long",
            "technique",
            "recovery",
            "race",
            "other"
          ]
        },
        "durationMin": {
          "type": "number",
          "exclusiveMinimum": 0
        },
        "distanceKm": {
          "type": "number",
          "exclusiveMinimum": 0
        },
        "hr": {
          "type": "array",
          "minItems": 2,
          "maxItems": 2,
          "items": {
            "type": "integer",
            "minimum": 60,
            "maximum": 220
          },
          "description": "[low, high] target HR."
        },
        "targetPaceMinKm": {
          "type": "number"
        },
        "structure": {
          "type": "string",
          "description": "Free-form interval structure, e.g. '6×400 m @5:00/km, rest 90 s'."
        },
        "intervals": {
          "type": "array",
          "minItems": 1,
          "items": {
            "$ref": "#/$defs/intervalStep"
          },
          "description": "Structured interval phases (ordered). Machine-readable alternative to `structure`; lets an app run the session hands-free (SPEC: Structured intervals)."
        },
        "notes": {
          "type": "string"
        }
      },
      "patternProperties": {
        "^x-": {}
      },
      "additionalProperties": false
    },
    "intervalStep": {
      "description": "One interval-plan item: a named phase (sec XOR meters, plus pace) OR a repeated work/rest block.",
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "phase": {
              "type": "string",
              "description": "Label, e.g. 'rozgrzewka', 'cooldown'."
            },
            "sec": {
              "type": "integer",
              "exclusiveMinimum": 0
            },
            "meters": {
              "type": "integer",
              "exclusiveMinimum": 0
            },
            "pace": {
              "$ref": "#/$defs/pace"
            }
          },
          "oneOf": [
            {
              "required": [
                "sec"
              ]
            },
            {
              "required": [
                "meters"
              ]
            }
          ],
          "patternProperties": {
            "^x-": {}
          },
          "additionalProperties": false
        },
        {
          "type": "object",
          "required": [
            "repeat",
            "work"
          ],
          "properties": {
            "repeat": {
              "type": "integer",
              "minimum": 1
            },
            "work": {
              "$ref": "#/$defs/intervalLeg"
            },
            "rest": {
              "$ref": "#/$defs/intervalLeg"
            }
          },
          "patternProperties": {
            "^x-": {}
          },
          "additionalProperties": false
        }
      ]
    },
    "intervalLeg": {
      "type": "object",
      "properties": {
        "sec": {
          "type": "integer",
          "exclusiveMinimum": 0
        },
        "meters": {
          "type": "integer",
          "exclusiveMinimum": 0
        },
        "pace": {
          "$ref": "#/$defs/pace"
        }
      },
      "oneOf": [
        {
          "required": [
            "sec"
          ]
        },
        {
          "required": [
            "meters"
          ]
        }
      ],
      "patternProperties": {
        "^x-": {}
      },
      "additionalProperties": false
    },
    "pace": {
      "type": "string",
      "pattern": "^[0-9]{1,2}:[0-5][0-9]$",
      "description": "Pace min/km as 'm:ss', e.g. '4:40'. Belt speed = 3600/paceSec km/h."
    },
    "log": {
      "type": "object",
      "description": "Facts. Append-only; written by the app (or by hand). Never edit the plan to record results.",
      "properties": {
        "sessions": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/loggedSession"
          }
        },
        "measurements": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/measurement"
          }
        },
        "dailiesSnapshot": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/dailyEntry"
          },
          "description": "Optional archive written once when the block is CLOSED (sleep/HRV/kcal live in HealthKit day-to-day)."
        },
        "notes": {
          "type": "string"
        }
      },
      "patternProperties": {
        "^x-": {}
      },
      "additionalProperties": false
    },
    "loggedSession": {
      "type": "object",
      "required": [
        "started"
      ],
      "properties": {
        "planSessionId": {
          "type": "string",
          "description": "Links to sessions[].id; omit for unplanned workouts."
        },
        "type": {
          "enum": [
            "strength",
            "run",
            "bike",
            "swim",
            "hiit",
            "mobility",
            "other"
          ],
          "description": "Required when planSessionId is absent."
        },
        "started": {
          "$ref": "#/$defs/isoDateTime"
        },
        "finished": {
          "$ref": "#/$defs/isoDateTime"
        },
        "feeling": {
          "type": "integer",
          "minimum": 1,
          "maximum": 5
        },
        "bodyWeightKg": {
          "type": "number",
          "description": "Snapshot at session start; keeps bodyweight tonnage stable over time."
        },
        "results": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/exerciseResult"
          }
        },
        "watch": {
          "$ref": "#/$defs/watchStats"
        },
        "cardioResult": {
          "$ref": "#/$defs/cardioResult"
        },
        "tonnageKg": {
          "type": "number",
          "description": "Derived; apps may cache it here for portability."
        },
        "amendments": {
          "type": "array",
          "items": {
            "type": "object",
            "required": [
              "at",
              "change"
            ],
            "properties": {
              "at": {
                "$ref": "#/$defs/isoDateTime"
              },
              "change": {
                "type": "string"
              }
            },
            "additionalProperties": false,
            "patternProperties": {
              "^x-": {}
            }
          },
          "description": "Human-readable record of mid-block plan edits affecting this session."
        },
        "notes": {
          "type": "string"
        },
        "skipped": {
          "type": "boolean",
          "description": "Deliberately skipped by the athlete (no training happened). Coaches use this to revise the remaining weeks."
        }
      },
      "patternProperties": {
        "^x-": {}
      },
      "additionalProperties": false
    },
    "exerciseResult": {
      "type": "object",
      "required": [
        "exerciseId",
        "sets"
      ],
      "properties": {
        "exerciseId": {
          "$ref": "#/$defs/slug"
        },
        "variant": {
          "type": "string"
        },
        "sets": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "object",
            "required": [
              "reps"
            ],
            "properties": {
              "reps": {
                "type": "integer",
                "minimum": 0
              },
              "kg": {
                "type": "number",
                "minimum": 0,
                "description": "Actual load; for bodyweight exercises the ADDED load. Omit for pure BW / drop-round sets."
              },
              "rpe": {
                "type": "number",
                "minimum": 5,
                "maximum": 10
              },
              "done": {
                "type": "boolean",
                "default": true
              }
            },
            "additionalProperties": false,
            "patternProperties": {
              "^x-": {}
            }
          }
        },
        "notes": {
          "type": "string"
        }
      },
      "patternProperties": {
        "^x-": {}
      },
      "additionalProperties": false
    },
    "watchStats": {
      "type": "object",
      "properties": {
        "durationMin": {
          "type": "number"
        },
        "avgHR": {
          "type": "integer"
        },
        "maxHR": {
          "type": "integer"
        },
        "kcal": {
          "type": "integer"
        },
        "zoneMin": {
          "type": "array",
          "minItems": 5,
          "maxItems": 5,
          "items": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "additionalProperties": false,
      "patternProperties": {
        "^x-": {}
      }
    },
    "cardioResult": {
      "type": "object",
      "properties": {
        "distanceKm": {
          "type": "number"
        },
        "durationMin": {
          "type": "number"
        },
        "avgPaceMinKm": {
          "type": "number"
        },
        "avgHR": {
          "type": "integer"
        },
        "maxHR": {
          "type": "integer"
        },
        "kcal": {
          "type": "integer"
        },
        "powerW": {
          "type": "integer"
        },
        "cadenceSpm": {
          "type": "integer"
        },
        "indoor": {
          "type": "boolean"
        },
        "zoneMin": {
          "type": "array",
          "minItems": 5,
          "maxItems": 5,
          "items": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "additionalProperties": false,
      "patternProperties": {
        "^x-": {}
      }
    },
    "measurement": {
      "type": "object",
      "required": [
        "date"
      ],
      "properties": {
        "date": {
          "$ref": "#/$defs/isoDate"
        },
        "weightKg": {
          "type": "number"
        },
        "bodyFatPct": {
          "type": "number"
        },
        "waistCm": {
          "type": "number"
        },
        "chestCm": {
          "type": "number"
        },
        "armsCm": {
          "type": "number"
        },
        "shouldersCm": {
          "type": "number"
        },
        "notes": {
          "type": "string"
        },
        "neckCm": {
          "type": "number"
        },
        "hipsCm": {
          "type": "number"
        },
        "thighCm": {
          "type": "number"
        },
        "bodyFatTapePct": {
          "type": "number",
          "description": "BF% computed from tape (US Navy formula): repeatable, unlike bioimpedance."
        }
      },
      "additionalProperties": false,
      "patternProperties": {
        "^x-": {}
      }
    },
    "dailyEntry": {
      "type": "object",
      "required": [
        "date"
      ],
      "properties": {
        "date": {
          "$ref": "#/$defs/isoDate"
        },
        "kcal": {
          "type": "integer"
        },
        "proteinG": {
          "type": "integer"
        },
        "carbsG": {
          "type": "integer"
        },
        "fatG": {
          "type": "integer"
        },
        "sleepMin": {
          "type": "integer"
        },
        "restingHR": {
          "type": "integer"
        },
        "hrvMs": {
          "type": "integer"
        },
        "steps": {
          "type": "integer"
        },
        "trimp": {
          "type": "integer"
        }
      },
      "additionalProperties": false,
      "patternProperties": {
        "^x-": {}
      }
    },
    "mealSlot": {
      "type": "object",
      "required": [
        "id",
        "kind",
        "kcal"
      ],
      "properties": {
        "id": {
          "type": "string"
        },
        "time": {
          "type": "string",
          "pattern": "^\\d{2}:\\d{2}$"
        },
        "kind": {
          "enum": [
            "locked",
            "flex"
          ],
          "description": "locked = ritual anchor (never rescaled); flex = water-filled to the day target."
        },
        "label": {
          "type": "string"
        },
        "idea": {
          "type": "string",
          "description": "flex: suggestion what to build"
        },
        "product": {
          "type": "object",
          "properties": {
            "name": {
              "type": "string"
            },
            "recipeId": {
              "type": "string"
            }
          },
          "additionalProperties": false,
          "description": "locked: concrete product or a recipe from the user's collection"
        },
        "kcal": {
          "type": "integer",
          "minimum": 0
        },
        "p": {
          "type": "number",
          "minimum": 0
        },
        "c": {
          "type": "number",
          "minimum": 0
        },
        "f": {
          "type": "number",
          "minimum": 0
        },
        "rule": {
          "enum": [
            "beforeStrength",
            "beforeRun",
            "afterWorkout",
            "lastMeal"
          ]
        },
        "reminder": {
          "type": "boolean"
        }
      },
      "patternProperties": {
        "^x-": {}
      },
      "additionalProperties": false
    },
    "mealDayType": {
      "type": "object",
      "properties": {
        "weight": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Day multiplier: target(day) = weight × weeklyBudget / Σ(week weights). Defaults: strength 1.08, long/test 1.05, interval/tempo 1.02, swim/bike 0.96, easy-family 0.90, rest/deload 0.82."
        },
        "kcalDelta": {
          "type": "integer"
        },
        "meals": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/mealSlot"
          }
        }
      },
      "patternProperties": {
        "^x-": {}
      },
      "additionalProperties": false
    },
    "mealDayOverride": {
      "type": "object",
      "properties": {
        "note": {
          "type": "string"
        },
        "kcalDelta": {
          "type": "integer"
        },
        "weightOverride": {
          "type": "number",
          "exclusiveMinimum": 0
        },
        "meals": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/mealSlot"
          }
        }
      },
      "patternProperties": {
        "^x-": {}
      },
      "additionalProperties": false
    },
    "recipeIngredient": {
      "type": "object",
      "required": [
        "name",
        "g"
      ],
      "properties": {
        "foodId": {
          "type": "string",
          "description": "optional curated:*/off:*/custom:* reference"
        },
        "name": {
          "type": "string"
        },
        "g": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "Amount in `unit` (field name is historical: semantics: amount)."
        },
        "kcal100": {
          "type": "number",
          "minimum": 0,
          "description": "kcal per 100 UNITS (g or ml: liquid labels state per 100 ml; enter 1:1)."
        },
        "p100": {
          "type": "number",
          "minimum": 0
        },
        "c100": {
          "type": "number",
          "minimum": 0
        },
        "f100": {
          "type": "number",
          "minimum": 0
        },
        "unit": {
          "enum": [
            "g",
            "ml"
          ],
          "description": "ml for liquids; omitted = grams."
        },
        "density": {
          "type": "number",
          "exclusiveMinimum": 0,
          "description": "g/ml for future cross-unit conversion (oil 0.91, honey 1.42)."
        }
      },
      "patternProperties": {
        "^x-": {}
      },
      "additionalProperties": false
    },
    "recipe": {
      "type": "object",
      "required": [
        "id",
        "name",
        "items"
      ],
      "properties": {
        "id": {
          "type": "string",
          "description": "kat-* seed classics · r-* user · tr-* trainer-proposed (trainer owns tr-*)"
        },
        "name": {
          "type": "string"
        },
        "items": {
          "type": "array",
          "minItems": 1,
          "items": {
            "$ref": "#/$defs/recipeIngredient"
          }
        },
        "tags": {
          "type": "array",
          "items": {
            "enum": [
              "pre",
              "intra",
              "post",
              "sniadanie",
              "obiad",
              "kolacja",
              "przekaska",
              "strength",
              "interval",
              "long",
              "easy",
              "swim",
              "rest"
            ]
          },
          "description": "TWO orthogonal axes in one array: slot axis (pre/intra/post/sniadanie/obiad/kolacja/przekaska; intra = DURING training) × session axis (strength/interval/long/easy/swim/rest = canonical dayType). Empty axis = universal on that axis. Matching: slot(S)∈axis1 AND (axis2 empty OR canon(dayType)∈axis2)."
        }
      },
      "patternProperties": {
        "^x-": {}
      },
      "additionalProperties": false
    },
    "mealLogEntry": {
      "type": "object",
      "required": [
        "slotId",
        "mode",
        "kcal"
      ],
      "properties": {
        "slotId": {
          "type": "string"
        },
        "mode": {
          "enum": [
            "planned",
            "swapped",
            "extra",
            "skipped"
          ]
        },
        "at": {
          "type": "string"
        },
        "items": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "foodId": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "g": {
                "type": "number"
              }
            },
            "additionalProperties": false
          }
        },
        "kcal": {
          "type": "number",
          "minimum": 0
        },
        "p": {
          "type": "number",
          "minimum": 0
        },
        "c": {
          "type": "number",
          "minimum": 0
        },
        "f": {
          "type": "number",
          "minimum": 0
        },
        "uid": {
          "type": "string",
          "description": "stable id for precise undo (Health mirror side-table)"
        }
      },
      "patternProperties": {
        "^x-": {}
      },
      "additionalProperties": false
    }
  }
}