rate-card 0.1.1 → 0.1.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 320d6905617967d73305befafac57482eed231ff28b39d3282d61a8ae31549ff
4
- data.tar.gz: a54e83be25c1f657dd5fe6eb4a416a02a8fb1f88e38439523f43d2a1c9fd9878
3
+ metadata.gz: 160b23b771a6920c394ff1d8e33b625499f0bd384c8aab8453471c18d1244a23
4
+ data.tar.gz: c03c94b913253b5037be1f5f4d2b9b11304943712215b81bcbae092148675b84
5
5
  SHA512:
6
- metadata.gz: d4c28f5cc71b01a00e5e7cd41e785acda43c117d26c456ea313239819fb95b9779efdc0a7b20c20f96a5013049e108c715386bdc28337e7d7bfbc14f0c917c07
7
- data.tar.gz: c02ecec88df73aa9fa01d33cd6107d8b1e4911e367bc6d54bb996a92834ee4cfce6add454a5b5209b283509322d276e44548f263af07eaad74fe8e40d4c10f20
6
+ metadata.gz: d11060dc015d5eaa28ab8ec883bc38b865383f22e85cccce7ea455945b304fabefa0f22c9463e0ed8bdf986fa3b3cc2d43975154d52296ed29999b518343e34a
7
+ data.tar.gz: 6f1649f1183ab8b1b7627b43494d9277999e22939e4492852be6827ff9694edd768bbd4ca7202bcf714b684ecc7deed9ace3d50661202a26070fdfcb0caf02a6
@@ -72,6 +72,40 @@ module RateCard
72
72
 
73
73
  BY_CARRIER = { 'USPS' => USPS, 'UPS' => UPS, 'FedEx' => FEDEX, 'DHL' => DHL_ECOMMERCE, 'OSM' => OSM }.freeze
74
74
 
75
+ # Rural/remote-area surcharge test addresses. These aren't zone charts
76
+ # (rural surcharge eligibility is a property of the destination zip on
77
+ # a carrier's rural list, not something for_carrier's zone lookup
78
+ # models), so they live outside BY_CARRIER and are looked up directly
79
+ # by callers that need them.
80
+ #
81
+ # Ported from ../rate_table_builder/constants.rb. USPS_RURAL_DAS zones
82
+ # 4-8 were tagged 'maybe' or unverified in the source file - re-verify
83
+ # against USPS's rural lookup before relying on them for a real card.
84
+ USPS_RURAL_DAS = {
85
+ 0 => { address1: '12078 W 4000 N', city: 'Bluebell', state: 'UT', postal_code: '84007', country: 'US' },
86
+ 1 => { address1: '6074 N 4700 W', city: 'Bear River City', state: 'UT', postal_code: '84301', country: 'US' },
87
+ 2 => { address1: '25670 Buffalo Run', city: 'Moran', state: 'WY', postal_code: '83013', country: 'US' },
88
+ 3 => { address1: '17 Texs Loop', city: 'Alder', state: 'MT', postal_code: '59710', country: 'US' },
89
+ 4 => { address1: '1250 153rd St SE', city: 'Norwich', state: 'ND', postal_code: '58768', country: 'US' },
90
+ 5 => { address1: '103 Stonegate Ct', city: 'Gurdon', state: 'AR', postal_code: '71743', country: 'US' },
91
+ 6 => { address1: '31 Edsel Rd', city: 'Webbville', state: 'KY', postal_code: '41180', country: 'US' },
92
+ 7 => { address1: '295 Old Waterford Road', city: 'Littleton', state: 'NH', postal_code: '03561', country: 'US' },
93
+ 8 => { address1: '77 Main Street', city: 'Mars Hill', state: 'ME', postal_code: '04758', country: 'US' }
94
+ }.freeze
95
+
96
+ # UPS has no per-zone rural chart - just one known address per DAS
97
+ # variant. EDAS = Extended Delivery Area Surcharge, RDAS = Remote
98
+ # Delivery Area Surcharge.
99
+ UPS_EDAS_EXCEPTION = { address1: '149 Pheasant Runn Road', city: 'Goshen', state: 'NH', postal_code: '03752',
100
+ country: 'US' }.freeze
101
+
102
+ UPS_RDAS_EXCEPTION = { address1: '106 Yellow Hammer Rd', city: 'Tyner', state: 'NC', postal_code: '27980',
103
+ country: 'US' }.freeze
104
+
105
+ # Looked up by surcharge-type symbol rather than zone, since UPS rural
106
+ # mode sweeps surcharge types instead of zones — see RunSpec#address_for.
107
+ UPS_RURAL = { edas: UPS_EDAS_EXCEPTION, rdas: UPS_RDAS_EXCEPTION }.freeze
108
+
75
109
  module_function
