From 98564be4b5025aa556a9639aaab737778ea7e897 Mon Sep 17 00:00:00 2001 From: cryptogopher Date: Tue, 10 Dec 2019 20:37:36 +0100 Subject: [PATCH] Displaying Measurement readouts, WIP --- app/controllers/ingredients_controller.rb | 4 +- app/controllers/measurements_controller.rb | 35 ++++++++- app/helpers/measurements_helper.rb | 8 ++ app/models/measurement.rb | 88 ++++++++++++++++++++++ app/views/measurements/_form.html.erb | 4 +- app/views/measurements/_index.html.erb | 4 +- app/views/measurements/_readouts.html.erb | 68 +++++++++++++++++ 7 files changed, 200 insertions(+), 11 deletions(-) create mode 100644 app/views/measurements/_readouts.html.erb diff --git a/app/controllers/ingredients_controller.rb b/app/controllers/ingredients_controller.rb index 64735da..85cf9b9 100644 --- a/app/controllers/ingredients_controller.rb +++ b/app/controllers/ingredients_controller.rb @@ -71,7 +71,7 @@ class IngredientsController < ApplicationController warnings = [] if params.has_key?(:file) - quantities = @project.quantities.map { |q| [q.name, q] }.to_h + quantities = @project.quantities.diet.map { |q| [q.name, q] }.to_h units = @project.units.map { |u| [u.shortname, u] }.to_h sources = @project.sources.map { |s| [s.name, s] }.to_h ingredients_params = [] @@ -207,7 +207,7 @@ class IngredientsController < ApplicationController end def prepare_nutrients - @quantities = @project.quantities.where(primary: true) + @quantities = @project.quantities.diet.where(primary: true) ingredients, requested_n, extra_n, @formula_q = @project.ingredients .filter(@project, session[:i_filters], @quantities) diff --git a/app/controllers/measurements_controller.rb b/app/controllers/measurements_controller.rb index d3adb1e..27c9bc8 100644 --- a/app/controllers/measurements_controller.rb +++ b/app/controllers/measurements_controller.rb @@ -1,12 +1,13 @@ class MeasurementsController < ApplicationController menu_item :body_trackers + before_action :init_session_filters before_action :find_project_by_project_id, only: [:index, :new, :create] before_action :find_measurement, only: [:edit, :update, :destroy, :retake, :readouts] before_action :authorize def index - session[:m_scope] = {} + session[:m_filters][:scope] = {} prepare_measurements end @@ -55,12 +56,16 @@ class MeasurementsController < ApplicationController end def readouts - session[:m_scope] = {name: @measurement.name} - prepare_measurements + session[:m_filters][:scope] = {name: @measurement.name} + prepare_readouts end private + def init_session_filters + session[:m_filters] ||= {} + end + def measurement_params params.require(:measurement).permit( :name, @@ -87,6 +92,28 @@ class MeasurementsController < ApplicationController def prepare_measurements @measurements = @project.measurements.includes(:source, :readouts) - .where(session[:m_scope]) + .filter(session[:m_filters]) + end + + def prepare_readouts + @quantities = @project.quantities.measurement.where(primary: true) + measurements, requested_r, extra_r, @formula_q = @project.measurements.includes(:source) + .filter(@project, session[:i_filters], @quantities) + + @nutrients = {} + @extra_nutrients = {} + ingredients.each_with_index do |i, index| + @nutrients[i] = [] + requested_n[index].each do |q_name, value| + amount, unitname = value + @nutrients[i] << [q_name, amount.nil? ? '-' : "#{amount} [#{unitname || '-'}]"] + end + + @extra_nutrients[i] = [] + extra_n[index].each do |q_name, value| + amount, unitname = value + @extra_nutrients[i] << [q_name, amount.nil? ? '-' : "#{amount} [#{unitname || '-'}]"] + end + end end end diff --git a/app/helpers/measurements_helper.rb b/app/helpers/measurements_helper.rb index e13348e..7498437 100644 --- a/app/helpers/measurements_helper.rb +++ b/app/helpers/measurements_helper.rb @@ -1,4 +1,12 @@ module MeasurementsHelper + def format_datetime(m) + m.taken_at.getlocal.strftime("%F %R").html_safe + end + + def format_time(m) + m.taken_at.getlocal.strftime("%R") + end + def quantity_options nested_set_options(@project.quantities.measurement) do |q| raw("#{' ' * q.level}#{q.name}") diff --git a/app/models/measurement.rb b/app/models/measurement.rb index 2501968..36b2790 100644 --- a/app/models/measurement.rb +++ b/app/models/measurement.rb @@ -45,4 +45,92 @@ class Measurement < ActiveRecord::Base def taken_at_time=(value) self.taken_at = Time.parse(value, self.taken_at) end + + def self.filter(filters, requested_q = Quantity.none) + measurements = all.where(filters[:scope]) + + if filters[:name].present? + measurements = measurements.where("name LIKE ?", "%#{filters[:name]}%") + end + + project = proxy_association.owner + formula_q = if filters[:readouts].present? + project.quantities.new(name: '__internal_q', + formula: filters[:readouts], + domain: :measurement) + end + apply_formula = formula_q.present? && formula_q.valid? + + result = + if !requested_q.empty? || apply_formula + computed = measurements.compute_nutrients(requested_q, apply_formula && formula_q) + requested_q.present? ? computed : [computed[0]] + else + [measurements] + end + result.push(formula_q) + end + + def self.compute_nutrients(requested_q, filter_q = nil) + ingredients = all + unchecked_q = requested_q.map { |q| [q, nil] } + unchecked_q << [filter_q, nil] if filter_q + + nutrients = Hash.new { |h,k| h[k] = {} } + Nutrient.where(ingredient: ingredients).includes(:quantity, :unit) + .order('quantities.lft') + .pluck('quantities.name', :ingredient_id, :amount, 'units.shortname') + .each { |q_name, i_id, a, u_id| nutrients[q_name][i_id] = [a, u_id] } + + extra_q = nutrients.keys - requested_q.pluck(:name) + + 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) or not requiring calculation/computed + if q.formula.blank? || (nutrients[q.name].length == ingredients.count) + completed_q[q.name] = nutrients.delete(q.name) { {} } + next + end + + # quantity with formula requires refresh of dependencies availability + if deps.nil? || !deps.empty? + deps ||= q.formula_quantities + deps.reject! { |q| completed_q.has_key?(q.name) } + deps.each { |q| unchecked_q << [q, nil] unless unchecked_q.index { |u| u[0] == q } } + end + + # quantity with formula has all dependencies satisfied, requires calculation + if deps.empty? + input_q = q.formula_quantities + inputs = ingredients.select { |i| nutrients[q.name][i.id].nil? }.map do |i| + [ + i, + input_q.map do |i_q| + nutrient_data = completed_q[i_q.name][i.id] || [nil, nil] + [i_q.name, nutrient_data[0]] + end.to_h + ] + end + q.calculate(inputs).each { |i, result| nutrients[q.name][i.id] = result } + unchecked_q.unshift([q, deps]) + next + end + + # quantity still has unsatisfied dependencies, move to the end of queue + unchecked_q << [q, deps] + end + + all_q = nutrients.merge(completed_q) + [ + filter_q ? ingredients.to_a.keep_if { |i| all_q[filter_q.name][i.id][0] } : ingredients, + ingredients.map { |i| requested_q.map { |q| [q.name, all_q[q.name][i.id]] } }, + ingredients.map do |i| + extra_q.map { |q_name| [q_name, all_q[q_name][i.id]] if all_q[q_name][i.id] } + end + ] + end end diff --git a/app/views/measurements/_form.html.erb b/app/views/measurements/_form.html.erb index 647777e..6d3a3ad 100644 --- a/app/views/measurements/_form.html.erb +++ b/app/views/measurements/_form.html.erb @@ -6,8 +6,8 @@

