rail_feeds 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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