Class: Item::HeatingElementPlan

Inherits:
Object
  • Object
show all
Defined in:
app/services/item/heating_element_plan.rb

Overview

Service object: heating element plan.

Constant Summary collapse

SPECIAL_LEGEND_DESCRIPTIONS =

Special legend descriptions.

{ 'ot_sensor' => 'OT Sensor *', 'slab_sensor' => 'SCE-SLAB-SS-KIT Sensor #' }.freeze

Instance Attribute Summary collapse

Delegated Instance Attributes collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(line_items, options = {}) ⇒ HeatingElementPlan

Initialize with an LineItem result set of heating elements or pass
options:
cable_spacing: 3.0 if you want to append the cable spacing in inche to the width
heating_system: name of the heating system

Parameters:

  • line_items (ActiveRecord::Relation<LineItem>)

    heating element line items

  • options (Hash) (defaults to: {})

    Options hash

Options Hash (options):

  • :heating_system (String)

    name of the heating system (defaults to 'TempZone Thin Cable')

  • :cable_spacing (Float)

    cable spacing in inches, appended to the width

  • :voltage (Integer)

    system voltage (defaults to 120)

  • :is_countertop (Boolean)

    whether this is a countertop installation (defaults to false)

  • :installation_postal_code (String)

    postal code of the installation

  • :electricity_rate (Float)

    electricity rate used for cost estimates

  • :installation_sqft (Float)

    installation area in square feet

  • :locale (String, Symbol)

    locale for output (defaults to I18n.locale)

  • :state_code (String)

    state code of the installation

  • :country_iso (String)

    country ISO code of the installation



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'app/services/item/heating_element_plan.rb', line 55

def initialize(line_items, options = {})
  @options = options
  self.all_line_items = line_items.without_children
  self.all_line_items_including_parents = line_items
  self.other_items = all_line_items.to_a - all_line_items.heating_elements.to_a # line_items.controls.to_a + line_items.accessories.to_a
  self.heating_system = @options[:heating_system] || 'TempZone Thin Cable' # use this as the default
  self.cable_spacing = @options[:cable_spacing]
  self.voltage = @options[:voltage] || 120 # use this as the default
  self.is_countertop = @options[:is_countertop] || false # use this as the default
  self.heating_elements_line_items = all_line_items.heating_elements.sort_for_plan

  self.installation_postal_code = @options[:installation_postal_code]
  self.electricity_rate = @options[:electricity_rate]
  self.installation_sqft = @options[:installation_sqft]
  self.locale = @options[:locale] || I18n.locale
  self.state_code = @options[:state_code]
  self.country_iso = @options[:country_iso]
  generate_heating_elements_lines
end

Instance Attribute Details

#all_line_itemsObject

Returns the value of attribute all_line_items.



8
9
10
# File 'app/services/item/heating_element_plan.rb', line 8

def all_line_items
  @all_line_items
end

#all_line_items_including_parentsObject

Returns the value of attribute all_line_items_including_parents.



8
9
10
# File 'app/services/item/heating_element_plan.rb', line 8

def all_line_items_including_parents
  @all_line_items_including_parents
end

#cable_spacingObject

Returns the value of attribute cable_spacing.



8
9
10
# File 'app/services/item/heating_element_plan.rb', line 8

def cable_spacing
  @cable_spacing
end

#country_isoObject

Returns the value of attribute country_iso.



8
9
10
# File 'app/services/item/heating_element_plan.rb', line 8

def country_iso
  @country_iso
end

#electricity_rateObject

Returns the value of attribute electricity_rate.



8
9
10
# File 'app/services/item/heating_element_plan.rb', line 8

def electricity_rate
  @electricity_rate
end

#heating_elements_line_itemsObject

Returns the value of attribute heating_elements_line_items.



8
9
10
# File 'app/services/item/heating_element_plan.rb', line 8

def heating_elements_line_items
  @heating_elements_line_items
end

#heating_elements_linesObject

Returns the value of attribute heating_elements_lines.



8
9
10
# File 'app/services/item/heating_element_plan.rb', line 8

def heating_elements_lines
  @heating_elements_lines
end

#heating_systemObject

Returns the value of attribute heating_system.



8
9
10
# File 'app/services/item/heating_element_plan.rb', line 8

def heating_system
  @heating_system
end

#installation_postal_codeObject

Returns the value of attribute installation_postal_code.



8
9
10
# File 'app/services/item/heating_element_plan.rb', line 8

def installation_postal_code
  @installation_postal_code
end

#installation_sqftObject

Returns the value of attribute installation_sqft.



8
9
10
# File 'app/services/item/heating_element_plan.rb', line 8

def installation_sqft
  @installation_sqft
end

#is_countertopObject

Returns the value of attribute is_countertop.



8
9
10
# File 'app/services/item/heating_element_plan.rb', line 8

def is_countertop
  @is_countertop
end

#localeObject

Returns the value of attribute locale.



8
9
10
# File 'app/services/item/heating_element_plan.rb', line 8

def locale
  @locale
end

#other_itemsObject

Returns the value of attribute other_items.



8
9
10
# File 'app/services/item/heating_element_plan.rb', line 8

def other_items
  @other_items
end

#state_codeObject

Returns the value of attribute state_code.



8
9
10
# File 'app/services/item/heating_element_plan.rb', line 8

def state_code
  @state_code
end

#voltageObject

Returns the value of attribute voltage.



8
9
10
# File 'app/services/item/heating_element_plan.rb', line 8

def voltage
  @voltage
end

Class Method Details

.get_room_best_location(rc) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'app/services/item/heating_element_plan.rb', line 23

def self.get_room_best_location(rc)
  res = {}
  if rc.opportunity&.installation_postal_code.present?
    res[:installation_postal_code] = rc.opportunity.installation_postal_code
  elsif (shippping_address = rc.orders.first&.shipping_address || rc.quotes.first&.shipping_address).present?
    res[:state_code] = shippping_address.state_code
    res[:country_iso] = shippping_address.country_iso
    res[:installation_postal_code] = shippping_address.zip
  else
    res[:state_code] = rc.opportunity.customer.state_code
    res[:country_iso] = rc.opportunity.customer.country_iso
    res[:installation_postal_code] = rc.opportunity.customer.zip
  end
  res
end