76
110
 
77
111
  # Raises rather than falling back. The old USPS default meant asking for
@@ -30,6 +30,11 @@ module RateCard
30
30
  'dhl' => 'DHL Express', 'dhl_ecommerce' => 'DHL', 'osm' => 'OSM', 'amazon' => 'Amazon'
31
31
  }.freeze
32
32
 
33
+ # Carriers with a rural/DAS surcharge test address in Constants::Addresses.
34
+ # Independent of zone-chart support - a carrier can have a verified zone
35
+ # chart and no rural chart at all.
36
+ RURAL_AWARE = %w[USPS UPS].freeze
37
+
33
38
  module_function
34
39
 
35
40
  # OTHER is only for a malformed entry with no code at all, so a service is
@@ -44,6 +49,10 @@ module RateCard
44
49
  def display_order
45
50
  DISPLAY_ORDER
46
51
  end
52
+
53
+ def rural_aware?(carrier)
54
+ RURAL_AWARE.include?(carrier.to_s)
55
+ end
47
56
  end
48
57
  end
49
58
  end
@@ -32,7 +32,7 @@ module RateCard
32
32
 
33
33
  def read(ui:, io: $stdin)
34
34
  loop do
35
- io.print(BRACKETED_PASTE_OFF) if io.tty?
35
+ $stdout.print(BRACKETED_PASTE_OFF) if io.tty?
36
36
  ui.prompt('eHub API token: ')
37
37
  raw = read_masked(io)
38
38
  return nil if raw.nil?
@@ -16,7 +16,7 @@ module RateCard
16
16
  class RunSpec < Struct.new(
17
17
  :token, :customer_name, :customer_id, :carrier, :services, :zones,
18
18
  :weight_unit, :weights, :package_type, :rate_keys, :output_base,
19
- :show_table, :started_at, :rate_mode, :cubic_tiers,
19
+ :show_table, :started_at, :rate_mode, :cubic_tiers, :rural,
20
20
  keyword_init: true
21
21
  )
22
22
  # Our rate-key name => the field it arrives as in service_rates. Getting
@@ -44,10 +44,26 @@ module RateCard
44
44
  self.class.compact_range(zones)
45
45
  end
46
46
 
47
+ def zone_label(zone)
48
+ zone.is_a?(Symbol) ? zone.to_s.upcase : "Z#{zone}"
49
+ end
50
+
51
+ # true for USPS with rural mode on, or any UPS surcharge type/combination.
52
+ # nil/false (rural mode off, or a non-rural-aware carrier) is falsy here.
53
+ def rural?
54
+ !!rural
55
+ end
56
+
47
57
  # [1,2,3,5] => "1-3,5". Runs of three or more collapse; a pair stays listed,
48
58
  # since "4,5" is no longer than "4-5" and reads as what the user typed.
59
+ #
60
+ # UPS rural mode sweeps surcharge-type symbols (:edas, :rdas) instead of
61
+ # numbered zones - those aren't a range at all, so they're just listed.
49
62
  def self.compact_range(numbers)
50
- sorted = numbers.to_a.sort.uniq
63
+ array = numbers.to_a
64
+ return array.join(',') if array.any? { |n| n.is_a?(Symbol) }
65
+
66
+ sorted = array.sort.uniq
51
67
  runs = sorted.slice_when { |a, b| b != a + 1 }.to_a
52
68
  runs.map { |run| run.length >= 3 ? "#{run.first}-#{run.last}" : run.join(',') }.join(',')
53
69
  end
@@ -104,7 +120,14 @@ module RateCard
104
120
  end
105
121
  end
106
122
 
