summaryrefslogtreecommitdiff
path: root/app/services/fertilizer_recipe_calculator.rb
diff options
context:
space:
mode:
Diffstat (limited to 'app/services/fertilizer_recipe_calculator.rb')
-rw-r--r--app/services/fertilizer_recipe_calculator.rb136
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
Copyright 2019--2025 Marius PETER