.new_from_room_configuration(rc, _options = {}) ⇒ Object



11
12
13
14
15
16
17
18
19
20
21
# File 'app/services/item/heating_element_plan.rb', line 11

def self.new_from_room_configuration(rc, _options = {})
  new(rc.line_items.goods,
      heating_system: rc.heating_system_type_name,
      cable_spacing: rc.installation_plan_cable_spacing || rc.line_items.primary_heating_element_spacing,
      voltage: rc.installation_plan_voltage_id,
      electricity_rate: rc.opportunity.electricity_rate,
      is_countertop: rc.is_countertop?,
      installation_sqft: rc.installation_sqft,
      locale: rc.opportunity.customer.locale,
      **get_room_best_location(rc))
end

Instance Method Details

#air_sensorsObject



459
460
461
# File 'app/services/item/heating_element_plan.rb', line 459

def air_sensors
  line_items_for_sku(%w[AIR-SS AIR-SS-2 SCPM-SS-120])
end

#alternative_volts_explanationObject

make a hash of all alternative volts which we can display on the install plan



301
302
303
304
305
306
307
308
309
310
311
# File 'app/services/item/heating_element_plan.rb', line 301

def alternative_volts_explanation
  volts = {}
  symbol = 1
  heating_elements_lines.select { |hel| hel.alternative_volts.present? }.each do |hel|
    unless volts.value?(hel.alternative_volts)
      volts[symbol] = hel.alternative_volts
      symbol += 1
    end
  end
  volts
end

#breaker_rulesObject



608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
# File 'app/services/item/heating_element_plan.rb', line 608

def breaker_rules
  rules = []
  if ['TempZone Flex Roll', 'Environ Flex Roll', 'TempZone Easy Mat', 'Environ Easy Mat', 'TempZone Cable', 'TempZone Ruler Cable', 'TempZone Thin Cable',
      'TempZone Shower Mat', 'TempZone Custom Mat', 'Shower Mat Bench'].include?(heating_system)
    if relays.present?
      breakers = {}
      heating_elements_lines.each do |hel|
        bs = "#{breaker_size(hel.amps)} Amp #{hel.volts}V GFI Heater Circuit"
        new_qty = (breakers[bs] || 0) + 1
        breakers[bs] = new_qty
      end
      breakers.each_pair { |key, value| rules << "#{value} x #{key}" }
      rules << "#{controls.sum(:quantity)} x 15 Amp 120V Non-GFI Control Circuit" unless controls.empty?
    elsif (num_power_modules = power_modules.sum(:quantity)).positive?
      # How many switching thermostats do we have as well?
      num_tstats = current_switching_thermostats.sum(&:quantity)
      total_switching_relays = num_tstats + num_power_modules
      if total_switching_relays == heating_elements_lines.length
        # SPECIAL CASE: If total_switching_relays matches number of heating elements, then assume 1 per switching relay and show each unit's breaker based on the heating element's amps.
        breakers = {}
        heating_elements_lines.each do |hel|
          bs = "#{breaker_size(hel.amps)} Amp #{hel.volts}V Non-GFI Heater Circuit"
          new_qty = (breakers[bs] || 0) + 1
          breakers[bs] = new_qty
        end
        breakers.each_pair do |key, value|
          rules << "#{value} x #{key}"
        end
      else
        rules << "#{total_switching_relays} x 20 Amp #{heating_system_voltage}V Non-GFI Circuit"
      end
    elsif integration_kits.sum(:quantity) == 1
      # if there is just 1 integration kit then show breaker info based on total heating load
      rules << "1 x #{breaker_size(total_amps, 30)} Amp #{heating_system_voltage}V GFI Circuit"
    elsif integration_kits.sum(:quantity) > 1
      rules << "#{integration_kits.sum(:quantity)} x 30 Amp #{integration_kits.first.item.voltage}V GFI Circuit"
    elsif current_switching_thermostats.present?
      if (num_tstats = current_switching_thermostats.sum(&:quantity)) > 1
        if num_tstats == heating_elements_lines.length
          # SPECIAL CASE: If multiple thermostats matches number of heating elements, then assume 1 per tstat and show each unit's breaker based on the heating element's amps.
          breakers = {}
          heating_elements_lines.each do |hel|
            bs = "#{breaker_size(hel.amps)} Amp #{hel.volts}V Non-GFI Heater Circuit"
            new_qty = (breakers[bs] || 0) + 1
            breakers[bs] = new_qty
          end
          breakers.each_pair do |key, value|
            rules << "#{value} x #{key}"
          end
        else
          # If multiple thermostats, then show qty of breakers = qty of thermostats, and show each unit need 20 Amps breaker, since we are assuming maximum load.
          rules << "#{num_tstats} x 20 Amp #{heating_system_voltage}V Non-GFI Circuit"
        end
      else
        # else if single thermostat then show 1 breaker and use regular breaker size rule for total heating elements, upto max of 20 Amps.
        rules << "1 x #{breaker_size(total_amps, 20)} Amp #{heating_system_voltage}V Non-GFI Circuit"
      end
    else # no controls, so use generic sentence
      rules << 'The warming system must be electrically grounded and protected by a GFCI'
    end
  elsif ['Slab Heat Mat', 'Slab Heat Cable'].include?(heating_system)
    if relays.present?
      breakers = {}
      heating_elements_lines.each do |hel|
        bs = "#{breaker_size(hel.amps)} Amp #{hel.volts}V GFI Heater Circuit"
        new_qty = (breakers[bs] || 0) + 1
        breakers[bs] = new_qty
      end
      breakers.each_pair { |key, value| rules << "#{value} x #{key}" }
      rules << "#{controls.sum(:quantity)} x 15 Amp 120V Non-GFI Control Circuit" unless controls.empty?
    elsif (num_power_modules = power_modules.sum(:quantity)).positive? # if a power modulator is present
      # How many switching thermostats do we have as well?
      num_tstats = current_switching_thermostats.sum(&:quantity)
      total_switching_relays = num_tstats + num_power_modules
      rules << "#{total_switching_relays} x 20 Amp #{heating_system_voltage}V Non-GFI Circuit"
    elsif controls.empty? # no relays, no controls, so use generic sentence
      rules << 'The warming system must be electrically grounded and protected by a GFCI'
    else # no relays, but there are controls
      rules << "#{controls.sum(:quantity)} x #{breaker_size(total_amps)} Amp #{heating_system_voltage}V Non-GFI Circuit" unless controls.empty?
    end
  elsif ['Snow Melt Mat', 'Snow Melt PowerMat', 'Snow Melt OmniMat', 'Snow Melt EcoMat', 'Snow Melt Cable'].include?(heating_system)
    if relays.present?
      breakers = {}
      heating_elements_lines.each do |hel|
        bs = "#{breaker_size(hel.amps)} Amp #{hel.volts}V GFEP Heater Circuit"
        breakers[bs] ||= 0
        breakers[bs] += 1
      end
      breakers.each_pair { |key, value| rules << "#{value} x #{key}" }
      rules << "#{controls.sum(:quantity)} x 15 Amp 120V Non-GFI Control Circuit" unless controls.empty?
    elsif has_item?(ItemConstants::POWER_MODULATORS) # if a power modulator is present
      power_modulator_count = line_items_for_sku(ItemConstants::POWER_MODULATORS).sum(:quantity)
      rules << "#{power_modulator_count} x 120V Non-GFI Control Circuit"
      rules << "1 x #{heating_system_voltage}V Non-GFI Circuit per contactor"
    elsif controls.empty? # no relays, no controls, so qty of breakers = qty of heating elements
      breakers = {}
      heating_elements_lines.each do |hel|
        bs = "#{breaker_size(hel.amps)} Amp #{hel.volts}V GFEP Heater Circuit"
        new_qty = (breakers[bs] || 0) + 1
        breakers[bs] = new_qty
      end
      breakers.each_pair { |key, value| rules << "#{value} x #{key}" }
    else # no relays, but there are controls
      rules << "#{controls.sum(:quantity)} x #{breaker_size(total_amps)} Amp #{heating_system_voltage}V GFEP Circuit" unless controls.empty?
    end
  elsif /roof|pipe/i.match?(heating_system)
    number_of_power_kits = line_items_for_sku(%w[SR-PWR-KIT PT-PWR-KIT-H]).sum(:quantity)
    if has_item?('SR-PLUG-KIT') # if a plug-in kit is present
      number_of_kits = line_items_for_sku('SR-PLUG-KIT').sum(:quantity)
      rules << "#{number_of_kits} x 15 AMP 120V Non-GFI Circuit (1 outlet required per plug-in kit)"
    elsif relays.present?
      rules << "#{controls.sum(:quantity)} x 15 Amp 120V Non-GFI Control Circuit" unless controls.empty?
      if [120, 240].include?(heating_system_voltage)
        rules << if number_of_power_kits.zero?
                   "#{heating_system_voltage}V"
                 else
                   "#{number_of_power_kits} x #{heating_system_voltage}V GFEP Heater Circuit"
                 end
        rules << "Refer to electrical plan's circuit breaker size vs cable length table"
      end
    elsif heating_system.match(/roof/i) && has_item?(ItemConstants::POWER_MODULATORS) # if r&g and a power modulator is present
      power_modulator_count = line_items_for_sku(ItemConstants::POWER_MODULATORS).sum(:quantity)
      rules << "#{power_modulator_count} x 120V Non-GFI Control Circuit"
      rules << "1 x #{heating_system_voltage}V Non-GFI Circuit per contactor"
    elsif controls.empty? # no relays, no controls, so qty of breakers = qty of heating elements
      rules << if number_of_power_kits.zero?
                 "#{heating_system_voltage}V"
               else
                 "#{number_of_power_kits} x #{heating_system_voltage}V GFEP Heater Circuit"
               end
      rules << "Refer to electrical plan's circuit breaker size vs cable length table"
    elsif /pipe/i.match?(heating_system) # no relays, but there are controls
      rules << "Refer to electrical plan's circuit breaker size vs cable length table"
    else
      unless controls.empty?
        rules << "#{controls.sum(:quantity)} x #{roof_and_gutter_breaker_size(heating_system_voltage,
                                                                              total_length)} Amp #{heating_system_voltage}V GFEP Circuit"
      end
    end
  else
    []
  end
  rules
