rail_feeds 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +31 -0
  5. data/.travis.yml +26 -0
  6. data/CHANGELOG.md +3 -0
  7. data/Gemfile +6 -0
  8. data/Guardfile +25 -0
  9. data/LICENSE.md +32 -0
  10. data/README.md +77 -0
  11. data/Rakefile +3 -0
  12. data/doc/guides/Logging.md +13 -0
  13. data/doc/guides/Network Rail/CORPUS.md +34 -0
  14. data/doc/guides/Network Rail/SMART.md +39 -0
  15. data/doc/guides/Network Rail/Schedule.md +138 -0
  16. data/file +0 -0
  17. data/lib/rail_feeds/credentials.rb +45 -0
  18. data/lib/rail_feeds/logging.rb +51 -0
  19. data/lib/rail_feeds/network_rail/corpus.rb +77 -0
  20. data/lib/rail_feeds/network_rail/credentials.rb +22 -0
  21. data/lib/rail_feeds/network_rail/http_client.rb +57 -0
  22. data/lib/rail_feeds/network_rail/schedule/association.rb +208 -0
  23. data/lib/rail_feeds/network_rail/schedule/data.rb +215 -0
  24. data/lib/rail_feeds/network_rail/schedule/days.rb +95 -0
  25. data/lib/rail_feeds/network_rail/schedule/fetcher.rb +193 -0
  26. data/lib/rail_feeds/network_rail/schedule/header/cif.rb +102 -0
  27. data/lib/rail_feeds/network_rail/schedule/header/json.rb +79 -0
  28. data/lib/rail_feeds/network_rail/schedule/header.rb +22 -0
  29. data/lib/rail_feeds/network_rail/schedule/parser/cif.rb +141 -0
  30. data/lib/rail_feeds/network_rail/schedule/parser/json.rb +87 -0
  31. data/lib/rail_feeds/network_rail/schedule/parser.rb +108 -0
  32. data/lib/rail_feeds/network_rail/schedule/stp_indicator.rb +72 -0
  33. data/lib/rail_feeds/network_rail/schedule/tiploc.rb +100 -0
  34. data/lib/rail_feeds/network_rail/schedule/train_schedule/change_en_route.rb +158 -0
  35. data/lib/rail_feeds/network_rail/schedule/train_schedule/location/intermediate.rb +119 -0
  36. data/lib/rail_feeds/network_rail/schedule/train_schedule/location/origin.rb +91 -0
  37. data/lib/rail_feeds/network_rail/schedule/train_schedule/location/terminating.rb +72 -0
  38. data/lib/rail_feeds/network_rail/schedule/train_schedule/location.rb +76 -0
  39. data/lib/rail_feeds/network_rail/schedule/train_schedule.rb +392 -0
  40. data/lib/rail_feeds/network_rail/schedule.rb +33 -0
  41. data/lib/rail_feeds/network_rail/smart.rb +186 -0
  42. data/lib/rail_feeds/network_rail/stomp_client.rb +77 -0
  43. data/lib/rail_feeds/network_rail.rb +16 -0
  44. data/lib/rail_feeds/version.rb +14 -0
  45. data/lib/rail_feeds.rb +10 -0
  46. data/rail_feeds.gemspec +32 -0
  47. data/spec/fixtures/network_rail/schedule/data/full.yaml +60 -0
  48. data/spec/fixtures/network_rail/schedule/data/starting.yaml +131 -0
  49. data/spec/fixtures/network_rail/schedule/data/update-gap.yaml +10 -0
  50. data/spec/fixtures/network_rail/schedule/data/update-next.yaml +13 -0
  51. data/spec/fixtures/network_rail/schedule/data/update-old.yaml +10 -0
  52. data/spec/fixtures/network_rail/schedule/data/update.yaml +112 -0
  53. data/spec/fixtures/network_rail/schedule/parser/train_create.json +1 -0
  54. data/spec/fixtures/network_rail/schedule/parser/train_delete.json +1 -0
  55. data/spec/fixtures/network_rail/schedule/train_schedule/json-data.yaml +67 -0
  56. data/spec/rail_feeds/credentials_spec.rb +46 -0
  57. data/spec/rail_feeds/logging_spec.rb +81 -0
  58. data/spec/rail_feeds/network_rail/corpus_spec.rb +92 -0
  59. data/spec/rail_feeds/network_rail/credentials_spec.rb +22 -0
  60. data/spec/rail_feeds/network_rail/http_client_spec.rb +88 -0
  61. data/spec/rail_feeds/network_rail/schedule/association_spec.rb +205 -0
  62. data/spec/rail_feeds/network_rail/schedule/data_spec.rb +219 -0
  63. data/spec/rail_feeds/network_rail/schedule/days_shared.rb +99 -0
  64. data/spec/rail_feeds/network_rail/schedule/days_spec.rb +4 -0
  65. data/spec/rail_feeds/network_rail/schedule/fetcher_spec.rb +228 -0
  66. data/spec/rail_feeds/network_rail/schedule/header/cif_spec.rb +72 -0
  67. data/spec/rail_feeds/network_rail/schedule/header/json_spec.rb +51 -0
  68. data/spec/rail_feeds/network_rail/schedule/header_spec.rb +19 -0
  69. data/spec/rail_feeds/network_rail/schedule/parser/cif_spec.rb +197 -0
  70. data/spec/rail_feeds/network_rail/schedule/parser/json_spec.rb +172 -0
  71. data/spec/rail_feeds/network_rail/schedule/parser_spec.rb +34 -0
  72. data/spec/rail_feeds/network_rail/schedule/stp_indicator_shared.rb +49 -0
  73. data/spec/rail_feeds/network_rail/schedule/stp_indicator_spec.rb +4 -0
  74. data/spec/rail_feeds/network_rail/schedule/tiploc_spec.rb +77 -0
  75. data/spec/rail_feeds/network_rail/schedule/train_schedule/change_en_route_spec.rb +121 -0
  76. data/spec/rail_feeds/network_rail/schedule/train_schedule/location/intermediate_spec.rb +95 -0
  77. data/spec/rail_feeds/network_rail/schedule/train_schedule/location/origin_spec.rb +87 -0
  78. data/spec/rail_feeds/network_rail/schedule/train_schedule/location/terminating_spec.rb +81 -0
  79. data/spec/rail_feeds/network_rail/schedule/train_schedule/location_spec.rb +35 -0
  80. data/spec/rail_feeds/network_rail/schedule/train_schedule_spec.rb +284 -0
  81. data/spec/rail_feeds/network_rail/schedule_spec.rb +41 -0
  82. data/spec/rail_feeds/network_rail/smart_spec.rb +194 -0
  83. data/spec/rail_feeds/network_rail/stomp_client_spec.rb +151 -0
  84. data/spec/rail_feeds/network_rail_spec.rb +7 -0
  85. data/spec/rail_feeds_spec.rb +11 -0
  86. data/spec/spec_helper.rb +47 -0
  87. metadata +282 -0
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailFeeds
4
+ module NetworkRail
5
+ module Schedule
6
+ class TrainSchedule
7
+ class Location
8
+ # A class for holding info about a particular train's terminating location
9
+ class Terminating < Location
10
+ # @!attribute [rw] path
11
+ # @return [String]
12
+ # @!attribute [rw] scheduled_arrival
13
+ # @return [String] The scheduled time for arriving at the location.
14
+ # @!attribute [rw] public_arrival
15
+ # @return [String] The public arrival time (HHMM).
16
+
17
+ attr_accessor :scheduled_arrival, :public_arrival, :path
18
+
19
+ def initialize(**attributes)
20
+ attributes.each do |attribute, value|
21
+ send "#{attribute}=", value
22
+ end
23
+ end
24
+
25
+ # rubocop:disable Metrics/AbcSize
26
+ # Initialize a new terminating from a CIF file line
27
+ def self.from_cif(line)
28
+ fail ArgumentError, "Invalid line:\n#{line}" unless line[0..1].eql?('LT')
29
+
30
+ new(
31
+ tiploc: line[2..8].strip,
32
+ tiploc_suffix: line[9].to_i,
33
+ scheduled_arrival: line[10..14].strip,
34
+ public_arrival: line[15..18].strip,
35
+ platform: line[19..21].strip,
36
+ path: line[22..24].strip,
37
+ activity: line[25..36].strip
38
+ )
39
+ end
40
+ # rubocop:enable Metrics/AbcSize
41
+
42
+ def to_cif
43
+ format('%-80.80s', [
44
+ 'LT',
45
+ format('%-7.7s', tiploc),
46
+ format('%-1.1s', tiploc_suffix),
47
+ format('%-5.5s', scheduled_arrival),
48
+ format('%-4.4s', public_arrival),
49
+ format('%-3.3s', platform),
50
+ format('%-3.3s', path),
51
+ format('%-12.12s', activity)
52
+ ].join) + "\n"
53
+ end
54
+
55
+ def to_hash_for_json
56
+ {
57
+ location_type: 'LT',
58
+ record_identity: 'LT',
59
+ tiploc_code: tiploc,
60
+ tiploc_instance: tiploc_suffix,
61
+ arrival: scheduled_arrival,
62
+ public_arrival: public_arrival,
63
+ platform: platform,
64
+ path: path
65
+ }
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'location/intermediate'
4
+ require_relative 'location/origin'
5
+ require_relative 'location/terminating'
6
+
7
+ module RailFeeds
8
+ module NetworkRail
9
+ module Schedule
10
+ class TrainSchedule
11
+ # A class for holding information about a particular train's particular location.
12
+ class Location
13
+ # @!attribute [rw] tiploc
14
+ # @return [String] The location where the change occurs.
15
+ # @!attribute [rw] tiploc_suffix
16
+ # @return [String]
17
+ # @!attribute [rw] platform
18
+ # @return [String]
19
+ # @!attribute [rw] activity
20
+ # @return [String]
21
+
22
+ attr_accessor :tiploc, :tiploc_suffix, :platform, :activity
23
+
24
+ def initialize
25
+ fail 'This class should never be instantiated'
26
+ end
27
+
28
+ # Initialize a new location from a CIF file line
29
+ # (will be of the appropriate sub class)
30
+ def self.from_cif(line)
31
+ case line[0..1]
32
+ when 'LO'
33
+ Origin.from_cif line
34
+ when 'LI'
35
+ Intermediate.from_cif line
36
+ when 'LT'
37
+ Terminating.from_cif line
38
+ else
39
+ fail ArgumentError, "Improper line type #{line[0..1]}: #{line}"
40
+ end
41
+ end
42
+
43
+ def ==(other)
44
+ hash == other&.hash
45
+ end
46
+
47
+ def hash
48
+ "#{tiploc}-#{tiploc_suffix}"
49
+ end
50
+
51
+ private
52
+
53
+ def self.parse_allowance(value)
54
+ half = value[-1].eql?('H')
55
+ value = value.to_f
56
+ half ? value + 0.5 : value
57
+ end
58
+ private_class_method :parse_allowance
59
+
60
+ def allowance_cif(value)
61
+ i = value.to_i
62
+ f = value.to_f - i
63
+ f.eql?(0.5) ? "#{i}H" : i.to_s
64
+ end
65
+
66
+ def allowance_json(value)
67
+ return nil if value.nil?
68
+ i = value.to_i
69
+ f = value.to_f - i
70
+ f.eql?(0.5) ? "#{i}H" : i.to_s
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,392 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+ require 'time'
5
+
6
+ require_relative 'train_schedule/change_en_route'
7
+ require_relative 'train_schedule/location'
8
+
9
+ module RailFeeds
10
+ module NetworkRail
11
+ module Schedule
12
+ # rubocop:disable Metrics/ClassLength
13
+ # A class for holding information about a particular train
14
+ class TrainSchedule
15
+ include Comparable
16
+ include Schedule::Days
17
+ include Schedule::STPIndicator
18
+
19
+ # @!attribute [rw] uid
20
+ # @return [String] The unique train identifier (letter then 5 numbers).
21
+ # Along with start_date uniquely identifies a schedule.
22
+ # @!attribute [rw] category
23
+ # @return [String]
24
+ # @!attribute [rw] status
25
+ # @return [String]
26
+ # @!attribute [rw] stp_indicator
27
+ # @return [String]
28
+ # * C - cancellation of permanent schedule
29
+ # * N - new STP schedule
30
+ # * O - STP overlay of permanent schedule
31
+ # * P - permanent
32
+ # @!attribute [rw] reservation_headcode
33
+ # @return [Integer, nil] The train's headcode in the NRS
34
+ # (National Reservation System).
35
+ # @!attribute [rw] signalling_headcode
36
+ # @return [String, nil] The headcode used in signalling the service.
37
+ # Will be nil for annonymous freight services.
38
+ # @!attribute [rw] service_code
39
+ # @return [Integer] Used for attribution of revenue.
40
+ # @!attribute [rw] start_date
41
+ # @return [Date] When the schedule starts.
42
+ # @!attribute [rw] end_date
43
+ # @return [Date] When the schedule ends.
44
+ # @!attribute [rw] days
45
+ # @return [Array<Boolean>] The days on which the service runs.
46
+ # [Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday]
47
+ # @!attribute [rw] bank_holiday_running
48
+ # @return [String, nil] Whether the service runs on bank holidays.
49
+ # * X - does not run on specified bank holiday Mondays.
50
+ # * G - does not run on Glasgow bank holidays.
51
+ # @!attribute [rw] power_type
52
+ # @return [String]
53
+ # @!attribute [rw] timing_load
54
+ # @return [String, nil]
55
+ # @!attribute [rw] speed
56
+ # @return [Integer] The planned speed (miles per hour).
57
+ # @!attribute [rw] operating_characteristics
58
+ # @return [String, nil]
59
+ # @!attribute [rw] seating_class
60
+ # @return [String, nil] The seating classes available.
61
+ # * B or blank - First and Standard
62
+ # * S - Standard only
63
+ # @!attribute [rw] sleeping_class
64
+ # @return [String, nil] The sleeping classes available.
65
+ # * B - First and Standard
66
+ # * F - First only
67
+ # * S - Standard only
68
+ # @!attribute [rw] reservations
69
+ # @return [String, nil] The reservation recommendations.
70
+ # * A - Reservations Compulsory
71
+ # * E - Reservations for bicycles essential
72
+ # * R - Reservations recommended
73
+ # * S - Reservations possible from any station
74
+ # * W - Wheelchair only reservations
75
+ # @!attribute [rw] catering
76
+ # @return [String, nil] The catering available, any 2 of:
77
+ # * H - Hot food available
78
+ # * C - Buffet service
79
+ # * R - Restaurant
80
+ # * F - Restaurant for first class passengers
81
+ # * M - Meal included for first class passengers
82
+ # * T - Trolly service
83
+ # @!attribute [rw] branding
84
+ # @return [String, nil] The service brand.
85
+ # * E - Eurostar
86
+ # @!attribute [rw] uic
87
+ # @return [Integer, nil] For train services running to/from continental Europe.
88
+ # @!attribute [rw] atoc
89
+ # @!attribute [rw] applicable_timetable
90
+ # @return [Boolean, nil] Whether the service is subject to
91
+ # performance monitoring (Applicable Timetable Service).
92
+ # @!attribute [rw] journey
93
+ # @return [Array<Location, ChangeEnRoute>] The combined locations and changes.
94
+ # change will precede location.
95
+
96
+ attr_accessor :uid, :category, :status,
97
+ :reservation_headcode, :signalling_headcode, :service_code,
98
+ :start_date, :end_date, :bank_holiday_running,
99
+ :power_type, :timing_load, :speed, :operating_characteristics,
100
+ :seating_class, :sleeping_class, :reservations, :catering,
101
+ :branding, :uic, :atoc, :applicable_timetable, :journey
102
+ # Attributes from modules :days, :stp_indicator
103
+
104
+ # Initialize a new train
105
+ def initialize(**attributes)
106
+ @journey = []
107
+ attributes.each do |attribute, value|
108
+ send "#{attribute}=", value
109
+ end
110
+ end
111
+
112
+ # Add details from a CIF schedule file line to this train schedule
113
+ def update_from_cif(line)
114
+ type = line[0..1]
115
+
116
+ if type.eql?('BS')
117
+ update_basic_information line
118
+ elsif type.eql?('BX')
119
+ update_extra_information line
120
+
121
+ elsif %w[LO LI LT].include?(type)
122
+ journey.push Location.from_cif(line)
123
+
124
+ elsif type.eql?('CR')
125
+ journey.push ChangeEnRoute.from_cif(line)
126
+
127
+ else
128
+ fail ArgumentError, "Improper line type #{line[0..1]}: #{line}"
129
+ end
130
+ end
131
+
132
+ # rubocop:disable Metrics/AbcSize
133
+ # rubocop:disable Metrics/LineLength
134
+ # rubocop:disable Metrics/MethodLength
135
+ # Create a new train schedule from json data.
136
+ def self.from_json(json)
137
+ data = ::JSON.parse(json)['JsonScheduleV1']
138
+
139
+ locations = data.dig('schedule_segment', 'schedule_location')&.map do |location|
140
+ location_from_json location
141
+ end
142
+
143
+ new(
144
+ uid: data['CIF_train_uid'],
145
+ category: Schedule.nil_or_strip(data.dig('schedule_segment', 'CIF_train_category')),
146
+ status: Schedule.nil_or_strip(data['train_status']),
147
+ reservation_headcode: Schedule.nil_or_i(data.dig('schedule_segment', 'CIF_headcode')),
148
+ signalling_headcode: Schedule.nil_or_strip(data.dig('schedule_segment', 'signalling_id')),
149
+ service_code: Schedule.nil_or_strip(data.dig('schedule_segment', 'CIF_train_service_code')),
150
+ start_date: Date.parse(data['schedule_start_date']),
151
+ end_date: data['schedule_end_date'] ? Date.parse(data['schedule_end_date']) : nil,
152
+ days: days_from_cif(data['schedule_days_runs']),
153
+ bank_holiday_running: Schedule.nil_or_strip(data['CIF_bank_holiday_running']),
154
+ power_type: Schedule.nil_or_strip(data.dig('schedule_segment', 'CIF_power_type')),
155
+ timing_load: Schedule.nil_or_strip(data.dig('schedule_segment', 'CIF_timing_load')),
156
+ speed: Schedule.nil_or_i(data.dig('schedule_segment', 'CIF_speed')),
157
+ operating_characteristics: Schedule.nil_or_strip(data.dig('schedule_segment', 'CIF_operating_characteristics')),
158
+ seating_class: Schedule.nil_or_strip(data.dig('schedule_segment', 'CIF_train_class')),
159
+ sleeping_class: Schedule.nil_or_strip(data.dig('schedule_segment', 'CIF_sleepers')),
160
+ reservations: Schedule.nil_or_strip(data.dig('schedule_segment', 'CIF_reservations')),
161
+ catering: Schedule.nil_or_strip(data.dig('schedule_segment', 'CIF_catering_code')),
162
+ branding: Schedule.nil_or_strip(data.dig('schedule_segment', 'CIF_service_branding')),
163
+ uic: Schedule.nil_or_i(data.dig('new_schedule_segment', 'uic_code')),
164
+ atoc: data['atoc_code'],
165
+ applicable_timetable: data['applicable_timetable'].eql?('Y'),
166
+ stp_indicator: stp_indicator_from_cif(data['CIF_stp_indicator']),
167
+ journey: locations
168
+ )
169
+ end
170
+ # rubocop:enable Metrics/AbcSize
171
+ # rubocop:enable Metrics/LineLength
172
+ # rubocop:enable Metrics/MethodLength
173
+
174
+ def to_cif
175
+ [
176
+ basic_to_cif,
177
+ extra_to_cif,
178
+ *journey.map(&:to_cif)
179
+ ].join
180
+ end
181
+
182
+ # rubocop:disable Metrics/AbcSize
183
+ # rubocop:disable Metrics/MethodLength
184
+ def to_json
185
+ {
186
+ 'JsonScheduleV1' => {
187
+ 'CIF_bank_holiday_running' => bank_holiday_running,
188
+ 'CIF_stp_indicator' => stp_indicator_to_cif,
189
+ 'CIF_train_uid' => uid,
190
+ 'applicable_timetable' => (applicable_timetable ? 'Y' : 'N'),
191
+ 'atoc_code' => atoc,
192
+ 'schedule_start_date' => start_date.strftime('%Y-%m-%d'),
193
+ 'train_status' => status,
194
+ 'transaction_type' => 'Create',
195
+ 'schedule_days_runs' => days_to_cif,
196
+ 'schedule_end_date' => end_date.strftime('%Y-%m-%d'),
197
+ 'new_schedule_segment' => {
198
+ 'traction_class' => '',
199
+ 'uic_code' => uic.to_s
200
+ },
201
+ 'schedule_segment' => {
202
+ 'signalling_id' => signalling_headcode,
203
+ 'CIF_train_category' => category,
204
+ 'CIF_headcode' => reservation_headcode.to_s,
205
+ 'CIF_course_indicator' => nil,
206
+ 'CIF_train_service_code' => service_code,
207
+ 'CIF_business_sector' => '??',
208
+ 'CIF_power_type' => power_type,
209
+ 'CIF_timing_load' => timing_load,
210
+ 'CIF_speed' => speed.to_s.rjust(3, '0'),
211
+ 'CIF_operating_characteristics' => operating_characteristics,
212
+ 'CIF_train_class' => seating_class,
213
+ 'CIF_sleepers' => sleeping_class,
214
+ 'CIF_reservations' => reservations,
215
+ 'CIF_connection_indicator' => nil,
216
+ 'CIF_catering_code' => catering,
217
+ 'CIF_service_branding' => branding,
218
+ 'schedule_location' => journey.map(&:to_hash_for_json).reject(&:nil?)
219
+ }
220
+ }
221
+ }.to_json
222
+ end
223
+ # rubocop:enable Metrics/AbcSize
224
+ # rubocop:enable Metrics/MethodLength
225
+
226
+ def hash
227
+ "#{uid}-#{start_date&.strftime('%Y%m%d')}"
228
+ end
229
+
230
+ def ==(other)
231
+ hash == other&.hash
232
+ end
233
+
234
+ def <=>(other)
235
+ values = [start_date, uid]
236
+ other_values = [other&.start_date, other&.uid]
237
+ values <=> other_values
238
+ end
239
+
240
+ private
241
+
242
+ # rubocop:disable Metrics/AbcSize
243
+ # rubocop:disable Metrics/MethodLength
244
+ def update_basic_information(line)
245
+ self.uid = line[3..8].strip
246
+ self.start_date = Schedule.make_date line[9..14]
247
+ self.end_date = Schedule.make_date line[15..20], allow_nil: line[2].eql?('D')
248
+ self.days = days_from_cif line[21..27]
249
+ self.bank_holiday_running = Schedule.nil_or_strip line[28]
250
+ self.status = Schedule.nil_or_strip(line[29])
251
+ self.category = Schedule.nil_or_strip(line[30..31])
252
+ self.signalling_headcode = Schedule.nil_or_strip line[32..35]
253
+ self.reservation_headcode = Schedule.nil_or_i line[36..39]
254
+ self.service_code = Schedule.nil_or_i line[41..48]
255
+ self.power_type = Schedule.nil_or_strip(line[50..52])
256
+ self.timing_load = Schedule.nil_or_strip line[53..56].strip
257
+ self.speed = Schedule.nil_or_i(line[57..59])
258
+ self.operating_characteristics = Schedule.nil_or_strip line[60..65].strip
259
+ self.seating_class = Schedule.nil_or_strip line[66]
260
+ self.sleeping_class = Schedule.nil_or_strip line[67]
261
+ self.reservations = Schedule.nil_or_strip line[68]
262
+ self.catering = Schedule.nil_or_strip(line[70..73])
263
+ self.branding = Schedule.nil_or_strip line[74..77].strip
264
+ self.stp_indicator = stp_indicator_from_cif line[79]
265
+ end
266
+ # rubocop:enable Metrics/AbcSize
267
+ # rubocop:enable Metrics/MethodLength
268
+
269
+ def update_extra_information(line)
270
+ self.uic = Schedule.nil_or_i line[6..10]
271
+ self.atoc = line[11..12]
272
+ self.applicable_timetable = line[13].eql?('Y')
273
+ end
274
+
275
+ # rubocop:disable Metrics/AbcSize
276
+ # rubocop:disable Metrics/MethodLength
277
+ def basic_to_cif
278
+ format('%-80.80s', [
279
+ 'BSN',
280
+ format('%-6.6s', uid),
281
+ # rubocop:disable Style/FormatStringToken
282
+ format('%-6.6s', start_date&.strftime('%y%m%d')),
283
+ format('%-6.6s', end_date&.strftime('%y%m%d')),
284
+ # rubocop:enable Style/FormatStringToken
285
+ days_to_cif,
286
+ format('%-1.1s', bank_holiday_running),
287
+ format('%-1.1s', status),
288
+ format('%-2.2s', category),
289
+ format('%-4.4s', signalling_headcode),
290
+ format('%-4.4s', reservation_headcode),
291
+ '1',
292
+ format('%-8.8s', service_code),
293
+ ' ',
294
+ format('%-3.3s', power_type),
295
+ format('%-4.4s', timing_load),
296
+ format('%-3.3s', speed),
297
+ format('%-6.6s', operating_characteristics),
298
+ format('%-1.1s', seating_class),
299
+ format('%-1.1s', sleeping_class),
300
+ format('%-1.1s', reservations),
301
+ ' ',
302
+ format('%-4.4s', catering),
303
+ format('%-4.4s', branding),
304
+ ' ',
305
+ stp_indicator_to_cif
306
+ ].join) + "\n"
307
+ end
308
+ # rubocop:enable Metrics/AbcSize
309
+ # rubocop:enable Metrics/MethodLength
310
+
311
+ def extra_to_cif
312
+ format('%-80.80s', [
313
+ 'BX ',
314
+ format('%-5.5s', uic),
315
+ format('%-2.2s', atoc),
316
+ format('%-1.1s', (applicable_timetable ? 'Y' : 'N'))
317
+ ].join) + "\n"
318
+ end
319
+
320
+ def self.location_from_json(hash)
321
+ return lo_from_json(hash) if hash['record_identity'].eql?('LO')
322
+ return li_from_json(hash) if hash['record_identity'].eql?('LI')
323
+ return lt_from_json(hash) if hash['record_identity'].eql?('LT')
324
+ nil
325
+ end
326
+ private_class_method :location_from_json
327
+
328
+ def self.lo_from_json(hash)
329
+ Location::Origin.new(
330
+ tiploc: hash['tiploc_code'],
331
+ tiploc_suffix: hash['tiploc_instance'],
332
+ platform: Schedule.nil_or_strip(hash['platform']),
333
+ activity: :unknown,
334
+ scheduled_departure: hash['departure'],
335
+ public_departure: hash['public_departure'],
336
+ line: Schedule.nil_or_strip(hash['line']),
337
+ engineering_allowance: parse_allowance(hash['engineering_allowance']),
338
+ pathing_allowance: parse_allowance(hash['pathing_allowance']),
339
+ performance_allowance: parse_allowance(hash['performance_allowance'])
340
+ )
341
+ end
342
+ private_class_method :lo_from_json
343
+
344
+ # rubocop:disable Metrics/AbcSize
345
+ # rubocop:disable Metrics/MethodLength
346
+ def self.li_from_json(hash)
347
+ Location::Intermediate.new(
348
+ tiploc: hash['tiploc_code'],
349
+ tiploc_suffix: hash['tiploc_instance'],
350
+ platform: Schedule.nil_or_strip(hash['platform']),
351
+ activity: :unknown,
352
+ scheduled_arrival: hash['arrival'],
353
+ public_arrival: hash['public_arrival'],
354
+ scheduled_departure: hash['departure'],
355
+ public_departure: hash['public_departure'],
356
+ scheduled_pass: hash['pass'],
357
+ line: Schedule.nil_or_strip(hash['line']),
358
+ path: Schedule.nil_or_strip(hash['path']),
359
+ engineering_allowance: parse_allowance(hash['engineering_allowance']),
360
+ pathing_allowance: parse_allowance(hash['pathing_allowance']),
361
+ performance_allowance: parse_allowance(hash['performance_allowance'])
362
+ )
363
+ end
364
+ private_class_method :li_from_json
365
+ # rubocop:enable Metrics/MethodLength
366
+ # rubocop:enable Metrics/AbcSize
367
+
368
+ def self.lt_from_json(hash)
369
+ Location::Terminating.new(
370
+ tiploc: hash['tiploc_code'],
371
+ tiploc_suffix: hash['tiploc_instance'],
372
+ platform: Schedule.nil_or_strip(hash['platform']),
373
+ activity: :unknown,
374
+ scheduled_arrival: hash['arrival'],
375
+ public_arrival: hash['public_arrival'],
376
+ path: Schedule.nil_or_strip(hash['path'])
377
+ )
378
+ end
379
+ private_class_method :lt_from_json
380
+
381
+ def self.parse_allowance(value)
382
+ return nil if value.nil? || value.empty?
383
+ half = value[-1].eql?('H')
384
+ value = value.to_f
385
+ half ? value + 0.5 : value
386
+ end
387
+ private_class_method :parse_allowance
388
+ end
389
+ # rubocop:enable Metrics/ClassLength
390
+ end
391
+ end
392
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'schedule/days'
4
+ require_relative 'schedule/stp_indicator'
5
+ require_relative 'schedule/association'
6
+ require_relative 'schedule/header'
7
+ require_relative 'schedule/tiploc'
8
+ require_relative 'schedule/train_schedule'
9
+ require_relative 'schedule/fetcher'
10
+ require_relative 'schedule/parser'
11
+ require_relative 'schedule/data'
12
+
13
+ module RailFeeds
14
+ module NetworkRail
15
+ module Schedule # :nodoc:
16
+ def self.nil_or_i(value)
17
+ return nil if value.to_s.strip.empty?
18
+ value.to_i
19
+ end
20
+
21
+ def self.nil_or_strip(value)
22
+ return nil if value.to_s.strip.empty?
23
+ value.strip
24
+ end
25
+
26
+ def self.make_date(value, allow_nil: false)
27
+ return nil if allow_nil && value.strip.empty?
28
+ return Date.new(9999, 12, 31) if value.eql?('999999')
29
+ Date.strptime(value, '%y%m%d')
30
+ end
31
+ end
32
+ end
33
+ end