mirror of
https://github.com/Growstuff/growstuff.git
synced 2026-03-28 11:41:31 -04:00
Merge pull request #2286 from Br3nda/predict-perenial-harvest
Predict month of harvest on perennials [EDIT: show nearby harvesting months for all crops]
This commit is contained in:
@@ -29,14 +29,8 @@ before_install:
|
||||
# - sudo apt update
|
||||
- sudo apt install dpkg
|
||||
- sudo apt install google-chrome-stable
|
||||
- ./script/install_codeclimate.sh
|
||||
- ./script/install_linters.sh
|
||||
- ./script/install_elasticsearch.sh
|
||||
before_script:
|
||||
- >
|
||||
if [ "${COVERAGE}" = "true" ]; then
|
||||
./cc-test-reporter before-build
|
||||
fi
|
||||
script:
|
||||
- set -e
|
||||
- >
|
||||
@@ -52,7 +46,8 @@ script:
|
||||
after_script:
|
||||
- >
|
||||
if [ "${COVERAGE}" = "true" ]; then
|
||||
./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT;
|
||||
gem install codeclimate-test-reporter
|
||||
codeclimate-test-reporter
|
||||
fi
|
||||
- >
|
||||
if [ "${PERCY}" = "true" ]; then
|
||||
|
||||
@@ -33,6 +33,9 @@ class PlantingsController < ApplicationController
|
||||
def show
|
||||
@photos = @planting.photos.includes(:owner).order(date_taken: :desc)
|
||||
@matching_seeds = matching_seeds
|
||||
@neighbours = @planting.nearby_same_crop
|
||||
.where.not(id: @planting.id)
|
||||
.limit(6)
|
||||
respond_with @planting
|
||||
end
|
||||
|
||||
|
||||
@@ -54,6 +54,27 @@ module PredictHarvest
|
||||
first_harvest_predicted_at > Time.zone.today
|
||||
end
|
||||
|
||||
def harvest_months
|
||||
Rails.cache.fetch("#{cache_key_with_version}/harvest_months", expires_in: 5.minutes) do
|
||||
neighbours_for_harvest_predictions.where.not(harvested_at: nil)
|
||||
.group("extract(MONTH from harvested_at)::int")
|
||||
.count
|
||||
end
|
||||
end
|
||||
|
||||
def neighbours_for_harvest_predictions
|
||||
# use this planting's harvest if any
|
||||
return harvests if harvests.size.positive?
|
||||
|
||||
# otherwise use nearby plantings
|
||||
if location
|
||||
return Harvest.where(planting: nearby_same_crop.has_harvests)
|
||||
.where.not(planting_id: nil)
|
||||
end
|
||||
|
||||
Harvest.none
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def harvests_with_dates
|
||||
|
||||
@@ -34,6 +34,11 @@ class Garden < ApplicationRecord
|
||||
numericality: { only_integer: false, greater_than_or_equal_to: 0 },
|
||||
allow_nil: true
|
||||
|
||||
scope :located, lambda {
|
||||
where.not(gardens: { location: '' })
|
||||
.where.not(gardens: { latitude: nil })
|
||||
.where.not(gardens: { longitude: nil })
|
||||
}
|
||||
AREA_UNITS_VALUES = {
|
||||
"square metres" => "square metre",
|
||||
"square feet" => "square foot",
|
||||
|
||||
@@ -32,11 +32,18 @@ class Planting < ApplicationRecord
|
||||
|
||||
##
|
||||
## Scopes
|
||||
scope :located, lambda {
|
||||
joins(:garden)
|
||||
.where.not(gardens: { location: '' })
|
||||
.where.not(gardens: { latitude: nil })
|
||||
.where.not(gardens: { longitude: nil })
|
||||
}
|
||||
scope :active, -> { where('finished <> true').where('finished_at IS NULL OR finished_at < ?', Time.zone.now) }
|
||||
scope :annual, -> { joins(:crop).where(crops: { perennial: false }) }
|
||||
scope :perennial, -> { joins(:crop).where(crops: { perennial: true }) }
|
||||
scope :interesting, -> { has_photos.one_per_owner.order(planted_at: :desc) }
|
||||
scope :recent, -> { order(created_at: :desc) }
|
||||
scope :has_harvests, -> { where('plantings.harvests_count > 0') }
|
||||
scope :one_per_owner, lambda {
|
||||
joins("JOIN members m ON (m.id=plantings.owner_id)
|
||||
LEFT OUTER JOIN plantings p2
|
||||
@@ -48,7 +55,7 @@ class Planting < ApplicationRecord
|
||||
delegate :name, :slug, :en_wikipedia_url, :default_scientific_name, :plantings_count,
|
||||
to: :crop, prefix: true
|
||||
|
||||
delegate :annual?, :svg_icon, to: :crop
|
||||
delegate :annual?, :perennial?, :svg_icon, to: :crop
|
||||
delegate :location, :longitude, :latitude, to: :garden
|
||||
|
||||
##
|
||||
@@ -96,6 +103,17 @@ class Planting < ApplicationRecord
|
||||
planted? && !finished?
|
||||
end
|
||||
|
||||
def nearby_same_crop
|
||||
return Planting.none if location.blank?
|
||||
|
||||
# latitude, longitude = Geocoder.coordinates(location, params: { limit: 1 })
|
||||
Planting.joins(:garden)
|
||||
.where(crop: crop)
|
||||
.located
|
||||
.where('gardens.latitude < ? AND gardens.latitude > ?',
|
||||
latitude + 10, latitude - 10)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# check that any finished_at date occurs after planted_at
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
%p
|
||||
Planted in
|
||||
= link_to @planting.garden, @planting.garden
|
||||
- if @planting.owner.location
|
||||
- if @planting.garden.location
|
||||
%p
|
||||
%small
|
||||
View other plantings, members and more near
|
||||
= link_to @planting.owner.location, place_path(@planting.owner.location, anchor: "plantings")
|
||||
= link_to @planting.garden.location, place_path(@planting.garden.location, anchor: "plantings")
|
||||
.col= render "members/avatar", member: @planting.owner
|
||||
@@ -1,23 +1,39 @@
|
||||
- if @planting.crop.annual?
|
||||
- if planting.annual?
|
||||
.d-flex.justify-content-between
|
||||
- if @planting.planted_at.present?
|
||||
%p.small #{ image_icon 'planting-hand'} Planted #{I18n.l @planting.planted_at}
|
||||
- if @planting.first_harvest_date.present?
|
||||
%p.small #{harvest_icon} Harvest started #{I18n.l @planting.first_harvest_date}
|
||||
- elsif @planting.first_harvest_predicted_at.present?
|
||||
%p.small #{harvest_icon} First harvest expected #{I18n.l @planting.first_harvest_predicted_at}
|
||||
- if @planting.finished_at.present?
|
||||
%p.small #{finished_icon} Finished #{I18n.l @planting.finished_at}
|
||||
- elsif @planting.finish_predicted_at.present?
|
||||
%p.small #{finished_icon} Finish expected #{I18n.l @planting.finish_predicted_at}
|
||||
- if @planting.planted_at.present? && @planting.expected_lifespan.present?
|
||||
- if planting.planted_at.present?
|
||||
%p.small #{ image_icon 'planting-hand'} Planted #{I18n.l planting.planted_at}
|
||||
- if planting.first_harvest_date.present?
|
||||
%p.small #{harvest_icon} Harvest started #{I18n.l planting.first_harvest_date}
|
||||
- elsif planting.first_harvest_predicted_at.present?
|
||||
%p.small #{harvest_icon} First harvest expected #{I18n.l planting.first_harvest_predicted_at}
|
||||
- if planting.finished_at.present?
|
||||
%p.small #{finished_icon} Finished #{I18n.l planting.finished_at}
|
||||
- elsif planting.finish_predicted_at.present?
|
||||
%p.small #{finished_icon} Finish expected #{I18n.l planting.finish_predicted_at}
|
||||
- if planting.planted_at.present? && planting.expected_lifespan.present?
|
||||
.progress
|
||||
.progress-bar{"aria-valuemax" => "100", "aria-valuemin" => "0", "aria-valuenow" => @planting.percentage_grown, role: "progressbar", style: "width: #{@planting.percentage_grown}%"}
|
||||
.progress-bar{"aria-valuemax" => "100", "aria-valuemin" => "0", "aria-valuenow" => planting.percentage_grown, role: "progressbar", style: "width: #{planting.percentage_grown}%"}
|
||||
%ul.list-unstyled.d-flex.justify-content-between
|
||||
- in_weeks(@planting.expected_lifespan).times do |week_number|
|
||||
%li{class: @planting.planted_at + week_number.weeks > Time.zone.today ? 'text-muted progress-fade' : '', 'data-toggle': "tooltip", 'data-placement': "top", title: I18n.l(@planting.planted_at + week_number.weeks)}
|
||||
- in_weeks(planting.expected_lifespan).times do |week_number|
|
||||
%li{class: planting.planted_at + week_number.weeks > Time.zone.today ? 'text-muted progress-fade' : '', 'data-toggle': "tooltip", 'data-placement': "top", title: I18n.l(planting.planted_at + week_number.weeks)}
|
||||
= render 'timeline_icon',
|
||||
planting: @planting,
|
||||
planting: planting,
|
||||
week_number: week_number,
|
||||
date_this_week: @planting.planted_at + week_number.weeks
|
||||
(One emojii = 1 week)
|
||||
date_this_week: planting.planted_at + week_number.weeks
|
||||
%small (One emojii = 1 week)
|
||||
.harvest-months
|
||||
Harvest months:
|
||||
- if planting.harvest_months.empty?
|
||||
%span.text-muted
|
||||
We need more data on this crop in your latitude. There's not enough
|
||||
info yet to predict harvests.
|
||||
- else
|
||||
- (1..12).each do |month|
|
||||
- if planting.harvest_months.keys().include?(month.to_f)
|
||||
.badge.badge-info.badge-harvesting{id: "month-#{month}"}
|
||||
= I18n.t('date.abbr_month_names')[month]
|
||||
- else
|
||||
.badge.text-muted{'aria-hidden': "true", id: "month-#{month}"}
|
||||
= I18n.t('date.abbr_month_names')[month]
|
||||
- unless planting.garden.location.blank?
|
||||
in #{link_to planting.garden.location, place_path(planting.garden.location)}
|
||||
|
||||
@@ -14,6 +14,15 @@
|
||||
%li.breadcrumb-item= link_to @planting.owner, member_plantings_path(@planting.owner)
|
||||
%li.breadcrumb-item.active= link_to @planting.crop.name, @planting
|
||||
|
||||
|
||||
- if @planting.parent_seed.nil? && @matching_seeds && @matching_seeds.any? && @planting.owner == current_member
|
||||
.alert.alert-info{role: "alert"}
|
||||
= bootstrap_form_for(@planting) do |f|
|
||||
Is this from one of these plantings?
|
||||
- @matching_seeds.each do |seed|
|
||||
= f.radio_button :parent_seed_id, seed.id, label: seed
|
||||
= f.submit "save", class: 'btn btn-sm'
|
||||
|
||||
.planting
|
||||
.row
|
||||
.col-md-8.col-xs-12
|
||||
@@ -30,13 +39,6 @@
|
||||
|
||||
= render 'timeline', planting: @planting
|
||||
|
||||
- if @planting.parent_seed.nil? && @matching_seeds && @matching_seeds.any? && @planting.owner == current_member
|
||||
.alert.alert-info{role: "alert"}
|
||||
= bootstrap_form_for(@planting) do |f|
|
||||
Is this from one of these plantings?
|
||||
- @matching_seeds.each do |seed|
|
||||
= f.radio_button :parent_seed_id, seed.id, label: seed
|
||||
= f.submit "save", class: 'btn btn-sm'
|
||||
|
||||
.col-md-4.col-xs-12
|
||||
= render 'plantings/owner', planting: @planting
|
||||
@@ -52,16 +54,33 @@
|
||||
:growstuff_markdown
|
||||
#{strip_tags(@planting.description)}
|
||||
%section= render 'plantings/photos', photos: @photos, planting: @planting
|
||||
|
||||
%section.harvests
|
||||
%a{name: 'harvests'}
|
||||
= render 'plantings/harvests', planting: @planting
|
||||
|
||||
%section.descendants
|
||||
%a{name: 'seeds'}
|
||||
= render 'plantings/descendants', planting: @planting
|
||||
|
||||
.col-md-4.col-xs-12
|
||||
= render 'plantings/actions', planting: @planting
|
||||
%hr/
|
||||
= render @planting.crop
|
||||
.row
|
||||
.col-md-6
|
||||
%section.harvests
|
||||
%a{name: 'harvests'}
|
||||
= render 'plantings/harvests', planting: @planting
|
||||
.col-md-6
|
||||
%section.descendants
|
||||
%a{name: 'seeds'}
|
||||
= render 'plantings/descendants', planting: @planting
|
||||
|
||||
- if @planting.location
|
||||
%section.neighbours
|
||||
%h2 World Neighbours
|
||||
- @neighbours.each do |planting|
|
||||
= link_to planting, class: 'list-group-item list-group-item-action flex-column align-items-start' do
|
||||
.d-flex.w-100.justify-content-between
|
||||
%p.mb-2
|
||||
= image_tag planting_image_path(planting), width: 75, class: 'rounded shadow'
|
||||
.text-right
|
||||
%h5= planting.crop.name
|
||||
- if planting.planted_from.present?
|
||||
%span.badge.badge-success= planting.planted_from.pluralize
|
||||
%small.text-muted
|
||||
planted by #{planting.owner}
|
||||
in #{planting.garden.location}
|
||||
%p Other #{@planting.crop.name} plantings at the same latitude
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ "${COVERAGE}" = "true" ]; then
|
||||
set -euv
|
||||
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter;
|
||||
chmod +x ./cc-test-reporter;
|
||||
fi
|
||||
@@ -39,7 +39,7 @@ FactoryBot.define do
|
||||
factory :london_member do
|
||||
sequence(:login_name) { |n| "JohnH#{n}" } # for the astronomer who figured out longitude
|
||||
location { 'Greenwich, UK' }
|
||||
# including lat/long explicitly because geocoder doesn't work with FG
|
||||
# including lat/long explicitly because geocoder doesn't work with FactoryBot
|
||||
latitude { 51.483 }
|
||||
longitude { 0.004 }
|
||||
end
|
||||
|
||||
60
spec/features/plantings/prediction_spec.rb
Normal file
60
spec/features/plantings/prediction_spec.rb
Normal file
@@ -0,0 +1,60 @@
|
||||
require "rails_helper"
|
||||
require 'custom_matchers'
|
||||
describe "Display a planting", :js, :elasticsearch do
|
||||
describe 'planting perennial' do
|
||||
let(:garden) { FactoryBot.create :garden, location: 'Edinburgh' }
|
||||
let(:crop) { FactoryBot.create(:crop, name: 'feijoa', perennial: true) }
|
||||
let(:planting) { FactoryBot.create :planting, crop: crop, garden: garden, owner: garden.owner }
|
||||
|
||||
describe 'no harvest to predict from' do
|
||||
before { visit planting_path(planting) }
|
||||
it { expect(planting.harvest_months).to eq({}) }
|
||||
it { expect(page).to have_content 'We need more data on this crop in your latitude.' }
|
||||
end
|
||||
|
||||
describe 'harvests used to predict' do
|
||||
before do
|
||||
FactoryBot.create :harvest, planting: planting, crop: crop, harvested_at: '1 May 2019'
|
||||
FactoryBot.create :harvest, planting: planting, crop: crop, harvested_at: '18 June 2019'
|
||||
FactoryBot.create_list :harvest, 4, planting: planting, crop: crop, harvested_at: '18 August 2019'
|
||||
end
|
||||
before { visit planting_path(planting) }
|
||||
it { expect(page.find("#month-1")[:class]).not_to include("badge-harvesting") }
|
||||
it { expect(page.find("#month-2")[:class]).not_to include("badge-harvesting") }
|
||||
it { expect(page.find("#month-5")[:class]).to include("badge-harvesting") }
|
||||
it { expect(page.find("#month-6")[:class]).to include("badge-harvesting") }
|
||||
it { expect(page.find("#month-8")[:class]).to include("badge-harvesting") }
|
||||
end
|
||||
|
||||
describe 'nearby plantings used to predict' do
|
||||
# Note the locations used need to be stubbed in geocoder
|
||||
|
||||
before do
|
||||
# Near by planting with harvests
|
||||
nearby_garden = FactoryBot.create :garden, location: 'Greenwich, UK'
|
||||
nearby_planting = FactoryBot.create :planting, crop: crop,
|
||||
garden: nearby_garden, owner: nearby_garden.owner, planted_at: '1 January 2000'
|
||||
FactoryBot.create :harvest, planting: nearby_planting, crop: crop,
|
||||
harvested_at: '1 May 2019'
|
||||
FactoryBot.create :harvest, planting: nearby_planting, crop: crop,
|
||||
harvested_at: '18 June 2019'
|
||||
FactoryBot.create_list :harvest, 4, planting: nearby_planting, crop: crop,
|
||||
harvested_at: '18 August 2008'
|
||||
|
||||
# far away planting harvests
|
||||
faraway_garden = FactoryBot.create :garden, location: 'Amundsen-Scott Base, Antarctica'
|
||||
faraway_planting = FactoryBot.create :planting, garden: faraway_garden, crop: crop,
|
||||
owner: faraway_garden.owner, planted_at: '16 May 2001'
|
||||
|
||||
FactoryBot.create_list :harvest, 4, planting: faraway_planting, crop: crop,
|
||||
harvested_at: '18 December 2006'
|
||||
end
|
||||
before { visit planting_path(planting) }
|
||||
it { expect(page.find("#month-1")[:class]).not_to include("badge-harvesting") }
|
||||
it { expect(page.find("#month-2")[:class]).not_to include("badge-harvesting") }
|
||||
it { expect(page.find("#month-5")[:class]).to include("badge-harvesting") }
|
||||
it { expect(page.find("#month-6")[:class]).to include("badge-harvesting") }
|
||||
it { expect(page.find("#month-8")[:class]).to include("badge-harvesting") }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -61,6 +61,7 @@ describe "Display a planting", :js, :elasticsearch do
|
||||
it { expect(find('.plantingfact--quantity')).to have_text '100' }
|
||||
end
|
||||
end
|
||||
|
||||
context 'signed in' do
|
||||
include_context 'signed in member'
|
||||
before { visit planting_path(planting) }
|
||||
|
||||
@@ -207,6 +207,49 @@ describe Planting do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'planting perennial' do
|
||||
let(:crop) { FactoryBot.create(:crop, name: 'feijoa', perennial: true) }
|
||||
it { expect(planting.perennial?).to eq true }
|
||||
describe 'no harvest to predict from' do
|
||||
it { expect(planting.harvest_months).to eq({}) }
|
||||
end
|
||||
|
||||
describe 'harvests used to predict' do
|
||||
before do
|
||||
FactoryBot.create :harvest, planting: planting, crop: crop, harvested_at: '1 May 2019'
|
||||
FactoryBot.create :harvest, planting: planting, crop: crop, harvested_at: '18 June 2019'
|
||||
FactoryBot.create_list :harvest, 4, planting: planting, crop: crop, harvested_at: '18 August 2019'
|
||||
end
|
||||
it { expect(planting.harvest_months).to eq(5 => 1, 6 => 1, 8 => 4) }
|
||||
end
|
||||
|
||||
describe 'nearby plantings used to predict' do
|
||||
# Note the locations used need to be stubbed in geocoder
|
||||
let(:garden) { FactoryBot.create :garden, location: 'Edinburgh', owner: garden_owner }
|
||||
|
||||
before do
|
||||
# Near by planting with harvests
|
||||
nearby_garden = FactoryBot.create :garden, location: 'Greenwich, UK'
|
||||
nearby_planting = FactoryBot.create :planting, crop: crop,
|
||||
garden: nearby_garden, owner: nearby_garden.owner, planted_at: '1 January 2000'
|
||||
FactoryBot.create :harvest, planting: nearby_planting, crop: crop,
|
||||
harvested_at: '1 May 2019'
|
||||
FactoryBot.create :harvest, planting: nearby_planting, crop: crop,
|
||||
harvested_at: '18 June 2019'
|
||||
FactoryBot.create_list :harvest, 4, planting: nearby_planting, crop: crop,
|
||||
harvested_at: '18 August 2008'
|
||||
|
||||
# far away planting harvests
|
||||
faraway_garden = FactoryBot.create :garden, location: 'Amundsen-Scott Base, Antarctica'
|
||||
faraway_planting = FactoryBot.create :planting, garden: faraway_garden, crop: crop,
|
||||
owner: faraway_garden.owner, planted_at: '16 May 2001'
|
||||
|
||||
FactoryBot.create_list :harvest, 4, planting: faraway_planting, crop: crop,
|
||||
harvested_at: '18 December 2006'
|
||||
end
|
||||
it { expect(planting.harvest_months).to eq(5 => 1, 6 => 1, 8 => 4) }
|
||||
end
|
||||
end
|
||||
it 'has an owner' do
|
||||
planting.owner.should be_an_instance_of Member
|
||||
end
|
||||
|
||||
@@ -13,6 +13,7 @@ describe "plantings/show" do
|
||||
before do
|
||||
assign(:planting, planting)
|
||||
assign(:photos, planting.photos.paginate(page: 1))
|
||||
assign(:neighbours, planting.nearby_same_crop)
|
||||
controller.stub(:current_user) { member }
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user