end

#breaker_size(amps = total_amps, max_amps = 50) ⇒ Object



494
495
496
497
498
499
500
501
502
# File 'app/services/item/heating_element_plan.rb', line 494

def breaker_size(amps = total_amps, max_amps = 50)
  case amps
  when 0..12 then 15
  when 12.01..16 then [20, max_amps].min
  when 16.01..24 then [30, max_amps].min
  when 24.01..32 then [40, max_amps].min
  when 32.01..40 then [50, max_amps].min
  end
end

#build_from_line_item(line_item, qty = nil, heating_element_number = nil) ⇒ Object



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'app/services/item/heating_element_plan.rb', line 264

def build_from_line_item(line_item, qty = nil, heating_element_number = nil)
  item = line_item.item
  multiplier = (qty || line_item.quantity || 1).to_f
  # Self healing in case specs are missing
  if item.voltage.blank? || item.amps.blank? || item.watts.blank? || item.ohms.blank?
    item.update_rendered_product_specifications(tokens: %w[voltage amps watts ohms])
    item.reload
  end
  begin
    element_pole_assignments = if line_item.room_configuration && heating_element_number
                                 line_item.room_configuration.element_pole_assignments.ordered.where(heating_element_number: heating_element_number,
                               heating_element_item_id: line_item.item_id)
                               else
                                 ElementPoleAssignment.none
                               end
    Item::HeatingElementLine.new(
      sku: item.sku,
      width: cable_spacing || item.width,
      length: (item.length.nil? ? nil : item.length * multiplier),
      volts: item.voltage,
      alternative_volts: item.alternative_volts.presence&.join(', '),
      amps: (item.amps.to_f * multiplier).round(2),
      watts: (item.watts.to_f * multiplier),
      btu_per_hr: (item.watts.to_f * multiplier) * RoomConfiguration::BTU_PER_HOUR_PER_WATT,
      watts_per_linear_feet: item.watts_per_linear_feet,
      cold_lead_length: item.cold_lead_length,
      ohms: item.ohms&.round(2),
      poles: element_pole_assignments.map(&:pole_number).join(', '),
      relay: element_pole_assignments.first&.relay_identifier,
      heating_element_number: heating_element_number
    )
  rescue StandardError => e
    ErrorReporting.error(e, { line_item_id: line_item.id, item_id: item.id, item_sku: item.sku })
  end