123
+ # A Symbol zone (:edas, :rdas) only ever comes from UPS rural mode, so it
124
+ # disambiguates from a real zone number without needing to check carrier
125
+ # or rural separately. USPS rural mode keeps integer zones, just against
126
+ # the rural chart instead of the normal one.
107
127
  def address_for(zone)
128
+ return Constants::Addresses::UPS_RURAL[zone] if zone.is_a?(Symbol)
129
+ return Constants::Addresses::USPS_RURAL_DAS[zone] if carrier == 'USPS' && rural?
130
+
108
131
  Constants::Addresses.for_carrier(carrier)[zone]
109
132
  end
110
133
 
@@ -136,7 +159,16 @@ module RateCard
136
159
  end
137
160
 
138
161
  def run_dir
139
- Pathname.new(output_base).join("#{slug}_#{customer_id}_#{timestamp}")
162
+ Pathname.new(output_base).join("#{slug}_#{customer_id}_#{timestamp}#{rural_suffix}")
163
+ end
164
+
165
+ def rural_suffix
166
+ case rural
167
+ when true then '_rural'
168
+ when :edas, :rdas then "_#{rural}"
169
+ when :both then '_edas_rdas'
170
+ else ''
171
+ end
140
172
  end
141
173
 
142
174
  def validate!
@@ -47,7 +47,7 @@ module RateCard
47
47
  end
48
48
 
49
49
  def render(service, rate_key)
50
- header = [spec.weight_label, *spec.zones.map { |zone| "Z#{zone}" }]
50
+ header = [spec.weight_label, *spec.zones.map { |zone| spec.zone_label(zone) }]
51
51
  body = rows(service, rate_key)
52
52
 
53
53
  Lipgloss::Table.new
@@ -24,7 +24,7 @@ module RateCard
24
24
 
25
25
  # No :token stage — see TokenPrompt for why it cannot live in the loop.
26
26
  STAGES = %i[
27
- loading rate_mode carrier services zones unit weights package_type
27
+ loading rate_mode carrier rural services zones unit weights package_type
28
28
  rate_keys confirm fetching
29
29
  ].freeze
30
30
 
@@ -177,6 +177,7 @@ module RateCard
177
177
  case stage
178
178
  when :rate_mode then rate_mode_field
179
179
  when :carrier then carrier_field
180
+ when :rural then rural_field
180
181
  when :services then services_field
181
182
  when :zones then zones_field
182
183
  when :unit then unit_field
@@ -238,8 +239,43 @@ module RateCard
238
239
  )
239
240
  end
240
241
 
242
+ # Only USPS and UPS have a rural/DAS chart (Addresses::USPS_RURAL_DAS,
243
+ # Addresses::UPS_RURAL); every other carrier is decided :normal (nil)
244
+ # and skips this stage, same as any other single-answer stage.
245
+ def rural_field
246
+ return nil unless Constants::Carriers.rural_aware?(carrier)
247
+
248
+ choices = carrier == 'USPS' ? usps_rural_choices : ups_rural_choices
249
+ Fields::Select.new(label: 'Rural / DAS test mode', choices: choices,
250
+ selected: choices.index { |_, v| v == @answers[:rural] } || 0)
251
+ end
252
+
253
+ def usps_rural_choices
254
+ [['Normal', false], ['Rural (DAS)', true]]
255
+ end
256
+
257
+ def ups_rural_choices
258
+ [['Normal', nil], ['EDAS', :edas], ['RDAS', :rdas], ['Both', :both]]
259
+ end
260
+
261
+ # UPS rural mode has no real zone choice - each surcharge type is a
262
+ # single fixed address - so it's decided here as the chosen surcharge
263
+ # symbol(s) and the zones stage is skipped rather than asked.
264
+ def ups_rural_zones
265
+ case @answers[:rural]
266
+ when :edas then [:edas]
267
+ when :rdas then [:rdas]
268
+ when :both then %i[edas rdas]
269
+ end
270
+ end
271
+
241
272
  def zones_field
242
- available = Constants::Addresses.available_zones(carrier)
273
+ if carrier == 'UPS' && (decided = ups_rural_zones)
274
+ @answers[:zones] = decided
275
+ return nil
276
+ end
277
+
278
+ available = usps_rural? ? Constants::Addresses::USPS_RURAL_DAS.keys.sort : Constants::Addresses.available_zones(carrier)
243
279
  full = "#{available.first}-#{available.last}"