<%= f.text_field :name, size: 40, required: true %>

<%= f.date_field :taken_at_date, required: true %> - <%= f.time_field :taken_at_time, value: @measurement.taken_at.getlocal.strftime("%R"), - required: true, label: '' %> + <%= f.time_field :taken_at_time, value: format_time(@measurement), required: true, + label: '' %>

<% @measurement.readouts.each_with_index do |r, index| %> <%= f.fields_for 'readouts_attributes', r, index: '' do |ff| %> diff --git a/app/views/measurements/_index.html.erb b/app/views/measurements/_index.html.erb index 5ce6b63..eb8c1e8 100644 --- a/app/views/measurements/_index.html.erb +++ b/app/views/measurements/_index.html.erb @@ -12,9 +12,7 @@ <% @measurements.each do |m| %> <% next if m.new_record? %> - - <%= m.taken_at.getlocal.strftime("%F %R").html_safe %> - + <%= format_datetime(m) %>
<%= link_to m.name, readouts_measurement_path(m) %> diff --git a/app/views/measurements/_readouts.html.erb b/app/views/measurements/_readouts.html.erb new file mode 100644 index 0000000..fc2ce48 --- /dev/null +++ b/app/views/measurements/_readouts.html.erb @@ -0,0 +1,68 @@ +<% if @readouts.any? %> + <%= render partial: 'measurements/options' %> + + + + + <% total_width = 3 + @quantities.length %> + + <% @quantities.each do |q| %> + + <% end %> + + + + <% @readouts.each do |m, values| %> + <% row_class = "measurement #{cycle('odd', 'even')}" %> + + + <% values.each do |*, value| %> + + <% end %> + + + + + <% values.each do |q_name, *| %> + + <% end %> + + + + <% values.each do |*, value| %> + + <% end %> + + + <% extras = @extra_readouts[i] %> + <% extras.each_slice(@quantities.length).with_index do |values, index| %> + + + <% values.each do |q_name, *| %> + + <% end %> + <% if @quantities.length > values.length %> + + <% end %> + + + + <% values.each do |*, value| %> + + <% end %> + <% if @quantities.length > values.length %> + + <% end %> + + <% end %> + + <% end %> + +
<%= l(:field_name) %><%= q.name %>
+ <%= format_datetime(m) %> + <%= value %>
+<% else %> +

<%= l(:label_no_data) %>

+<% end %>