end

#cold_leadsObject



467
468
469
# File 'app/services/item/heating_element_plan.rb', line 467

def cold_leads
  line_items_for_sku('COLDLEAD')
end

#cold_leads_lengthObject



480
481
482
# File 'app/services/item/heating_element_plan.rb', line 480

def cold_leads_length
  cold_leads.sum(:quantity)
end

#control_voltageObject



431
432
433
# File 'app/services/item/heating_element_plan.rb', line 431

def control_voltage
  relays.present? ? 120 : heating_system_voltage
end

#controlsObject

Alias for All_line_items#controls

Returns:

  • (Object)

    All_line_items#controls

See Also:



441
# File 'app/services/item/heating_element_plan.rb', line 441

delegate :controls, to: :all_line_items

#current_switching_thermostatsObject



580
581
582
# File 'app/services/item/heating_element_plan.rb', line 580

def current_switching_thermostats
  all_line_items.smartstats + all_line_items.easystats + all_line_items.oj_tstats
end

#display_cutting_info?Boolean

Returns:

  • (Boolean)


540
541
542
# File 'app/services/item/heating_element_plan.rb', line 540

def display_cutting_info?
  ['TempZone Flex Roll', 'Environ Flex Roll'].include?(heating_system)
end

#electrical_consumptionObject



769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
# File 'app/services/item/heating_element_plan.rb', line 769

def electrical_consumption
  return nil if heating_elements_line_items.any? { |he| he.item.is_self_regulating_cable? }
  return nil unless total_watts&.positive?

  avg_rate = ElectricityRate.get_average_for_locale(locale) # I don't see a way to determine the locale to use if no zip code
  disclaimer = "This calculation is based on the national average of $#{avg_rate} per kWh, but consumption may vary based on individual conditions."
  if electricity_rate.present?
    avg_rate = electricity_rate.round(4)
    disclaimer = "This calculation is based on the rate of $#{avg_rate} per kWh, but consumption may vary based on individual conditions."
  elsif installation_postal_code.present?
    result = ElectricityRate.get_from_postal_code(installation_postal_code)
    if result[:status] == :ok && result[:average_rate].present?
      avg_rate = result[:average_rate].round(4)
      disclaimer = "This calculation is based on the average rate of $#{avg_rate} per kWh for zip/postal code #{installation_postal_code}, but consumption may vary based on individual conditions."
    end
  elsif (state_rate = ElectricityRate.get_average_rate_for_country_iso_state_code(country_iso, state_code))
    avg_rate = state_rate
    disclaimer = "This calculation is based on the average rate of $#{avg_rate} per kWh for state/province #{state_code}, but consumption may vary based on individual conditions."
  end

  # 2.3 = thermal inertia
  # 1 = number of hours operation
  if heating_system.index(/roof|snow|pipe/i)
    # For snow melt we typically don't account for thermal inertia, because these systems can be running non-stop (Anatoliy 3/16/17)
    coefficient = 1
  else
    coefficient = 0.4348
    disclaimer << ' Operating cost is estimated based on the average thermal inertia due to the thermostat cycling the power during the heating period.'
  end
  watts_per_sqft(true)

  consumption_rate = ((avg_rate * coefficient * total_watts) / 1000).round(2)
  {
    rate: consumption_rate,
    coefficient:,
    avg_rate:,
    total_watts:,
    disclaimer:
  }
end

#electrical_plan_controlsObject



471
472
473
474
# File 'app/services/item/heating_element_plan.rb', line 471

def electrical_plan_controls
  # here we really want parents_only i.e. no kit compnents, I believe, per Anatoliy's request to not display the Zone braker twice
  all_line_items_including_parents.electrical_plan_controls.parents_only + line_items_for_sku('SR-PLUG-KIT')
end

#electrical_rough_in_kitsObject



584
585
586
# File 'app/services/item/heating_element_plan.rb', line 584

def electrical_rough_in_kits
  all_line_items_including_parents.joins(:item).where(items: { sku: 'FHE-ROUGH-IN-KIT-03' })
end

#extra_instructionsObject



753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
# File 'app/services/item/heating_element_plan.rb', line 753

def extra_instructions
  instructions = []
  # Cold lead
  if cold_leads_length.positive?
    instructions << 'COLD LEAD:'
    instructions << "#{cold_leads_length}' of additional cold lead"
  end

  # Aerial Sensor
  if air_sensors.present?
    instructions << 'AERIAL SENSOR:'
    instructions << 'Provided, but not shown on drawing. Location per installer\'s discretion. WarmlyYours recommends it to be in an area most indicative of snow conditions.'
  end
  instructions
end

#first_heating_element_voltageObject



572
573
574
# File 'app/services/item/heating_element_plan.rb', line 572

def first_heating_element_voltage
  heating_elements_lines.first&.volts
end

#floor_loadObject



403
404
405
406
# File 'app/services/item/heating_element_plan.rb', line 403

def floor_load
  # needs to be 1000.0 so both values are Float, else can lead to it being 0
  (total_watts / 1000.0).round(1)
end

#floor_load_displayObject

Floor load formatted to ~3 significant figures across the full range of
possible loads: integer part is never rounded away for large loads
(21453 kW stays "21453"), and decimals grow as the value drops below 1 kW
(0.573 kW stays "0.573", 0.0121 kW stays "0.0121"). Avoids the old
round(1) collapsing small loads to a single decimal ("0.6").



413
414
415
416
417
418
419
420
421
# File 'app/services/item/heating_element_plan.rb', line 413

def floor_load_display
  kw = total_watts / 1000.0
  return "0" if kw.zero?

  # ponytail: 3 sig figs = 2 - order-of-magnitude; clamped so the integer
  # part is always shown in full and never negative.
  decimals = [2 - Math.log10(kw.abs).floor, 0].max
  format("%.#{decimals}f", kw)
end

#flooring_labelObject



604
605
606
# File 'app/services/item/heating_element_plan.rb', line 604

def flooring_label
  is_outdoor? || is_countertop? ? 'Surface Type' : 'Flooring'
end