244
280
  answered = @answers[:zones]
245
281
 
@@ -255,6 +291,10 @@ module RateCard
255
291
  )
256
292
  end
257
293
 
294
+ def usps_rural?
295
+ carrier == 'USPS' && @answers[:rural] == true
296
+ end
297
+
258
298
  # Weight is fixed per cubic tier, so asking for a display unit is
259
299
  # meaningless in cubic mode — decided as :oz (unused) and skipped.
260
300
  def unit_field
@@ -431,6 +471,7 @@ module RateCard
431
471
  weights: cubic ? [] : @answers[:weights],
432
472
  cubic_tiers: cubic ? @answers[:weights] : [],
433
473
  rate_mode: @answers[:rate_mode] || :weight,
474
+ rural: @answers[:rural],
434
475
  package_type: @answers[:package_type],
435
476
  rate_keys: @answers[:rate_keys],
436
477
  output_base: @output_base,
@@ -458,14 +499,21 @@ module RateCard
458
499
  # in the transcript above, but they arrived one at a time over eight
459
500
  # screens; this is the only place they can be read against each other.
460
501
  def recap_view
502
+ carrier_line = " #{Theme.bold(carrier)}#{rural_recap} · #{@answers[:services].map(&:name).join(', ')}"
461
503
  [
462
- " #{Theme.bold(carrier)} · #{@answers[:services].map(&:name).join(', ')}",
504
+ carrier_line,
463
505
  " zones #{RunSpec.compact_range(@answers[:zones])} · #{rows_recap} · #{@answers[:package_type]}",
464
506
  " columns: #{@answers[:rate_keys].map { |k| RunSpec::RATE_KEY_LABELS.fetch(k) }.join(', ')}",
465
507
  " #{Theme.bold(call_count.to_s)} rate calls against #{Theme.danger('production')}"
466
508
  ].join("\n")
467
509
  end
468
510
 
511
+ def rural_recap
512
+ return '' if !@answers[:rural] || @answers[:rural].nil?
513
+
514
+ " (#{rural_label(@answers[:rural])})"
515
+ end
516
+
469
517
  def rows_recap
470
518
  if @answers[:rate_mode] == :cubic
471
519
  "cubic tiers #{RunSpec.compact_range(@answers[:weights])}"
@@ -500,8 +548,8 @@ module RateCard
500
548
  # transcript of the run stays on screen — the recap is then a summary of
501
549
  # what is already visible rather than the first chance to check it.
502
550
  def answered_line(stage, value)
503
- label = { rate_mode: 'rate by', carrier: 'carrier', services: 'services', zones: 'zones',
504
- unit: 'unit', weights: 'weights', package_type: 'package',
551
+ label = { rate_mode: 'rate by', carrier: 'carrier', rural: 'rural / DAS', services: 'services',
552
+ zones: 'zones', unit: 'unit', weights: 'weights', package_type: 'package',
505
553
  rate_keys: 'columns' }[stage]
506
554
  label = 'cubic tiers' if stage == :weights && @answers[:rate_mode] == :cubic
507
555
  return nil if label.nil?
@@ -514,10 +562,20 @@ module RateCard
514
562
  when :services then value.map(&:name).join(', ')
515
563
  when :zones then RunSpec.compact_range(value)
516
564
  when :weights then RunSpec.compact_range(value)
565
+ when :rural then rural_label(value)
517
566
  when :rate_keys then value.map { |k| RunSpec::RATE_KEY_LABELS.fetch(k) }.join(', ')
518
567
  else value.to_s
519
568
  end
520
569
  end
570
+
571
+ def rural_label(value)
572
+ case value
573
+ when false, nil then 'normal'
574
+ when true then 'rural (DAS)'
575
+ when :edas, :rdas then value.to_s.upcase
576
+ when :both then 'EDAS + RDAS'
577
+ end
578
+ end
521
579
  end
522
580
  end
523
581
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RateCard
4
- VERSION = '0.1.1'
4
+ VERSION = '0.1.3'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rate-card
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - eHub