diff options
Diffstat (limited to 'app/services/fertilizer_recipe_calculator.rb')
-rw-r--r-- | app/services/fertilizer_recipe_calculator.rb | 136 |
1 files changed, 0 insertions, 136 deletions
diff --git a/app/services/fertilizer_recipe_calculator.rb b/app/services/fertilizer_recipe_calculator.rb deleted file mode 100644 index 28ba8aa..0000000 --- a/app/services/fertilizer_recipe_calculator.rb +++ /dev/null @@ -1,136 +0,0 @@ -class FertilizerRecipeCalculator - NUTRIENTS = %i[nno3 p k ca mg s nnh4].freeze - - def self.call(latest, target, water_volume_l: 100_000) - new(latest, target, water_volume_l).call - end - - def initialize(latest, target, water_volume_l) - @latest = latest || NutrientMeasurement.new - @target = target || NutrientMeasurement.new - @vol_l = water_volume_l.to_f - end - - # Returns a Hash { FertilizerComponent => qty_kg } - def call - deltas_mg_per_l = NUTRIENTS.index_with do |k| - [ (value(@target, k) - value(@latest, k)), 0 ].max - end - - # mg/L * L = mg -> kg - needed_kg = deltas_mg_per_l.transform_values { |mg_l| mg_l * @vol_l / 1_000_000.0 } - - recipe = Hash.new(0.0) - - # 1) P via MAP or DAP (accounts NH4-N) - if (p_need = needed_kg[:p]) > 0 - map = fc_by_formula("MAP") || fc_by_name_like("monoammonium phosphate") - dap = fc_by_formula("DAP") || fc_by_name_like("diammonium phosphate") - carrier = [ map, dap ].compact.find { |c| positive?(c.p) } - if carrier - kg = kg_for(p_need, carrier.p) - recipe[carrier] += kg - needed_kg[:nnh4] = [ needed_kg[:nnh4] - kg * pct(carrier.nnh4), 0 ].max if positive?(carrier.nnh4) - needed_kg[:p] = 0 - end - end - - # 2) NO3-N via KNO3 then Ca(NO3)2 (accounts K/Ca) - if (n_need = needed_kg[:nno3]) > 0 - kno3 = fc_by_formula("KNO3") || fc_by_name_like("potassium nitrate") - can = fc_by_formula("Ca(NO3)2") || fc_by_name_like("calcium nitrate") - - if kno3&.nno3.to_f > 0 - kg = kg_for(n_need, kno3.nno3) - recipe[kno3] += kg - needed_kg[:k] = [ needed_kg[:k] - kg * pct(kno3.k), 0 ].max - n_need -= kg * pct(kno3.nno3) - end - - if n_need > 0 && can&.nno3.to_f > 0 - kg = kg_for(n_need, can.nno3) - recipe[can] += kg - needed_kg[:ca] = [ needed_kg[:ca] - kg * pct(can.ca), 0 ].max - n_need = 0 - end - - needed_kg[:nno3] = [ n_need, 0 ].max - end - - # 3) K via K2SO4 (accounts S) - if (k_need = needed_kg[:k]) > 0 - sop = fc_by_formula("K2SO4") || fc_by_name_like("potassium sulfate") - if sop&.k.to_f > 0 - kg = kg_for(k_need, sop.k) - recipe[sop] += kg - needed_kg[:s] = [ needed_kg[:s] - kg * pct(sop.s), 0 ].max - needed_kg[:k] = 0 - end - end - - # 4) Ca via Ca(NO3)2 (accounts NO3-N) - if (ca_need = needed_kg[:ca]) > 0 - can = recipe.keys.find { |c| norm_formula(c) == "ca(no3)2" } || - fc_by_formula("Ca(NO3)2") || fc_by_name_like("calcium nitrate") - if can&.ca.to_f > 0 - kg = kg_for(ca_need, can.ca) - recipe[can] += kg - needed_kg[:nno3] = [ needed_kg[:nno3] - kg * pct(can.nno3), 0 ].max - needed_kg[:ca] = 0 - end - end - - # 5) Mg via MgSO4 (accounts S) - if (mg_need = needed_kg[:mg]) > 0 - mgs = fc_by_formula("MgSO4") || fc_by_name_like("magnesium sulfate") - if mgs&.mg.to_f > 0 - kg = kg_for(mg_need, mgs.mg) - recipe[mgs] += kg - needed_kg[:s] = [ needed_kg[:s] - kg * pct(mgs.s), 0 ].max - needed_kg[:mg] = 0 - end - end - - # 6) S via K2SO4 or MgSO4 (whichever we already used or find) - if (s_need = needed_kg[:s]) > 0 - sop = recipe.keys.find { |c| norm_formula(c) == "k2so4" } || - fc_by_formula("K2SO4") || fc_by_name_like("potassium sulfate") - mgs = recipe.keys.find { |c| norm_formula(c) == "mgso4" } || - fc_by_formula("MgSO4") || fc_by_name_like("magnesium sulfate") - carrier = [ sop, mgs ].compact.find { |c| positive?(c.s) } - if carrier - kg = kg_for(s_need, carrier.s) - recipe[carrier] += kg - needed_kg[:k] = [ needed_kg[:k] - kg * pct(carrier.k), 0 ].max if positive?(carrier.k) - needed_kg[:mg] = [ needed_kg[:mg] - kg * pct(carrier.mg), 0 ].max if positive?(carrier.mg) - needed_kg[:s] = 0 - end - end - - recipe.delete_if { |_c, kg| kg < 0.01 } - recipe - end - - private - - def value(obj, key) = obj.public_send(key).to_f - - def fc_by_formula(formula) - FertilizerComponent.where("LOWER(formula) = ?", formula.to_s.downcase).first - end - - def fc_by_name_like(name) - FertilizerComponent.where("LOWER(name) LIKE ?", "%#{name.downcase}%").first - end - - def kg_for(need_kg_element, percent_in_product) - return 0.0 unless positive?(percent_in_product) - need_kg_element / pct(percent_in_product) - end - - def pct(v) = v.to_f / 100.0 - - def positive?(v) = v.to_f > 0.0 - - def norm_formula(c) = c.formula.to_s.downcase.gsub(/\s+/, "") -end |