#generate_heating_elements_linesObject



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'app/services/item/heating_element_plan.rb', line 194

def generate_heating_elements_lines
  self.heating_elements_lines = []
  heating_element_number = 0
  heating_elements_line_items.each do |he_line_item|
    if he_line_item.item.spec_value(:plan_grouping) == 'group_qty_to_length'
      # Group items by sku on plan
      # Build heating element line will use implicit line item quantity multiplier
      heating_elements_lines << build_from_line_item(he_line_item)
    else
      # one line per quantity will split the line items rendering one in the table for each qty
      he_line_item.quantity.times do
        heating_element_number += 1
        # Force quantity multiplier to one since we're splitting the rendering
        heating_elements_lines << build_from_line_item(he_line_item, 1, heating_element_number)
      end
    end
  end
end

#has_item?(sku) ⇒ Boolean

Returns:

  • (Boolean)


447
448
449
# File 'app/services/item/heating_element_plan.rb', line 447

def has_item?(sku)
  line_items_for_sku(sku).present?
end

#heating_elements_table(with_cold_lead: false, with_ohms: false, with_pole_assignment: false) ⇒ Object



345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
# File 'app/services/item/heating_element_plan.rb', line 345

def heating_elements_table(with_cold_lead: false, with_ohms: false, with_pole_assignment: false)
  table = []
  heating_elements_lines.each_with_index do |hel, i|
    row = {}
    row[:row_number] = i + 1
    heating_elements_table_column_headers(with_cold_lead: with_cold_lead, with_ohms: with_ohms, with_row_number: false, with_pole_assignment: with_pole_assignment).each_key do |attrib|
      row[attrib] = if attrib == :volts
                      hel.volts_with_alternative_volts(alternative_volts_explanation)
                    else
                      hel.send(attrib)
                    end
    end
    table << row
  end
  table
end

#heating_elements_table_column_headers(with_cold_lead: false, with_ohms: false, with_row_number: true, with_pole_assignment: false) ⇒ Object

rubocop:disable Lint/UnusedMethodArgument



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# File 'app/services/item/heating_element_plan.rb', line 317

def heating_elements_table_column_headers(with_cold_lead: false, with_ohms: false, with_row_number: true, with_pole_assignment: false) # rubocop:disable Lint/UnusedMethodArgument
  _ = with_cold_lead
  _ = with_ohms
  headers = {}
  headers[:row_number] = '#' if with_row_number
  unless /TempZone Custom Mat/i.match?(heating_system)
    headers[:imperial_width] = (cable_spacing.present? ? 'Spacing' : 'Width') unless /roof|pipe/i.match?(heating_system)
    headers[:imperial_length] = 'Length'
  end
  headers[:imperial_cold_lead_length] = 'Lead' unless /roof|pipe/i.match?(heating_system)
  headers[:volts] = 'Volts'
  if /roof|pipe/i.match?(heating_system)
    headers[:watts_per_linear_feet] = 'Watts Per Ft'
  else
    headers[:watts] = 'Watts'
    headers[:btu_per_hr] = 'BTU/h'
    headers[:amps] = 'Amps'
    headers[:ohms] = 'Ohms'
  end

  if with_pole_assignment
    headers[:poles] = 'Poles'
    headers[:relay] = 'Relay'
  end

  headers
end

#heating_elements_table_footersObject



362
363
364
365
366
367
368
369
370
371
# File 'app/services/item/heating_element_plan.rb', line 362

def heating_elements_table_footers
  return {} if heating_elements_lines.length <= 1 # don't add a footer if there's only 1 row

  footers = {}
  footers[:imperial_length] = UnitConversions.inches_to_feetinches(total_length) if heating_system.index(/roof|pipe/i)
  footers[:watts] = total_watts unless heating_system.index(/roof|pipe/i)
  footers[:btu_per_hr] = total_btu_per_hr unless heating_system.index(/roof|pipe/i)
  footers[:amps] = total_amps unless heating_system.index(/roof|pipe/i)
  footers
end

#heating_system_nameObject



548
549
550
551
552
553
554
555
556
557
558
559
# File 'app/services/item/heating_element_plan.rb', line 548

def heating_system_name
  case heating_system
  when 'TempZone Thin Cable'
    'TempZone 3W Cable'
  when 'TempZone Cable'
    'TempZone 3.7W Cable'
  when 'TempZone Ruler Cable'
    'TempZone 3.7W Ruler Cable'
  else
    heating_system
  end
end

#heating_system_voltageObject



576
577
578
# File 'app/services/item/heating_element_plan.rb', line 576

def heating_system_voltage
  voltage || first_heating_element_voltage
end

#heating_system_with_voltageObject



561
562
563
564
565
566
567
568
569
570
# File 'app/services/item/heating_element_plan.rb', line 561

def heating_system_with_voltage
  addendum_for_mixed_systems = ''
  addendum_for_mixed_systems = ' & Cable' if (heating_system_name.to_s.downcase.index('mat') || heating_system_name.to_s.downcase.index('roll')) && heating_elements_line_items.any? do |li|
    li.item.is_cable_system?
  end
  addendum_for_mixed_systems = ' & Mat/Roll' if heating_system_name.to_s.downcase.index('cable') && !heating_system_name.to_s.downcase.index('roof') && !heating_system_name.to_s.downcase.index('pipe') && heating_elements_line_items.any? do |li|
                                                  !li.item.is_cable_system?
                                                end
  "#{heating_system_name}#{addendum_for_mixed_systems} #{voltage}V"
end

#hide_flip_turn?Boolean

Returns:

  • (Boolean)


544
545
546
# File 'app/services/item/heating_element_plan.rb', line 544

def hide_flip_turn?
  [nil, 'Environ Flex Roll'].include?(heating_system)
end

#highlighted_columnsObject



313
314
315
# File 'app/services/item/heating_element_plan.rb', line 313

def highlighted_columns
  %i[voltage watts btu_per_hr amps ohms]
end

#instructions(include_electrical_plan_rules = false, include_installation_plan_rules = false) ⇒ Object



810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
# File 'app/services/item/heating_element_plan.rb', line 810

