diff --git a/app/models/formula.rb b/app/models/formula.rb index 341a8be..cd82a37 100644 --- a/app/models/formula.rb +++ b/app/models/formula.rb @@ -17,6 +17,66 @@ class Formula < ActiveRecord::Base end end + def self.resolve(quantities, items, subitems) + unchecked_q = quantities.map { |q| [q, nil] } + + # TODO: jesli wartosci nie ma w subitems to pytac w yield (jesli jest block) + completed_q = {} + # FIXME: loop should finish unless there is circular dependency in formulas + # for now we don't guard against that + while !unchecked_q.empty? + q, deps = unchecked_q.shift + + # quantity not computable: no formula/invalid formula (syntax error/runtime error) + # or not requiring calculation/computed + if !q.formula || q.formula.errors.any? || !q.formula.valid? || + (subitems[q].length == items.count) + completed_q[q] = subitems.delete(q) { {} } + next + end + + # quantity with formula requires refresh of dependencies availability + if deps.nil? || !deps.empty? + deps ||= q.formula.quantities.clone + deps.reject! { |d| completed_q.has_key?(d) } + deps.each { |d| unchecked_q << [d, nil] unless unchecked_q.index { |u| u[0] == d } } + end + + # quantity with formula has all dependencies satisfied, requires calculation + if deps.empty? + output_items = items.select { |i| subitems[q][i].nil? } + input_q = q.formula.quantities + inputs = input_q.map do |i_q| + values = completed_q[i_q].values_at(*output_items).map { |v| v || [nil, nil] } + values.map! { |v, u| [v || BigDecimal(0), u] } if q.formula.zero_nil + [i_q, values] + end + begin + calculated = q.formula.calculate(inputs.to_h) + rescue Exception => e + output_items.each { |o_i| subitems[q][o_i] = nil } + q.formula.errors.add( + :code, :computation_failed, + { + quantity: q.name, + description: e.message, + count: output_items.size == subitems[q].size ? 'all' : output_items.size + } + ) + else + output_items.each_with_index { |o_i, idx| subitems[q][o_i] = calculated[idx] } + end + unchecked_q.unshift([q, deps]) + next + end + + # quantity still has unsatisfied dependencies, move to the end of queue + unchecked_q << [q, deps] + end + + completed_q + end + def calculate(inputs) raise(InvalidInputs, 'No inputs') if inputs.empty? diff --git a/lib/body_tracking/items_with_quantities.rb b/lib/body_tracking/items_with_quantities.rb index 4ca54bf..5b5a092 100644 --- a/lib/body_tracking/items_with_quantities.rb +++ b/lib/body_tracking/items_with_quantities.rb @@ -52,9 +52,6 @@ module BodyTracking def compute_quantities(requested_q, filter_q = nil) items = all - requested_q ||= Quantity.none - unchecked_q = requested_q.map { |q| [q, nil] } - unchecked_q << [filter_q, nil] if filter_q relations = RELATIONS[proxy_association.klass.name] subitems = Hash.new { |h,k| h[k] = {} } @@ -66,58 +63,8 @@ module BodyTracking subitems[s.quantity][item] = [subitem_value, s.unit] end - completed_q = {} - # FIXME: loop should finish unless there is circular dependency in formulas - # for now we don't guard against that - while !unchecked_q.empty? - q, deps = unchecked_q.shift - - # quantity not computable: no formula/invalid formula (syntax error/runtime error) - # or not requiring calculation/computed - if !q.formula || q.formula.errors.any? || !q.formula.valid? || - (subitems[q].length == items.count) - completed_q[q] = subitems.delete(q) { {} } - next - end - - # quantity with formula requires refresh of dependencies availability - if deps.nil? || !deps.empty? - deps ||= q.formula.quantities.clone - deps.reject! { |d| completed_q.has_key?(d) } - deps.each { |d| unchecked_q << [d, nil] unless unchecked_q.index { |u| u[0] == d } } - end - - # quantity with formula has all dependencies satisfied, requires calculation - if deps.empty? - output_items = items.select { |i| subitems[q][i].nil? } - input_q = q.formula.quantities - inputs = input_q.map do |i_q| - values = completed_q[i_q].values_at(*output_items).map { |v| v || [nil, nil] } - values.map! { |v, u| [v || BigDecimal(0), u] } if q.formula.zero_nil - [i_q, values] - end - begin - calculated = q.formula.calculate(inputs.to_h) - rescue Exception => e - output_items.each { |o_i| subitems[q][o_i] = nil } - q.formula.errors.add( - :code, :computation_failed, - { - quantity: q.name, - description: e.message, - count: output_items.size == subitems[q].size ? 'all' : output_items.size - } - ) - else - output_items.each_with_index { |o_i, idx| subitems[q][o_i] = calculated[idx] } - end - unchecked_q.unshift([q, deps]) - next - end - - # quantity still has unsatisfied dependencies, move to the end of queue - unchecked_q << [q, deps] - end + quantities = (requested_q || Quantity.none) + Array(filter_q) + completed_q = Formula.resolve(quantities, items, subitems) filter_values = completed_q.delete(filter_q) items.to_a.keep_if { |i| filter_values[i][0] } if filter_values