Class: CoachZed::ScheduleParser

Inherits:
Object
  • Object
show all
Defined in:
lib/coach_zed/schedule_parser.rb

Defined Under Namespace

Classes: Error

Class Method Summary collapse

Class Method Details

.extract_json(raw_schedule) ⇒ Object



19
20
21
22
23
24
25
# File 'lib/coach_zed/schedule_parser.rb', line 19

def self.extract_json(raw_schedule)
  text = raw_schedule.to_s.strip
  return text unless text.start_with?("```")

  fenced = text.match(/```(?:json)?\s*(.*?)\s*```/m)
  fenced ? fenced[1].to_s.strip : text
end

.normalize!(schedule) ⇒ Object



27
28
29
30
31
32
33
34
# File 'lib/coach_zed/schedule_parser.rb', line 27

def self.normalize!(schedule)
  return unless schedule.is_a?(Hash)

  days = schedule["days"]
  return unless days.is_a?(Array) && !days.empty?

  schedule["program_length_days"] = days.length
end

.parse(raw_schedule) ⇒ Object



9
10
11
12
13
14
15
16
17
# File 'lib/coach_zed/schedule_parser.rb', line 9

def self.parse(raw_schedule)
  json = extract_json(raw_schedule)
  schedule = JSON.parse(json)
  normalize!(schedule)
  validate!(schedule)
  schedule
rescue JSON::ParserError => e
  raise Error, "invalid schedule JSON: #{e.message}"
end

.validate!(schedule) ⇒ Object



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/coach_zed/schedule_parser.rb', line 36

def self.validate!(schedule)
  raise Error, "schedule must be a JSON object" unless schedule.is_a?(Hash)

  days = schedule.fetch("days")
  raise Error, "schedule must contain days" unless days.is_a?(Array) && !days.empty?

  expected_length = schedule["program_length_days"] || days.length
  raise Error, "program_length_days must match days length" unless expected_length.to_i == days.length

  days.each_with_index do |day, index|
    validate_day!(day, index + 1)
  end
rescue KeyError => e
  raise Error, "missing required schedule field: #{e.key}"
end

.validate_day!(day, expected_day_number) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/coach_zed/schedule_parser.rb', line 52

def self.validate_day!(day, expected_day_number)
  raise Error, "each day must be an object" unless day.is_a?(Hash)
  raise Error, "day_number must be sequential" unless day.fetch("day_number").to_i == expected_day_number
  raise Error, "day_type must be workout or rest" unless %w[workout rest].include?(day.fetch("day_type"))
  raise Error, "notes must be present" unless day.key?("notes")

  if day["day_type"] == "workout"
    workout = day.fetch("workout")
    raise Error, "workout must be present for workout days" unless workout.is_a?(Hash)

    %w[title catalog_path domain session_duration].each do |field|
      raise Error, "workout must include #{field}" unless workout[field].is_a?(String) && !workout[field].empty?
    end
  end
rescue KeyError => e
  raise Error, "missing required day field: #{e.key}"
end