def instructions(include_electrical_plan_rules = false, include_installation_plan_rules = false)
  inst = []
  inst << 'All power leads travel back to thermostat or junction box.' if ['TempZone Flex Roll', 'Environ Flex Roll', 'Environ Easy Mat',
                                                                           'TempZone Easy Mat', 'TempZone Cable', 'TempZone Ruler Cable', 'TempZone Thin Cable', 'Slab Heat Cable', 'Slab Heat Mat', 'TempZone Shower Mat', 'Shower Mat Bench'].include?(heating_system)
  inst << 'All power leads travel back to the controller, relay panel or junction box.' if heating_system.index(/roof|snow|pipe/i)
  if include_installation_plan_rules == true
    inst << 'Always start warming cable down unless noted otherwise.' if ['TempZone Flex Roll', 'Environ Flex Roll'].include?(heating_system)
    inst << 'Cut only mesh, never cut warming cable.' if ['TempZone Flex Roll', 'Slab Heat Mat', 'Snow Melt Mat', 'Snow Melt PowerMat', 'Snow Melt OmniMat', 'Snow Melt EcoMat'].include?(heating_system)
    inst << 'Cut only silver material, never cut warming cable.' if ['Environ Flex Roll'].include?(heating_system)
    inst << 'Never cut heating rolls.' if ['Environ Easy Mat', 'TempZone Easy Mat', 'TempZone Shower Mat',
                                           'Shower Mat Bench'].include?(heating_system)
    inst << 'Never cut heating cable.' if ['TempZone Cable', 'TempZone Ruler Cable', 'TempZone Thin Cable', 'Snow Melt Cable', 'Snow Melt Cable'].include?(heating_system)
    inst << 'Never wire multiple rolls or cables together.' # all systems
    inst << 'Verify sum of panels equals roll length for each roll.' if ['TempZone Flex Roll', 'Environ Flex Roll', 'Slab Heat Mat',
                                                                         'Snow Melt Mat', 'Snow Melt PowerMat', 'Snow Melt OmniMat', 'Snow Melt EcoMat'].include?(heating_system)
  end
  inst << 'Keep the bus wires separated. The bus wires will short if they touch each other.' if heating_system.index(/roof|pipe/i)
  inst << 'Keep components and ends of heating cable dry before installation.' if heating_system.index(/roof|pipe/i)
  inst << 'The heating cable should not be embedded in insulation or roofing material.' if heating_system.index(/roof/i)
  inst << 'Do not twist cable during installation.' if heating_system.index(/roof|pipe/i)
  if include_electrical_plan_rules == true
    inst << 'Always test rolls/cables out of the box with an Ohm meter and Mega-Ohm meter (optional). See manual for other required tests.' # all systems
    unless heating_system.index(/roof|pipe|snow/i)
      inst << 'Integration Kit: Can be installed on the side or inside of the separate electrical box or a distribution panel, according to NEC and local code requirements.' if integration_kits.present?
      if has_item?(%w[UWG4-4999 UWG4-4999-WY UWG5-4999-WY UWG4-4999-B UDG4-4999 UDG4-4999-WY UDG4-4999-B UDG-4999 UTN4-4999 USG-4000 USG5-4000]) # if an OJ tstat is present
        tstat_box_size = conduit_box_size = '3"x2"x3-1/2" single gang'
      else
        tstat_box_size = '4"x4" square'
        conduit_box_size = '4" double gang'
      end
      if thermostats.any? && integration_kits.blank?
        inst << "Thermostat: Install a #{tstat_box_size}, 2-1/8\" deep electrical box according to NEC and local code requirements. Electrical boxes should be located on interior walls, commonly 60\" from the floor."
      end
      inst << "Power lead Conduit: The shielded power lead can be installed with or without electrical conduit depending on local code requirements. In either case, remove one of the knock-outs in the #{conduit_box_size} box to route the lead. If electrical conduit is not required by code, install a wire collar to secure the leads where they enter the box."
      if sensors.present?
        inst << 'Floor Sensor: A floor sensor comes with our floor heating systems. It can be installed in a conduit separate from the electrical power lead. If local code requires the low voltage sensor wire be housed in conduit, it must use a separate conduit from the power leads (high voltage).'
      end
      if power_modules.present?
        inst << 'Power module(s) must be wired to the main Thermostat using low-voltage field wiring cable, recommended min. 20 AWG. Connect wiring from A&B terminals on the front of the thermostat base to C&D on the front of the power module base. (Keep polarity: A to C and B to D). Maximum distance between thermostat and power modules is 80 ft (25 m.) See the diagrams in the power module box.'
      end
    end
  end
  inst
end

#integration_kitsObject

Alias for All_line_items#integration_kits

Returns:

  • (Object)

    All_line_items#integration_kits

See Also:



437
# File 'app/services/item/heating_element_plan.rb', line 437

delegate :integration_kits, to: :all_line_items

#is_countertop?Boolean

Returns:

  • (Boolean)


592
593
594
# File 'app/services/item/heating_element_plan.rb', line 592

def is_countertop?
  is_countertop == true
end

#is_environ?Boolean

Returns:

  • (Boolean)


242
243
244
# File 'app/services/item/heating_element_plan.rb', line 242

def is_environ?
  ['Environ Flex Roll', 'Environ Easy Mat'].include?(heating_system)
end

#is_indoor_floor_heating_system?Boolean

Returns:

  • (Boolean)


237
238
239
240
# File 'app/services/item/heating_element_plan.rb', line 237

def is_indoor_floor_heating_system?
  ['TempZone Flex Roll', 'TempZone Easy Mat', 'TempZone Shower Mat', 'Shower Mat Bench', 'TempZone Cable', 'TempZone Ruler Cable', 'TempZone Thin Cable', 'Environ Flex Roll',
   'Environ Easy Mat'].include?(heating_system)
end

#is_outdoor?Boolean

Returns:

  • (Boolean)


588
589
590
# File 'app/services/item/heating_element_plan.rb', line 588

def is_outdoor?
  heating_system.index(/roof|snow|pipe/i) != nil
end

#is_pipe_freeze?Boolean

Returns:

  • (Boolean)


596
597
598
# File 'app/services/item/heating_element_plan.rb', line 596

def is_pipe_freeze?
  heating_system.index(/pipe/i) != nil
end

#legendObject



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'app/services/item/heating_element_plan.rb', line 213

