module NutrientVector extend ActiveSupport::Concern NUTRIENT_KEYS = %i[ nno3 p k ca mg s na cl si fe zn b mn cu mo nnh4 ].freeze included do # Define simple readers (nno3, p, k, ...) that read from #nutrient_values NUTRIENT_KEYS.each do |k| define_method(k) { nutrient_values[k] } end end # Hash-like access def [](key) key = key.to_sym return nil unless NUTRIENT_KEYS.include?(key) public_send(key) end def keys = NUTRIENT_KEYS # Returns a copy to avoid accidental mutation def to_h NUTRIENT_KEYS.index_with { |k| public_send(k) } end # Iterate over pairs def each_pair return enum_for(:each_pair) unless block_given? NUTRIENT_KEYS.each { |k| yield k, public_send(k) } end # Simple difference (self - other), useful for “how far from target?” def delta_against(other) NUTRIENT_KEYS.index_with { |k| (public_send(k).to_f) - (other.public_send(k).to_f) } end # Percent difference relative to other (e.g., measurement vs target) # Returns 0 when both are 0, and nil when target is 0 but measurement isn’t. def percent_diff_against(other) NUTRIENT_KEYS.index_with do |k| a = public_send(k).to_f b = other.public_send(k).to_f if b.zero? && a.zero? 0.0 elsif b.zero? nil else ((a - b) / b) * 100.0 end end end end