def legend
  symbols = case heating_system
            when /TempZone Flex Roll|Environ Flex Roll|TempZone Shower Mat|Shower Mat Bench/i
              %w[start end roll_number]
            when /Snow Melt Mat|TempZone Easy Mat|Slab Heat Mat/i
              %w[start end mat_number]
            when /TempZone Cable|TempZone Ruler Cable|TempZone Thin Cable|Snow Melt Cable|Slab Heat Cable/i
              %w[start end cable_number halfway_mark]
            when /roof|pipe/i
              %w[start end cable_number]
            when 'Environ Easy Mat'
              ['roll_number']
            else
              []
            end
  symbols << 'thermostat' if all_line_items.thermostats.present?
  symbols << 'relay' if relays.present? && is_indoor_floor_heating_system?
  symbols << 'power_module' if all_line_items.power_modules.present?
  symbols << 'integration_kit' if integration_kits.present?
  symbols << 'ot_sensor' if ot_sensors.present?
  symbols << 'slab_sensor' if has_item?('SLAB-SS') || has_item?('SCE-SLAB-SS-56')
  symbols
end

#line_items_for_sku(sku) ⇒ Object



451
452
453
# File 'app/services/item/heating_element_plan.rb', line 451

def line_items_for_sku(sku)
  all_line_items.joins(:item).where(items: { sku: })
end

#number_of_circuitsObject



484
485
486
487
488
489
490
491
492
# File 'app/services/item/heating_element_plan.rb', line 484

def number_of_circuits
  circuits = 0
  if integration_kits.sum(:quantity).positive?
    circuits = integration_kits.sum(:quantity)
  else
    electrical_plan_controls.each { |c| circuits += (c.item.spec_value(:circuits_required).to_i * c.quantity) }
  end
  circuits
end

#ot_sensorsObject



463
464
465
# File 'app/services/item/heating_element_plan.rb', line 463

def ot_sensors
  line_items_for_sku(['SCP-120', *ItemConstants::POWER_MODULATORS])
end

#other_items_tableObject



373
374
375
376
377
378
379
380
381
382
383
384
# File 'app/services/item/heating_element_plan.rb', line 373

def other_items_table
  oi_table = []
  other_items.each do |line_item|
    item_name = begin
      "#{line_item.item.sku} - #{line_item.item.name}"
    rescue StandardError
      'n/a'
    end
    oi_table << [item_name, line_item.quantity]
  end
  oi_table
end

#power_kitsObject



455
456
457
# File 'app/services/item/heating_element_plan.rb', line 455

def power_kits
  line_items_for_sku('SR-PWR-KIT')
end

#power_modulesObject

Alias for All_line_items#power_modules

Returns:

  • (Object)

    All_line_items#power_modules

See Also:



435
# File 'app/services/item/heating_element_plan.rb', line 435

delegate :power_modules, to: :all_line_items

#relaysObject



443
444
445
# File 'app/services/item/heating_element_plan.rb', line 443

def relays
  all_line_items.relay_panels
end

#requires_breaker_size_vs_circuit_length_table?Boolean

Returns:

  • (Boolean)


532
533
534
535
536
537
538
# File 'app/services/item/heating_element_plan.rb', line 532

def requires_breaker_size_vs_circuit_length_table?
  if heating_system.match(/roof|pipe/i) && (has_item?(ItemConstants::PIPE_TRACING_CABLE_5_WATT_PER_FT_SPOOL_SKUS) || has_item?(ItemConstants::PIPE_TRACING_CABLE_8_WATT_PER_FT_SPOOL_SKUS) || has_item?(ItemConstants::PIPE_TRACING_CABLE_10_WATT_PER_FT_SPOOL_SKUS))
    return true
  end

  false
end

#roof_and_gutter_breaker_recommendations(volts) ⇒ Object



524
525
526
527
528
529
530
# File 'app/services/item/heating_element_plan.rb', line 524

def roof_and_gutter_breaker_recommendations(volts)
  if volts == 120
    "<table class='rg-breakers'><tr><th>Max Length</th><th>Breaker</th></tr><tr><td>107 ft</td><td>15 A 120V GFEP</td></tr><tr><td>142 ft</td><td>20 A 120V GFEP</td></tr><tr><td>214 ft</td><td>30 A 120V GFEP</td></tr></table>"
  elsif volts == 240
    "<table class='rg-breakers'><tr><th>Max Length</th><th>Breaker</th></tr><tr><td>214 ft</td><td>15 A 240V GFEP</td></tr><tr><td>286 ft</td><td>20 A 240V GFEP</td></tr><tr><td>429 ft</td><td>30 A 240V GFEP</td></tr></table>"
  end
end

#roof_and_gutter_breaker_size(volts, length) ⇒ Object



504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
# File 'app/services/item/heating_element_plan.rb', line 504

def roof_and_gutter_breaker_size(volts, length)
  # length will be in inches, so lets convert it to feet
  length /= 12
  if volts == 120
    case length
    when 0..107 then 15
    when 108..142 then 20
    else 30
    end
  elsif volts == 240
    case length
    when 0..214 then 15
    when 215..286 then 20
    else 30
    end
  else # not set up for that voltage, so just give the max
    30
  end
end

#room_labelObject



600
601
602
# File 'app/services/item/heating_element_plan.rb', line 600

def room_label
  is_outdoor? ? 'Area' : 'Room'
end

#sensorsObject



476
477
478
# File 'app/services/item/heating_element_plan.rb', line 476

def sensors
  all_line_items.has_floor_sensor
end

#shorten_text(text, num_chars = 32) ⇒ Object



855
856
857
858
859
# File 'app/services/item/heating_element_plan.rb', line 855

def shorten_text(text, num_chars = 32)
  res = text
  res = "#{text.to_s[0..(num_chars - 4)]} ..." if text.to_s.length > num_chars
  res
end

#slab_heat_cable_watts_per_sqftObject



183
184
185
186
187
188
189
190
191
192
# File 'app/services/item/heating_element_plan.rb', line 183

def slab_heat_cable_watts_per_sqft
  {
    '3': 23,
    '3.0': 23,
    '4': 17,
    '4.0': 17,
    '5': 15,
    '5.0': 15
  }.fetch((cable_spacing || 3.0).round(2).to_s.to_sym, 23)
end

#snow_melt_cable_watts_per_sqftObject



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'app/services/item/heating_element_plan.rb', line 167

def snow_melt_cable_watts_per_sqft
  {
    '2.5': 'Up to 60',
    '3': 'Up to 50',
    '3.0': 'Up to 50',
    '3.5': 'Up to 43',
    '4': 'Up to 38.5',
    '4.0': 'Up to 38.5',
    '4.5': 'Up to 33',
    '5': 'Up to 31.5',
    '5.0': 'Up to 31.5',
    '6': 'Up to 25',
    '6.0': 'Up to 25'
  }.fetch((cable_spacing || 3.0).round(2).to_s.to_sym, 50)
end

#special_instructionsObject

TODO:

Is this needed? We already put that in the instructions if an AIR-SS is present.



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'app/services/item/heating_element_plan.rb', line 247

def special_instructions
  instructions = []
  if ot_sensors.present?
    instructions << "* Installers may locate the OT Sensor at their discretion.
    Placement shown is a recommendation only. Sensor must be inside metal conduit
    (capped off for replaceability) between two passes of heat cable."
  end
  if has_item?('SLAB-SS') || has_item?('SCE-SLAB-SS-56')
    instructions << "# Installers may locate Slab Sensor at their discretion.
    Placement shown is a recommendation only. Sensor should be located between
    two passes of Heating Cable where snow and ice problems normally occur.
    Sensor must be embedded horizontally with its top flush with the surroundings
    with the help of the accompanying installation plate."
  end
  instructions
end

#tempzone_thick_cable_watts_per_sqftObject



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'app/services/item/heating_element_plan.rb', line 150

def tempzone_thick_cable_watts_per_sqft
  {
    '2.5': 17.76,
    '2.9': 15.3,
    '3': 14.8,
    '3.0': 14.8,
    '3.13': 14.2,
    '3.5': 12.7,
    '3.75': 11.84,
    '4': 11.1,
    '4.0': 11.1,
    '4.75': 9.3,
    '5': 8.9,
    '5.0': 8.9
  }.fetch((cable_spacing || 3.0).round(2).to_s.to_sym, 14.8)
end

#tempzone_thin_cable_watts_per_sqftObject



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'app/services/item/heating_element_plan.rb', line 133

def tempzone_thin_cable_watts_per_sqft
  {
    '2.5': 14.4,
    '2.9': 12.4,
    '3': 12,
    '3.0': 12,
    '3.13': 11.5,
    '3.5': 10.3,
    '3.75': 9.6,
    '4': 8.7,
    '4.0': 8.7,
    '4.75': 7.6,
    '5': 7.2,
    '5.0': 7.2
  }.fetch((cable_spacing || 3.0).round(2).to_s.to_sym, 12)
end

#thermostatsObject

Alias for All_line_items#thermostats

Returns:

  • (Object)

    All_line_items#thermostats

See Also:



439
# File 'app/services/item/heating_element_plan.rb', line 439

delegate :thermostats, to: :all_line_items

#total_ampsObject



423
424
425
# File 'app/services/item/heating_element_plan.rb', line 423

def total_amps
  heating_elements_lines.to_a.sum { |he| he.amps || 0 }.round(2)
end

#total_btu_per_hrObject



399
400
401
# File 'app/services/item/heating_element_plan.rb', line 399

def total_btu_per_hr
  heating_elements_lines.to_a.sum { |he| he.btu_per_hr || 0 }.round
end

#total_lengthObject



427
428
429
# File 'app/services/item/heating_element_plan.rb', line 427

def total_length
  heating_elements_lines.to_a.sum { |he| he.length || 0 }
end

#total_wattsObject



386
387
388
389
390
391
392
393
394
395
396
397
# File 'app/services/item/heating_element_plan.rb', line 386

def total_watts
  heating_elements_lines.to_a.sum do |he|
    value = if he.respond_to?(:watts)
              he.watts
            elsif he.is_a?(Hash)
              he[:watts] || he['watts']
            else
              0
            end
    (value || 0).to_f
  end.round
end

#watts_per_sqft(watts_only = false) ⇒ Object



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'app/services/item/heating_element_plan.rb', line 75

def watts_per_sqft(watts_only = false)
  watts = case heating_system
          when /TempZone Flex Roll|TempZone Shower Mat|TempZone Easy Mat|Shower Mat Bench/i
            15
          when 'TempZone Custom Mat'
            watts_only ? 12.5 : '12-15'
          when /Environ Flex Roll|Environ Easy Mat/i
            12
          when 'Snow Melt Mat'
            if cable_spacing.to_f.round == 4
              'Up to 38.5'
            else
              (cable_spacing.to_f.round == 5 ? 'Up to 31.5' : 'Up to 50')
            end
          when 'Snow Melt PowerMat'
            'Up to 50'
          when 'Snow Melt OmniMat'
            'Up to 38.5'
          when 'Snow Melt EcoMat'
            'Up to 31.5'
          when 'Slab Heat Mat'
            20
          when 'TempZone Thin Cable'
            tempzone_thin_cable_watts_per_sqft
          when 'TempZone Cable'
            tempzone_thick_cable_watts_per_sqft
          when 'TempZone Ruler Cable'
            tempzone_thick_cable_watts_per_sqft
          when 'Snow Melt Cable'
            snow_melt_cable_watts_per_sqft
          when 'Slab Heat Cable'
            slab_heat_cable_watts_per_sqft
          when /roof/i
            if watts_only
              heating_system_voltage == 208 ? 6.5 : 7.5
            else
              heating_system_voltage == 208 ? '4-9' : '5-10'
            end
          when /pipe/i
            if has_item?(ItemConstants::PIPE_TRACING_CABLE_5_WATT_PER_FT_SPOOL_SKUS)
              5
            elsif has_item?(ItemConstants::PIPE_TRACING_CABLE_8_WATT_PER_FT_SPOOL_SKUS)
              8
            elsif has_item?(ItemConstants::PIPE_TRACING_CABLE_10_WATT_PER_FT_SPOOL_SKUS)
              10
            end
          end
  return watts if watts_only || watts.nil?

  text = if /roof|pipe/i.match?(heating_system)
           "#{watts} W/Ft."
         else
           "#{watts} W/Sq.ft."
         end
  text += " @ #{cable_spacing}\" spacing" if !is_environ? && cable_spacing
  text
end